GNU Octave 11.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
 
Loading...
Searching...
No Matches
oct-norm.cc
Go to the documentation of this file.
1////////////////////////////////////////////////////////////////////////
2//
3// Copyright (C) 2008-2026 The Octave Project Developers
4//
5// See the file COPYRIGHT.md in the top-level directory of this
6// distribution or <https://octave.org/copyright/>.
7//
8// This file is part of Octave.
9//
10// Octave is free software: you can redistribute it and/or modify it
11// under the terms of the GNU General Public License as published by
12// the Free Software Foundation, either version 3 of the License, or
13// (at your option) any later version.
14//
15// Octave is distributed in the hope that it will be useful, but
16// WITHOUT ANY WARRANTY; without even the implied warranty of
17// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18// GNU General Public License for more details.
19//
20// You should have received a copy of the GNU General Public License
21// along with Octave; see the file COPYING. If not, see
22// <https://www.gnu.org/licenses/>.
23//
24////////////////////////////////////////////////////////////////////////
25
26#if defined (HAVE_CONFIG_H)
27# include "config.h"
28#endif
29
30#include <cmath>
31
32#include <algorithm>
33#include <iostream>
34#include <limits>
35#include <type_traits>
36#include <vector>
37
38#include "Array-oct.h"
39#include "CColVector.h"
40#include "CMatrix.h"
41#include "CRowVector.h"
42#include "CSparse.h"
43#include "MArray.h"
44#include "blas-proto.h"
45#include "dColVector.h"
46#include "dDiagMatrix.h"
47#include "dMatrix.h"
48#include "dRowVector.h"
49#include "dSparse.h"
50#include "f77-fcn.h"
51#include "fCColVector.h"
52#include "fCMatrix.h"
53#include "fCRowVector.h"
54#include "fColVector.h"
55#include "fDiagMatrix.h"
56#include "fMatrix.h"
57#include "fRowVector.h"
58#include "lapack-proto.h"
59#include "lo-ieee.h"
60#include "mappers.h"
61#include "mx-cm-s.h"
62#include "mx-fcm-fs.h"
63#include "mx-fs-fcm.h"
64#include "mx-s-cm.h"
65#include "oct-cmplx.h"
66#include "oct-error.h"
67#include "oct-norm.h"
68#include "quit.h"
69#include "svd.h"
70
71#if defined (HAVE_ARPACK)
72# include "eigs-base.h"
73#endif
74
76
77// BLAS NRM2 wrappers for vector 2-norm
78
79inline double
81{
82 F77_INT f77_n = to_f77_int (n);
83 F77_INT f77_incx = to_f77_int (incx);
84 F77_DBLE result;
85 F77_FUNC (xdnrm2, XDNRM2) (f77_n, x, f77_incx, result);
86 return result;
87}
88
89inline float
91{
92 F77_INT f77_n = to_f77_int (n);
93 F77_INT f77_incx = to_f77_int (incx);
94 F77_REAL result;
95 F77_FUNC (xsnrm2, XSNRM2) (f77_n, x, f77_incx, result);
96 return result;
97}
98
99inline double
101{
102 F77_INT f77_n = to_f77_int (n);
103 F77_INT f77_incx = to_f77_int (incx);
104 F77_DBLE result;
105 F77_FUNC (xdznrm2, XDZNRM2) (f77_n, F77_CONST_DBLE_CMPLX_ARG (x), f77_incx, result);
106 return result;
107}
108
109inline float
111{
112 F77_INT f77_n = to_f77_int (n);
113 F77_INT f77_incx = to_f77_int (incx);
114 F77_REAL result;
115 F77_FUNC (xscnrm2, XSCNRM2) (f77_n, F77_CONST_CMPLX_ARG (x), f77_incx, result);
116 return result;
117}
118
119// LAPACK LANGE wrappers for dense matrix norms.
120// norm_type: 'O'/'1' = 1-norm, 'I' = inf-norm, 'F' = Frobenius
121//
122// Note: LANGE mishandles Inf due to internal scaling (Inf/Inf = NaN).
123// We fix this by checking for Inf when LANGE returns NaN.
124
125template <typename T, typename R>
126inline R
127lange_inf_fixup (R result, const T *data, octave_idx_type numel)
128{
129 // LANGE returned NaN - check if it should be Inf instead.
130 // If there's a genuine NaN in input, keep returning NaN.
131 // Only scan the matrix if LANGE returned NaN (no overhead otherwise).
132 if (math::isnan (result))
133 {
134 bool has_inf = false;
135 for (octave_idx_type i = 0; i < numel; i++)
136 {
137 if (math::isnan (data[i]))
138 return result; // Genuine NaN takes precedence
139 if (math::isinf (data[i]))
140 has_inf = true;
141 }
142 if (has_inf)
143 return numeric_limits<R>::Inf ();
144 }
145 return result;
146}
147
148inline double
149lange (char norm_type, const Matrix& m)
150{
151 F77_INT mm = to_f77_int (m.rows ());
152 F77_INT nn = to_f77_int (m.cols ());
153 F77_INT lda = std::max (mm, static_cast<F77_INT> (1));
154
155 OCTAVE_LOCAL_BUFFER (double, work, mm);
156
157 F77_DBLE result;
158 F77_FUNC (xdlange, XDLANGE) (F77_CONST_CHAR_ARG (&norm_type), mm, nn,
159 m.data (), lda, work, result
160 F77_CHAR_ARG_LEN (1));
161 return lange_inf_fixup (result, m.data (), m.numel ());
162}
163
164inline float
165lange (char norm_type, const FloatMatrix& m)
166{
167 F77_INT mm = to_f77_int (m.rows ());
168 F77_INT nn = to_f77_int (m.cols ());
169 F77_INT lda = std::max (mm, static_cast<F77_INT> (1));
170
171 OCTAVE_LOCAL_BUFFER (float, work, mm);
172
173 F77_REAL result;
174 F77_FUNC (xslange, XSLANGE) (F77_CONST_CHAR_ARG (&norm_type), mm, nn,
175 m.data (), lda, work, result
176 F77_CHAR_ARG_LEN (1));
177 return lange_inf_fixup (result, m.data (), m.numel ());
178}
179
180inline double
181lange (char norm_type, const ComplexMatrix& m)
182{
183 F77_INT mm = to_f77_int (m.rows ());
184 F77_INT nn = to_f77_int (m.cols ());
185 F77_INT lda = std::max (mm, static_cast<F77_INT> (1));
186
187 OCTAVE_LOCAL_BUFFER (double, work, mm);
188
189 F77_DBLE result;
190 F77_FUNC (xzlange, XZLANGE) (F77_CONST_CHAR_ARG (&norm_type), mm, nn,
192 lda, work, result
193 F77_CHAR_ARG_LEN (1));
194 return lange_inf_fixup (result, m.data (), m.numel ());
195}
196
197inline float
198lange (char norm_type, const FloatComplexMatrix& m)
199{
200 F77_INT mm = to_f77_int (m.rows ());
201 F77_INT nn = to_f77_int (m.cols ());
202 F77_INT lda = std::max (mm, static_cast<F77_INT> (1));
203
204 OCTAVE_LOCAL_BUFFER (float, work, mm);
205
206 F77_REAL result;
207 F77_FUNC (xclange, XCLANGE) (F77_CONST_CHAR_ARG (&norm_type),
208 mm, nn, F77_CONST_CMPLX_ARG (m.data ()),
209 lda, work, result
210 F77_CHAR_ARG_LEN (1));
211 return lange_inf_fixup (result, m.data (), m.numel ());
212}
213
214// Norm accumulators for various norms.
215// Reference: N. Higham, "Estimating the Matrix p-Norm",
216// Numer. Math., 62, pp. 539-555, 1992.
217//
218// For float types, we accumulate in double for better precision.
219// For double types, we use Hammarling scaling to avoid overflow/underflow.
220// Kahan compensation is NOT used since these accumulators are primarily
221// used in iterative methods where sqrt(eps) precision is sufficient.
222
223// Accumulator type: float promotes to double for better precision
224template <typename R>
225using accum_type = std::conditional_t<std::is_same_v<R, float>, double, R>;
226
227// norm accumulator for the p-norm
228// Uses Hammarling scaling to avoid overflow.
229template <typename R>
230class norm_accumulator_p
231{
232public:
233 using AT = accum_type<R>;
234
235 norm_accumulator_p (R pp) : m_p (pp), m_scl (0), m_sum (1) { }
236
237 OCTAVE_DEFAULT_CONSTRUCT_COPY_MOVE_DELETE (norm_accumulator_p)
238
239 template <typename U>
240 void accum (U val)
241 {
242 AT t = std::abs (val);
243 if (t != 0)
244 {
245 if (m_scl == t)
246 m_sum += 1;
247 else if (m_scl < t)
248 {
249 m_sum *= std::pow (m_scl / t, static_cast<AT> (m_p));
250 m_sum += 1;
251 m_scl = t;
252 }
253 else
254 m_sum += std::pow (t / m_scl, static_cast<AT> (m_p));
255 }
256 }
257
258 operator R ()
259 {
260 return static_cast<R> (m_scl * std::pow (m_sum, 1 / static_cast<AT> (m_p)));
261 }
262
263private:
264 R m_p;
265 AT m_scl, m_sum;
266};
267
268// norm accumulator for the minus p-pseudonorm
269template <typename R>
270class norm_accumulator_mp
271{
272public:
273 using AT = accum_type<R>;
274
275 norm_accumulator_mp (R pp) : m_p (pp), m_scl (0), m_sum (1) { }
276
277 OCTAVE_DEFAULT_CONSTRUCT_COPY_MOVE_DELETE (norm_accumulator_mp)
278
279 template <typename U>
280 void accum (U val)
281 {
282 AT t = 1 / std::abs (val);
283 if (m_scl == t)
284 m_sum += 1;
285 else if (m_scl < t)
286 {
287 m_sum *= std::pow (m_scl / t, static_cast<AT> (m_p));
288 m_sum += 1;
289 m_scl = t;
290 }
291 else if (t != 0)
292 m_sum += std::pow (t / m_scl, static_cast<AT> (m_p));
293 }
294
295 operator R ()
296 {
297 return static_cast<R> (m_scl * std::pow (m_sum, -1 / static_cast<AT> (m_p)));
298 }
299
300private:
301 R m_p;
302 AT m_scl, m_sum;
303};
304
305// norm accumulator for the 2-norm (euclidean)
306// Uses Hammarling scaling to avoid overflow.
307template <typename R>
308class norm_accumulator_2
309{
310public:
311 using AT = accum_type<R>;
312
313 norm_accumulator_2 () : m_scl (0), m_sum (0) { }
314
315 OCTAVE_DEFAULT_COPY_MOVE_DELETE (norm_accumulator_2)
316
317 void accum (R val)
318 {
319 AT t = std::abs (val);
320 if (t != 0)
321 {
322 if (m_scl == t)
323 m_sum += 1;
324 else if (m_scl < t)
325 {
326 AT r = m_scl / t;
327 m_sum *= r * r;
328 m_sum += 1;
329 m_scl = t;
330 }
331 else
332 {
333 AT r = t / m_scl;
334 m_sum += r * r;
335 }
336 }
337 }
338
339 void accum (std::complex<R> val)
340 {
341 accum (val.real ());
342 accum (val.imag ());
343 }
344
345 operator R ()
346 {
347 return static_cast<R> (m_scl * std::sqrt (m_sum));
348 }
349
350private:
351 AT m_scl, m_sum;
352};
353
354// norm accumulator for the 1-norm (city metric)
355template <typename R>
356class norm_accumulator_1
357{
358public:
359 using AT = accum_type<R>;
360
361 norm_accumulator_1 () : m_sum (0) { }
362
363 OCTAVE_DEFAULT_COPY_MOVE_DELETE (norm_accumulator_1)
364
365 template <typename U>
366 void accum (U val)
367 {
368 m_sum += std::abs (val);
369 }
370
371 operator R () { return static_cast<R> (m_sum); }
372
373private:
374 AT m_sum;
375};
376
377// norm accumulator for the inf-norm (max metric)
378template <typename R>
379class norm_accumulator_inf
380{
381public:
382
383 norm_accumulator_inf () : m_max (0) { }
384
385 OCTAVE_DEFAULT_COPY_MOVE_DELETE (norm_accumulator_inf)
386
387 template <typename U>
388 void accum (U val)
389 {
390 if (math::isnan (val))
391 m_max = numeric_limits<R>::NaN ();
392 else
393 m_max = std::max (m_max, std::abs (val));
394 }
395
396 operator R () { return m_max; }
397
398private:
399 R m_max;
400};
401
402// norm accumulator for the -inf pseudonorm (min abs value)
403template <typename R>
404class norm_accumulator_minf
405{
406public:
407
408 norm_accumulator_minf () : m_min (numeric_limits<R>::Inf ()) { }
409
410 OCTAVE_DEFAULT_COPY_MOVE_DELETE (norm_accumulator_minf)
411
412 template <typename U>
413 void accum (U val)
414 {
415 if (math::isnan (val))
416 m_min = numeric_limits<R>::NaN ();
417 else
418 m_min = std::min (m_min, std::abs (val));
419 }
420
421 operator R () { return m_min; }
422
423private:
424 R m_min;
425};
426
427// norm accumulator for the 0-pseudonorm (hamming distance)
428template <typename R>
429class norm_accumulator_0
430{
431public:
432
433 norm_accumulator_0 () : m_num (0) { }
434
435 OCTAVE_DEFAULT_COPY_MOVE_DELETE (norm_accumulator_0)
436
437 template <typename U>
438 void accum (U val)
439 {
440 if (val != static_cast<U> (0)) ++m_num;
441 }
442
443 operator R () { return m_num; }
444
445private:
446 unsigned int m_num;
447};
448
449// OK, we're armed :) Now let's go for the fun
450
451template <typename T, typename R, typename ACC>
452inline void
453vector_norm (const Array<T>& v, R& res, ACC acc)
454{
455 for (octave_idx_type i = 0; i < v.numel (); i++)
456 acc.accum (v(i));
457
458 res = acc;
459}
460
461// sparse version - only iterates over non-zero elements
462template <typename T, typename R, typename ACC>
463inline void
464vector_norm (const MSparse<T>& v, R& res, ACC acc)
465{
466 octave_idx_type nnz = v.nnz ();
467 for (octave_idx_type i = 0; i < nnz; i++)
468 acc.accum (v.data (i));
469
470 res = acc;
471}
472
473// dense versions
474template <typename T, typename R, typename ACC>
475void
476column_norms (const MArray<T>& m, MArray<R>& res, ACC acc)
477{
478 res = MArray<R> (dim_vector (1, m.columns ()));
479 for (octave_idx_type j = 0; j < m.columns (); j++)
480 {
481 ACC accj = acc;
482 for (octave_idx_type i = 0; i < m.rows (); i++)
483 accj.accum (m(i, j));
484
485 res.xelem (j) = accj;
486 }
487}
488
489template <typename T, typename R, typename ACC>
490void
491row_norms (const MArray<T>& m, MArray<R>& res, ACC acc)
492{
493 res = MArray<R> (dim_vector (m.rows (), 1));
494 std::vector<ACC> acci (m.rows (), acc);
495 for (octave_idx_type j = 0; j < m.columns (); j++)
496 {
497 for (octave_idx_type i = 0; i < m.rows (); i++)
498 acci[i].accum (m(i, j));
499 }
500
501 for (octave_idx_type i = 0; i < m.rows (); i++)
502 res.xelem (i) = acci[i];
503}
504
505// sparse versions
506template <typename T, typename R, typename ACC>
507void
508column_norms (const MSparse<T>& m, MArray<R>& res, ACC acc)
509{
510 res = MArray<R> (dim_vector (1, m.columns ()));
511 for (octave_idx_type j = 0; j < m.columns (); j++)
512 {
513 ACC accj = acc;
514 for (octave_idx_type k = m.cidx (j); k < m.cidx (j+1); k++)
515 accj.accum (m.data (k));
516
517 res.xelem (j) = accj;
518 }
519}
520
521template <typename T, typename R, typename ACC>
522void
523row_norms (const MSparse<T>& m, MArray<R>& res, ACC acc)
524{
525 res = MArray<R> (dim_vector (m.rows (), 1));
526 std::vector<ACC> acci (m.rows (), acc);
527 for (octave_idx_type j = 0; j < m.columns (); j++)
528 {
529 for (octave_idx_type k = m.cidx (j); k < m.cidx (j+1); k++)
530 acci[m.ridx (k)].accum (m.data (k));
531 }
532
533 for (octave_idx_type i = 0; i < m.rows (); i++)
534 res.xelem (i) = acci[i];
535}
536
537// Dense vector 2-norm using BLAS NRM2
538template <typename T, typename R>
539inline R
541{
542 return blas_nrm2 (v.numel (), v.data (), 1);
543}
544
545// Dispatcher for dense arrays - use BLAS for 2-norm
546template <typename T, typename R>
547R
548vector_norm (const MArray<T>& v, R p)
549{
550 R res;
551 if (p == 2)
552 res = vector_norm_2_blas<T, R> (v);
553 else if (p == 1)
554 vector_norm (v, res, norm_accumulator_1<R> ());
555 else if (math::isinf (p))
556 {
557 if (p > 0)
558 vector_norm (v, res, norm_accumulator_inf<R> ());
559 else
560 vector_norm (v, res, norm_accumulator_minf<R> ());
561 }
562 else if (p == 0)
563 vector_norm (v, res, norm_accumulator_0<R> ());
564 else if (p > 0)
565 vector_norm (v, res, norm_accumulator_p<R> (p));
566 else
567 vector_norm (v, res, norm_accumulator_mp<R> (p));
568 return res;
569}
570
571// Dispatcher for sparse arrays - accumulators only (no BLAS)
572template <typename T, typename R>
573R
574vector_norm (const MSparse<T>& v, R p)
575{
576 R res;
577 if (p == 2)
578 vector_norm (v, res, norm_accumulator_2<R> ());
579 else if (p == 1)
580 vector_norm (v, res, norm_accumulator_1<R> ());
581 else if (math::isinf (p))
582 {
583 if (p > 0)
584 vector_norm (v, res, norm_accumulator_inf<R> ());
585 else
586 vector_norm (v, res, norm_accumulator_minf<R> ());
587 }
588 else if (p == 0)
589 vector_norm (v, res, norm_accumulator_0<R> ());
590 else if (p > 0)
591 vector_norm (v, res, norm_accumulator_p<R> (p));
592 else
593 vector_norm (v, res, norm_accumulator_mp<R> (p));
594 return res;
595}
596
597// Dispatchers for column/row norms (use accumulators)
598#define DEFINE_COLROW_DISPATCHER(FCN_NAME, ARG_TYPE, RES_TYPE) \
599template <typename T, typename R> \
600RES_TYPE FCN_NAME (const ARG_TYPE& v, R p) \
601{ \
602 RES_TYPE res; \
603 if (p == 2) \
604 FCN_NAME (v, res, norm_accumulator_2<R> ()); \
605 else if (p == 1) \
606 FCN_NAME (v, res, norm_accumulator_1<R> ()); \
607 else if (math::isinf (p)) \
608 { \
609 if (p > 0) \
610 FCN_NAME (v, res, norm_accumulator_inf<R> ()); \
611 else \
612 FCN_NAME (v, res, norm_accumulator_minf<R> ()); \
613 } \
614 else if (p == 0) \
615 FCN_NAME (v, res, norm_accumulator_0<R> ()); \
616 else if (p > 0) \
617 FCN_NAME (v, res, norm_accumulator_p<R> (p)); \
618 else \
619 FCN_NAME (v, res, norm_accumulator_mp<R> (p)); \
620 return res; \
621}
622
627
628// The approximate subproblem in Higham's method. Find lambda and mu such
629// that norm ([lambda, mu], p) == 1 and norm (y*lambda + col*mu, p) is
630// maximized.
631// Real version. As in Higham's paper.
632template <typename ColVectorT, typename R>
633static void
634higham_subp (const ColVectorT& y, const ColVectorT& col,
635 octave_idx_type nsamp, R p, R& lambda, R& mu)
636{
637 R nrm = 0;
638 for (octave_idx_type i = 0; i < nsamp; i++)
639 {
640 octave_quit ();
641 R fi = i * static_cast<R> (M_PI) / nsamp;
642 R lambda1 = cos (fi);
643 R mu1 = sin (fi);
644 R lmnr = std::pow (std::pow (std::abs (lambda1), p)
645 + std::pow (std::abs (mu1), p), 1 / p);
646 lambda1 /= lmnr; mu1 /= lmnr;
647 R nrm1 = vector_norm (lambda1 * y + mu1 * col, p);
648 if (nrm1 > nrm)
649 {
650 lambda = lambda1;
651 mu = mu1;
652 nrm = nrm1;
653 }
654 }
655}
656
657// Complex version. Higham's paper does not deal with complex case, so we
658// use a simple extension. First, guess the magnitudes as in real version,
659// then try to rotate lambda to improve further.
660template <typename ColVectorT, typename R>
661static void
662higham_subp (const ColVectorT& y, const ColVectorT& col,
663 octave_idx_type nsamp, R p,
664 std::complex<R>& lambda, std::complex<R>& mu)
665{
666 typedef std::complex<R> CR;
667 R nrm = 0;
668 lambda = 1.0;
669 CR lamcu = lambda / std::abs (lambda);
670 // Probe magnitudes
671 for (octave_idx_type i = 0; i < nsamp; i++)
672 {
673 octave_quit ();
674 R fi = i * static_cast<R> (M_PI) / nsamp;
675 R lambda1 = cos (fi);
676 R mu1 = sin (fi);
677 R lmnr = std::pow (std::pow (std::abs (lambda1), p)
678 + std::pow (std::abs (mu1), p), 1 / p);
679 lambda1 /= lmnr; mu1 /= lmnr;
680 R nrm1 = vector_norm (lambda1 * lamcu * y + mu1 * col, p);
681 if (nrm1 > nrm)
682 {
683 lambda = lambda1 * lamcu;
684 mu = mu1;
685 nrm = nrm1;
686 }
687 }
688 R lama = std::abs (lambda);
689 // Probe orientation
690 for (octave_idx_type i = 0; i < nsamp; i++)
691 {
692 octave_quit ();
693 R fi = i * static_cast<R> (M_PI) / nsamp;
694 lamcu = CR (cos (fi), sin (fi));
695 R nrm1 = vector_norm (lama * lamcu * y + mu * col, p);
696 if (nrm1 > nrm)
697 {
698 lambda = lama * lamcu;
699 nrm = nrm1;
700 }
701 }
702}
703
704// the p-dual element (should work for both real and complex)
705template <typename T, typename R>
706inline T
708{
709 return math::signum (x) * std::pow (std::abs (x), p - 1);
710}
711
712// the VectorT is used for vectors, but actually it has to be
713// a Matrix type to allow all the operations. For instance SparseMatrix
714// does not support multiplication with column/row vectors.
715// the dual vector
716template <typename VectorT, typename R>
717VectorT
718dual_p (const VectorT& x, R p, R q)
719{
720 VectorT res (x.dims ());
721 for (octave_idx_type i = 0; i < x.numel (); i++)
722 res.xelem (i) = elem_dual_p (x(i), p);
723 return res / vector_norm (res, q);
724}
725
726// Higham's hybrid method
727template <typename MatrixT, typename VectorT, typename R>
728R
729higham (const MatrixT& m, R p, R tol, int maxiter,
730 VectorT& x)
731{
732 x.resize (m.columns (), 1);
733 // the OSE part
734 VectorT y (m.rows (), 1, 0), z (m.rows (), 1);
735 typedef typename VectorT::element_type RR;
736 RR lambda = 0;
737 RR mu = 1;
738 for (octave_idx_type k = 0; k < m.columns (); k++)
739 {
740 octave_quit ();
741 VectorT col (m.column (k));
742 if (k > 0)
743 higham_subp (y, col, 4*k, p, lambda, mu);
744 for (octave_idx_type i = 0; i < k; i++)
745 x(i) *= lambda;
746 x(k) = mu;
747 y = lambda * y + mu * col;
748 }
749
750 // the PM part
751 x = x / vector_norm (x, p);
752 R q = p / (p - 1);
753
754 R gamma = 0, gamma1;
755 int iter = 0;
756 while (iter < maxiter)
757 {
758 octave_quit ();
759 y = m * x;
760 gamma1 = gamma;
761 gamma = vector_norm (y, p);
762 z = dual_p (y, p, q);
763 z = z.hermitian ();
764 z = z * m;
765
766 if (iter > 0 && (vector_norm (z, q) <= gamma
767 || (gamma - gamma1) <= tol * gamma))
768 break;
769
770 z = z.hermitian ();
771 x = dual_p (z, q, p);
772 iter++;
773 }
774
775 return gamma;
776}
777
778// derive column vector and SVD types
779
780constexpr const char *p_less1_gripe = "xnorm: p must be >= 1";
781
782// Maximum number of iterations for iterative norm algorithms.
783// 256 seems to be a good value. Eventually, we can provide a means to change
784// this constant from Octave.
785constexpr int max_norm_iter = 256;
786
787// version with SVD for dense matrices, uses LAPACK LANGE for 1/Inf norms
788template <typename MatrixT, typename VectorT, typename R>
789R
790svd_matrix_norm (const MatrixT& m, R p, VectorT)
791{
792 R res = 0;
793 if (p == 2)
794 {
795 math::svd<MatrixT> fact (m, math::svd<MatrixT>::Type::sigma_only);
796 res = fact.singular_values () (0, 0);
797 }
798 else if (p == 1)
799 res = lange ('O', m);
800 else if (math::isinf (p) && p > 1)
801 res = lange ('I', m);
802 else if (p > 1)
803 {
804 VectorT x;
805 const R sqrteps = std::sqrt (std::numeric_limits<R>::epsilon ());
806 res = higham (m, p, sqrteps, max_norm_iter, x);
807 }
808 else
810
811 return res;
812}
813
814// Helper for conjugate that works with real types (no-op for real)
815template <typename T>
816constexpr T safe_conj (T x)
817{
818 if constexpr (std::is_arithmetic_v<T>)
819 return x;
820 else
821 return std::conj (x);
822}
823
824// Helper for dot product (conjugate-linear in first argument)
825template <typename VectorT>
826auto
827vector_dot (const VectorT& x, const VectorT& y)
828{
829 using T = typename VectorT::element_type;
830 T sum = T (0);
831 for (octave_idx_type i = 0; i < x.numel (); i++)
832 sum += safe_conj (x(i)) * y(i);
833 return sum;
834}
835
836// Power iteration for largest singular value (sparse 2-norm)
837// Computes sigma_max = sqrt(lambda_max(A'*A)) via power method on A'*A
838// This avoids forming A'*A explicitly by using two sparse matvecs per iteration
839// Used as fallback when ARPACK is not available.
840template <typename MatrixT, typename VectorT, typename R>
841R
842sparse_2norm_power (const MatrixT& m, R tol, int maxiter, VectorT)
843{
844 octave_idx_type nc = m.columns ();
845 octave_idx_type nr = m.rows ();
846
847 if (nc == 0 || nr == 0)
848 return R (0);
849
850 // Work with smaller dimension for efficiency
851 const bool use_aat = (nr < nc);
852 octave_idx_type n = std::min (nr, nc);
853
854 // Initialize x = ones(n,1) normalized
855 VectorT x (n, 1, R (1) / std::sqrt (static_cast<R> (n)));
856 VectorT y (n, 1);
857
858 R lambda = 0, lambda_prev = 0;
859
860 for (int iter = 0; iter < maxiter; iter++)
861 {
862 octave_quit ();
863
864 // Compute y = (A'*A)*x or y = (A*A')*x via two sparse matvecs
865 if (use_aat)
866 {
867 // y = A * (A' * x) -- fewer ops when nr < nc
868 auto z = m.hermitian () * x;
869 y = m * z;
870 }
871 else
872 {
873 // y = A' * (A * x) -- fewer ops when nc <= nr
874 auto z = m * x;
875 y = m.hermitian () * z;
876 }
877
878 // Rayleigh quotient: lambda = x' * y
879 lambda_prev = lambda;
880 lambda = std::abs (vector_dot (x, y));
881
882 // Normalize y -> x for next iteration
883 const R norm_y = vector_norm (y, R (2));
884
885 if (norm_y == R (0))
886 return R (0);
887
888 x = y / norm_y;
889
890 // Check convergence
891 if (iter > 0 && std::abs (lambda - lambda_prev) <= tol * lambda)
892 break;
893 }
894
895 return std::sqrt (lambda);
896}
897
898#if defined (HAVE_ARPACK)
899
900// ARPACK-based sparse 2-norm using Implicitly Restarted Lanczos.
901// Builds augmented matrix [0, A; A', 0] whose eigenvalues are +-sigma_i.
902// More robust than power iteration for clustered singular values.
903double
904sparse_2norm_arpack (const SparseMatrix& m, double tol, int maxiter)
905{
906 octave_idx_type nc = m.cols ();
907 octave_idx_type nr = m.rows ();
908
909 if (nc == 0 || nr == 0)
910 return 0.0;
911
912 octave_quit ();
913
914 // Build augmented sparse matrix: [0, A; A', 0]
915 // Eigenvalues are +-sigma_i, so max |eigenvalue| = sigma_max
916 octave_idx_type n = nr + nc;
917 SparseMatrix aug (n, n);
918
919 // Insert A in upper-right block (rows 0..nr-1, cols nr..n-1)
920 aug = aug.insert (m, 0, nr);
921
922 octave_quit ();
923
924 // Insert A' in lower-left block (rows nr..n-1, cols 0..nr-1)
925 aug = aug.insert (m.transpose (), nr, 0);
926
927 octave_quit ();
928
929 // Use ARPACK to find largest magnitude eigenvalue
930 octave_idx_type k = 1; // number of eigenvalues
931 octave_idx_type p = -1; // auto-select basis size (will become max(2k,20))
932 octave_idx_type info = 0; // start with random initial vector
933 Matrix eig_vec;
934 ColumnVector eig_val;
935 SparseMatrix B; // empty = standard eigenproblem
936 ColumnVector permB;
937 ColumnVector resid; // empty = random initial vector
938
939 EigsRealSymmetricMatrix (aug, std::string ("LM"), k, p, info,
940 eig_vec, eig_val, B, permB, resid,
941 std::cout, tol, false, false, 0, maxiter);
942
943 octave_quit ();
944
945 if (info < 0 || eig_val.numel () == 0)
946 return 0.0; // ARPACK failed, caller should fall back
947
948 return std::abs (eig_val (0));
949}
950
951// Complex version
952double
953sparse_2norm_arpack (const SparseComplexMatrix& m, double tol, int maxiter)
954{
955 octave_idx_type nc = m.cols ();
956 octave_idx_type nr = m.rows ();
957
958 if (nc == 0 || nr == 0)
959 return 0.0;
960
961 octave_quit ();
962
963 // Build augmented sparse matrix: [0, A; A', 0] where A' is conjugate transpose
964 octave_idx_type n = nr + nc;
965 SparseComplexMatrix aug (n, n);
966
967 // Insert A in upper-right block
968 aug = aug.insert (m, 0, nr);
969
970 octave_quit ();
971
972 // Insert A' (hermitian = conjugate transpose) in lower-left block
973 aug = aug.insert (m.hermitian (), nr, 0);
974
975 octave_quit ();
976
977 // For complex matrices, the augmented matrix is Hermitian
978 octave_idx_type k = 1;
979 octave_idx_type p = -1;
980 octave_idx_type info = 0;
981 ComplexMatrix eig_vec;
982 ComplexColumnVector eig_val;
984 ColumnVector permB;
986
987 EigsComplexNonSymmetricMatrix (aug, std::string ("LM"), k, p, info,
988 eig_vec, eig_val, B, permB, resid,
989 std::cout, tol, false, false, 0, maxiter);
990
991 octave_quit ();
992
993 if (info < 0 || eig_val.numel () == 0)
994 return 0.0;
995
996 return std::abs (eig_val (0));
997}
998
999#endif // HAVE_ARPACK
1000
1001// Unified sparse 2-norm: use ARPACK when available, fall back to power iteration
1002template <typename MatrixT, typename VectorT, typename R>
1003R
1004sparse_2norm (const MatrixT& m, R tol, int maxiter, VectorT)
1005{
1006#if defined (HAVE_ARPACK)
1007 // Use ARPACK (Lanczos) - more accurate than power iteration
1008 R res = sparse_2norm_arpack (m, tol, maxiter);
1009 if (res > R (0))
1010 return res;
1011 // Fall through to power iteration if ARPACK failed
1012#endif
1013
1014 return sparse_2norm_power (m, tol, maxiter, VectorT ());
1015}
1016
1017// SVD-free version for sparse matrices
1018template <typename MatrixT, typename VectorT, typename R>
1019R
1020matrix_norm (const MatrixT& m, R p, VectorT)
1021{
1022 R res = 0;
1023 if (p == 1)
1024 res = xcolnorms (m, static_cast<R> (1)).max ();
1025 else if (math::isinf (p) && p > 1)
1026 res = xrownorms (m, static_cast<R> (1)).max ();
1027 else if (p == 2)
1028 {
1029 // Use ARPACK (Lanczos) or power iteration for 2-norm (spectral norm)
1030 const R sqrteps = std::sqrt (std::numeric_limits<R>::epsilon ());
1031 res = sparse_2norm (m, sqrteps, max_norm_iter, VectorT ());
1032 }
1033 else if (p > 1)
1034 {
1035 VectorT x;
1036 const R sqrteps = std::sqrt (std::numeric_limits<R>::epsilon ());
1037 res = higham (m, p, sqrteps, max_norm_iter, x);
1038 }
1039 else
1041
1042 return res;
1043}
1044
1045// and finally, here's what we've promised in the header file
1046
1047#define DEFINE_XNORM_FCNS(PREFIX, RTYPE) \
1048RTYPE xnorm (const PREFIX##ColumnVector& x, RTYPE p) \
1049{ \
1050 return vector_norm (x, p); \
1051} \
1052RTYPE xnorm (const PREFIX##RowVector& x, RTYPE p) \
1053{ \
1054 return vector_norm (x, p); \
1055} \
1056RTYPE xnorm (const PREFIX##Matrix& x, RTYPE p) \
1057{ \
1058 return svd_matrix_norm (x, p, PREFIX##Matrix ()); \
1059} \
1060RTYPE xfrobnorm (const PREFIX##Matrix& x) \
1061{ \
1062 return lange ('F', x); \
1063}
1064
1069
1070// Sparse Frobenius norm - use accumulator over non-zeros
1071template <typename T, typename R>
1072inline void array_norm_2 (const T *v, octave_idx_type n, R& res)
1073{
1074 norm_accumulator_2<R> acc;
1075 for (octave_idx_type i = 0; i < n; i++)
1076 acc.accum (v[i]);
1077
1078 res = acc;
1079}
1080
1081#define DEFINE_XNORM_SPARSE_FCNS(PREFIX, RTYPE) \
1082RTYPE xnorm (const Sparse##PREFIX##Matrix& x, RTYPE p) \
1083{ \
1084 return matrix_norm (x, p, PREFIX##Matrix ()); \
1085} \
1086RTYPE xfrobnorm (const Sparse##PREFIX##Matrix& x) \
1087{ \
1088 RTYPE res; \
1089 array_norm_2 (x.data (), x.nnz (), res); \
1090 return res; \
1091}
1092
1095
1096#define DEFINE_COLROW_NORM_FCNS(PREFIX, RPREFIX, RTYPE) \
1097RPREFIX##RowVector \
1098xcolnorms (const PREFIX##Matrix& m, RTYPE p) \
1099{ \
1100 return column_norms (m, p); \
1101} \
1102RPREFIX##ColumnVector \
1103xrownorms (const PREFIX##Matrix& m, RTYPE p) \
1104{ \
1105 return row_norms (m, p); \
1106} \
1107
1110DEFINE_COLROW_NORM_FCNS(Float, Float, float)
1112
1114DEFINE_COLROW_NORM_FCNS(SparseComplex, , double)
1115
1116OCTAVE_END_NAMESPACE(octave)
#define Inf
Definition Faddeeva.cc:257
N Dimensional Array with copy-on-write semantics.
Definition Array-base.h:130
T & xelem(octave_idx_type n)
Size of the specified dimension.
Definition Array-base.h:547
octave_idx_type rows() const
Definition Array-base.h:485
octave_idx_type columns() const
Definition Array-base.h:497
octave_idx_type cols() const
Definition Array-base.h:495
const T * data() const
Size of the specified dimension.
Definition Array-base.h:687
octave_idx_type numel() const
Number of elements in the array.
Definition Array-base.h:440
double max() const
Template for N-dimensional array classes with like-type math operators.
Definition MArray.h:61
double max() const
SparseComplexMatrix hermitian() const
Definition CSparse.cc:853
SparseComplexMatrix & insert(const SparseComplexMatrix &a, octave_idx_type r, octave_idx_type c)
Definition CSparse.cc:803
SparseMatrix & insert(const SparseMatrix &a, octave_idx_type r, octave_idx_type c)
Definition dSparse.cc:168
SparseMatrix transpose() const
Definition dSparse.h:131
octave_idx_type cols() const
Definition Sparse.h:351
octave_idx_type * cidx()
Definition Sparse.h:595
octave_idx_type columns() const
Definition Sparse.h:352
T * data()
Definition Sparse.h:573
octave_idx_type * ridx()
Definition Sparse.h:582
octave_idx_type nnz() const
Actual number of nonzero terms.
Definition Sparse.h:338
octave_idx_type rows() const
Definition Sparse.h:350
Vector representing the dimensions (size) of an Array.
Definition dim-vector.h:92
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
octave_idx_type EigsComplexNonSymmetricMatrix(const M &m, const std::string typ, octave_idx_type k_arg, octave_idx_type p_arg, octave_idx_type &info, ComplexMatrix &eig_vec, ComplexColumnVector &eig_val, const M &_b, ColumnVector &permB, ComplexColumnVector &cresid, std::ostream &os, double tol, bool rvec, bool cholB, int disp, int maxit)
octave_idx_type EigsRealSymmetricMatrix(const M &m, const std::string typ, octave_idx_type k_arg, octave_idx_type p_arg, octave_idx_type &info, Matrix &eig_vec, ColumnVector &eig_val, const M &_b, ColumnVector &permB, ColumnVector &resid, std::ostream &os, double tol, bool rvec, bool cholB, int disp, int maxit)
Definition eigs-base.cc:791
#define F77_CONST_CMPLX_ARG(x)
Definition f77-fcn.h:313
float F77_REAL
Definition f77-fcn.h:303
double F77_DBLE
Definition f77-fcn.h:302
octave_f77_int_type F77_INT
Definition f77-fcn.h:306
#define F77_CONST_DBLE_CMPLX_ARG(x)
Definition f77-fcn.h:319
function gamma(x)
Definition gamma.f:3
F77_RET_T const F77_INT F77_CMPLX const F77_INT F77_CMPLX * B
OCTAVE_NORETURN liboctave_error_handler current_liboctave_error_handler
Definition lo-error.c:41
std::complex< double > Complex
Definition oct-cmplx.h:33
std::complex< float > FloatComplex
Definition oct-cmplx.h:34
#define OCTAVE_LOCAL_BUFFER(T, buf, size)
Definition oct-locbuf.h:44
VectorT dual_p(const VectorT &x, R p, R q)
Definition oct-norm.cc:718
void array_norm_2(const T *v, octave_idx_type n, R &res)
Definition oct-norm.cc:1072
std::conditional_t< std::is_same_v< R, float >, double, R > accum_type
Definition oct-norm.cc:225
R matrix_norm(const MatrixT &m, R p, VectorT)
Definition oct-norm.cc:1020
#define DEFINE_COLROW_NORM_FCNS(PREFIX, RPREFIX, RTYPE)
Definition oct-norm.cc:1096
RowVector xcolnorms(const Matrix &m, double p)
Definition oct-norm.cc:1108
constexpr const char * p_less1_gripe
Definition oct-norm.cc:780
constexpr T safe_conj(T x)
Definition oct-norm.cc:816
R sparse_2norm_power(const MatrixT &m, R tol, int maxiter, VectorT)
Definition oct-norm.cc:842
double sparse_2norm_arpack(const SparseMatrix &m, double tol, int maxiter)
Definition oct-norm.cc:904
#define DEFINE_COLROW_DISPATCHER(FCN_NAME, ARG_TYPE, RES_TYPE)
Definition oct-norm.cc:598
double lange(char norm_type, const Matrix &m)
Definition oct-norm.cc:149
R vector_norm_2_blas(const MArray< T > &v)
Definition oct-norm.cc:540
void column_norms(const MArray< T > &m, MArray< R > &res, ACC acc)
Definition oct-norm.cc:476
T elem_dual_p(T x, R p)
Definition oct-norm.cc:707
auto vector_dot(const VectorT &x, const VectorT &y)
Definition oct-norm.cc:827
ColumnVector xrownorms(const Matrix &m, double p)
Definition oct-norm.cc:1108
#define DEFINE_XNORM_SPARSE_FCNS(PREFIX, RTYPE)
Definition oct-norm.cc:1081
void vector_norm(const Array< T > &v, R &res, ACC acc)
Definition oct-norm.cc:453
void row_norms(const MArray< T > &m, MArray< R > &res, ACC acc)
Definition oct-norm.cc:491
double blas_nrm2(octave_idx_type n, const double *x, octave_idx_type incx)
Definition oct-norm.cc:80
R svd_matrix_norm(const MatrixT &m, R p, VectorT)
Definition oct-norm.cc:790
R sparse_2norm(const MatrixT &m, R tol, int maxiter, VectorT)
Definition oct-norm.cc:1004
R higham(const MatrixT &m, R p, R tol, int maxiter, VectorT &x)
Definition oct-norm.cc:729
constexpr int max_norm_iter
Definition oct-norm.cc:785
#define DEFINE_XNORM_FCNS(PREFIX, RTYPE)
Definition oct-norm.cc:1047
R lange_inf_fixup(R result, const T *data, octave_idx_type numel)
Definition oct-norm.cc:127
T::size_type numel(const T &str)
Definition oct-string.cc:81
F77_RET_T const F77_DBLE * x
subroutine xclange(norm, m, n, a, lda, work, retval)
Definition xclange.f:2
subroutine xdlange(norm, m, n, a, lda, work, retval)
Definition xdlange.f:2
subroutine xdnrm2(n, x, incx, retval)
Definition xdnrm2.f:2
subroutine xdznrm2(n, x, incx, retval)
Definition xdznrm2.f:2
F77_RET_T F77_FUNC(xerbla, XERBLA)(F77_CONST_CHAR_ARG_DEF(s_arg
subroutine xscnrm2(n, x, incx, retval)
Definition xscnrm2.f:2
subroutine xslange(norm, m, n, a, lda, work, retval)
Definition xslange.f:2
subroutine xsnrm2(n, x, incx, retval)
Definition xsnrm2.f:2
subroutine xzlange(norm, m, n, a, lda, work, retval)
Definition xzlange.f:2