GNU Octave 11.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
 
Loading...
Searching...
No Matches
sparse-xpow.cc
Go to the documentation of this file.
1////////////////////////////////////////////////////////////////////////
2//
3// Copyright (C) 1998-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 <limits>
31
32#include "Array-util.h"
33#include "oct-cmplx.h"
34#include "quit.h"
35
36#include "error.h"
37#include "ovl.h"
38#include "utils.h"
39
40#include "dSparse.h"
41#include "CSparse.h"
42#include "ov-re-sparse.h"
43#include "ov-cx-sparse.h"
44#include "sparse-xpow.h"
45
47
48static inline bool
49xisint (double x)
50{
51 // FIXME: If we return *only* octave::math::is_integer (x)
52 // and delete the lines with numeric_limits<int>,
53 // then that works on MacOS but not on Linux.
54 // It fails a test in sparse.tst that requires this calculation:
55 // Complex (realmin, realmin) .^ realmax.
56 // On MacOS it correctly gives zero, matching Matlab.
57 // On Linux it gives Inf + Nani.
58 // If we can make both platforms work without the extra check with
59 // numeric_limits<int>, then we should do so.
60 return (octave::math::is_integer (x)
61 && ((x >= 0 && x < std::numeric_limits<int>::max ())
62 || (x <= 0 && x > std::numeric_limits<int>::min ())));
63}
64
65// Safer pow functions. Only two make sense for sparse matrices, the
66// others should all promote to full matrices.
67
68template<typename T>
69static octave_value
70xpow_private (const T& a, double b)
71{
72 octave_value retval;
73
74 octave_idx_type nr = a.rows ();
75 octave_idx_type nc = a.cols ();
76
77 if (nr == 0 || nc == 0)
78 return T ();
79
80 // If we are here, A is not empty ==> A needs to be square.
81 if (nr != nc)
82 error ("for A^b, A must be a square matrix. Use .^ for elementwise power.");
83
84 if (! xisint (b))
85 error ("use full(a) ^ full(b)");
86
87 int btmp = static_cast<int> (b);
88 if (btmp == 0)
89 {
90 // Return sparse identity matrix, so it's always type SparseMatrix
91 // and does not have to be templated by typename T.
92 SparseMatrix tmp = SparseMatrix (nr, nr, nr);
93 for (octave_idx_type i = 0; i < nr; i++)
94 {
95 tmp.data (i) = 1.0;
96 tmp.ridx (i) = i;
97 }
98 for (octave_idx_type i = 0; i < nr + 1; i++)
99 tmp.cidx (i) = i;
100
101 retval = tmp;
102 }
103 else
104 {
105 T atmp;
106 if (btmp < 0)
107 {
108 btmp = -btmp;
109
110 octave_idx_type info;
111 double rcond = 0.0;
112 MatrixType mattyp (a);
113
114 // FIXME: This causes an error if the input sparse matrix is all-zeros.
115 // That behavior is inconsistent with A ^ b when A is a full all-zeros
116 // matrix, which just returns Inf of the same size with a warning.
117 atmp = a.inverse (mattyp, info, rcond, 1);
118
119 if (info == -1)
120 warning ("inverse: matrix singular to machine precision, rcond = %g", rcond);
121 }
122 else
123 atmp = a;
124
125 if (atmp.nnz () == 0) // Fast return for all-zeros matrix
126 return atmp;
127
128 T result (atmp);
129
130 btmp--;
131
132 // There are two approaches to the actual exponentiation.
133 // Exponentiation by squaring uses only a logarithmic number
134 // of multiplications but the matrices it multiplies tend to be dense
135 // towards the end.
136 // Linear multiplication uses a linear number of multiplications
137 // but one of the matrices it uses will be as sparse as the original
138 // matrix.
139 //
140 // The time to multiply fixed-size matrices is strongly affected by their
141 // sparsity. Denser matrices take much longer to multiply together.
142 // See this URL for a worked-through example:
143 // https://octave.discourse.group/t/3216/4
144 //
145 // The tradeoff is between many fast multiplications or a few slow ones.
146 //
147 // Large exponents favor the squaring technique, and sparse matrices
148 // favor linear multiplication.
149 //
150 // We calculate a threshold based on the sparsity of the input
151 // and use squaring for exponents larger than that.
152 //
153 // FIXME: Improve this threshold calculation.
154
155 uint64_t sparsity = atmp.numel () / atmp.nnz (); // reciprocal of density
156 int threshold = (sparsity >= 1000) ? 40
157 : (sparsity >= 100) ? 20
158 : 3;
159
160 if (btmp > threshold) // use squaring technique
161 {
162 while (btmp > 0)
163 {
164 if (btmp & 1)
165 result = result * atmp;
166
167 btmp >>= 1;
168
169 if (btmp > 0)
170 atmp = atmp * atmp;
171 }
172 }
173 else // use linear multiplication
174 {
175 for (int i = 0; i < btmp; i++)
176 result = result * atmp;
177 }
178
179 retval = result;
180 }
181
182 return retval;
183}
184
186xpow (const SparseMatrix& a, double b)
187{
188 return xpow_private<SparseMatrix> (a, b);
189}
190
192xpow (const SparseComplexMatrix& a, double b)
193{
194 return xpow_private<SparseComplexMatrix> (a, b);
195}
196
197// Safer pow functions that work elementwise for matrices.
198//
199// op2 \ op1: s m cs cm
200// +-- +---+---+----+----+
201// scalar | | * | 3 | * | 9 |
202// +---+---+----+----+
203// matrix | 1 | 4 | 7 | 10 |
204// +---+---+----+----+
205// complex_scalar | * | 5 | * | 11 |
206// +---+---+----+----+
207// complex_matrix | 2 | 6 | 8 | 12 |
208// +---+---+----+----+
209//
210// * -> not needed.
211
212// FIXME: these functions need to be fixed so that things
213// like
214//
215// a = -1; b = [ 0, 0.5, 1 ]; r = a .^ b
216//
217// and
218//
219// a = -1; b = [ 0, 0.5, 1 ]; for i = 1:3, r(i) = a .^ b(i), end
220//
221// produce identical results. Also, it would be nice if -1^0.5
222// produced a pure imaginary result instead of a complex number with a
223// small real part. But perhaps that's really a problem with the math
224// library...
225
226// Handle special case of scalar-sparse-matrix .^ sparse-matrix.
227// Forwarding to the scalar elem_xpow function and then converting the
228// result back to a sparse matrix is a bit wasteful but it does not
229// seem worth the effort to optimize -- how often does this case come up
230// in practice?
231
232template <typename S, typename SM>
233inline octave_value
234scalar_xpow (const S& a, const SM& b)
235{
236 octave_value val = elem_xpow (a, b);
237
238 if (val.iscomplex ())
240 else
241 return SparseMatrix (val.matrix_value ());
242}
243
244/*
245%!assert (sparse (2) .^ [3, 4], sparse ([8, 16]))
246%!assert <47775> (sparse (2i) .^ [3, 4], sparse ([-0-8i, 16]))
247
248%!test <*63080>
249%! Z = sparse ([]);
250%! A = sparse (zeros (0, 2));
251%! B = sparse (zeros (2, 0));
252%! assert (Z ^ 1, Z);
253%! assert (Z ^ 0, Z);
254%! assert (Z ^ -1, Z);
255%! assert (A ^ 1, Z);
256%! assert (A ^ 0, Z);
257%! assert (A ^ -1, Z);
258%! assert (B ^ 1, Z);
259%! assert (B ^ 0, Z);
260%! assert (B ^ -1, Z);
261
262%!test <*63080>
263%! A = sparse (zeros (2, 2));
264%! assert (A ^ 1, A);
265%! assert (A ^ 0, sparse (eye (2, 2)));
266
267%!test <63080>
268%! A = sparse (zeros (2, 2));
269%! assert (A ^ -1, sparse (inf (2, 2)));
270
271*/
272
273// -*- 1 -*-
275elem_xpow (double a, const SparseMatrix& b)
276{
277 octave_value retval;
278
279 octave_idx_type nr = b.rows ();
280 octave_idx_type nc = b.cols ();
281
282 double d1, d2;
283
284 if (a < 0.0 && ! b.all_integers (d1, d2))
285 {
286 Complex atmp (a);
287 ComplexMatrix result (nr, nc);
288
289 for (octave_idx_type j = 0; j < nc; j++)
290 {
291 for (octave_idx_type i = 0; i < nr; i++)
292 {
293 octave_quit ();
294 result(i, j) = std::pow (atmp, b(i, j));
295 }
296 }
297
298 retval = result;
299 }
300 else
301 {
302 Matrix result (nr, nc);
303
304 for (octave_idx_type j = 0; j < nc; j++)
305 {
306 for (octave_idx_type i = 0; i < nr; i++)
307 {
308 octave_quit ();
309 result(i, j) = std::pow (a, b(i, j));
310 }
311 }
312
313 retval = result;
314 }
315
316 return retval;
317}
318
319// -*- 2 -*-
321elem_xpow (double a, const SparseComplexMatrix& b)
322{
323 octave_idx_type nr = b.rows ();
324 octave_idx_type nc = b.cols ();
325
326 Complex atmp (a);
327 ComplexMatrix result (nr, nc);
328
329 for (octave_idx_type j = 0; j < nc; j++)
330 {
331 for (octave_idx_type i = 0; i < nr; i++)
332 {
333 octave_quit ();
334 result(i, j) = std::pow (atmp, b(i, j));
335 }
336 }
337
338 return result;
339}
340
341// -*- 3 -*-
343elem_xpow (const SparseMatrix& a, double b)
344{
345 // FIXME: What should a .^ 0 give? Matlab gives a
346 // sparse matrix with same structure as a, which is strictly
347 // incorrect. Keep compatibility.
348
349 octave_value retval;
350
351 octave_idx_type nz = a.nnz ();
352
353 if (b <= 0.0)
354 {
355 octave_idx_type nr = a.rows ();
356 octave_idx_type nc = a.cols ();
357
358 if (! xisint (b) && a.any_element_is_negative ())
359 {
360 ComplexMatrix result (nr, nc, Complex (std::pow (0.0, b)));
361
362 for (octave_idx_type j = 0; j < nc; j++)
363 for (octave_idx_type i = a.cidx (j); i < a.cidx (j+1); i++)
364 {
365 octave_quit ();
366 result(a.ridx (i), j) = std::pow (a.data (i), b);
367 }
368
369 retval = octave_value (result);
370 }
371 else
372 {
373 Matrix result (nr, nc, (std::pow (0.0, b)));
374
375 for (octave_idx_type j = 0; j < nc; j++)
376 for (octave_idx_type i = a.cidx (j); i < a.cidx (j+1); i++)
377 {
378 octave_quit ();
379 result(a.ridx (i), j) = std::pow (a.data (i), b);
380 }
381
382 retval = octave_value (result);
383 }
384 }
385 else if (! xisint (b) && a.any_element_is_negative ())
386 {
387 SparseComplexMatrix result (a);
388
389 for (octave_idx_type i = 0; i < nz; i++)
390 {
391 octave_quit ();
392 result.data (i) = std::pow (a.data (i), b);
393 }
394
395 result.maybe_compress (true);
396
397 retval = result;
398 }
399 else
400 {
401 SparseMatrix result (a);
402
403 for (octave_idx_type i = 0; i < nz; i++)
404 {
405 octave_quit ();
406 result.data (i) = std::pow (a.data (i), b);
407 }
408
409 result.maybe_compress (true);
410
411 retval = result;
412 }
413
414 return retval;
415}
416
417// -*- 4 -*-
420{
421 octave_value retval;
422
423 octave_idx_type nr = a.rows ();
424 octave_idx_type nc = a.cols ();
425
426 octave_idx_type b_nr = b.rows ();
427 octave_idx_type b_nc = b.cols ();
428
429 if (a.numel () == 1 && b.numel () > 1)
430 return scalar_xpow (a(0), b);
431
432 if (nr != b_nr || nc != b_nc)
433 octave::err_nonconformant ("operator .^", nr, nc, b_nr, b_nc);
434
435 int convert_to_complex = 0;
436 for (octave_idx_type j = 0; j < nc; j++)
437 for (octave_idx_type i = a.cidx (j); i < a.cidx (j+1); i++)
438 {
439 if (a.data(i) < 0.0)
440 {
441 double btmp = b (a.ridx (i), j);
442 if (! xisint (btmp))
443 {
444 convert_to_complex = 1;
445 goto done;
446 }
447 }
448 }
449
450done:
451
452 // This is a dumb operator for sparse matrices anyway, and there is
453 // no sensible way to handle the 0.^0 versus the 0.^x cases. Therefore
454 // allocate a full matrix filled for the 0.^0 case and shrink it later
455 // as needed.
456
457 if (convert_to_complex)
458 {
459 SparseComplexMatrix complex_result (nr, nc, Complex (1.0, 0.0));
460
461 for (octave_idx_type j = 0; j < nc; j++)
462 {
463 for (octave_idx_type i = a.cidx (j); i < a.cidx (j+1); i++)
464 {
465 octave_quit ();
466 complex_result.xelem (a.ridx (i), j)
467 = std::pow (Complex (a.data (i)), Complex (b(a.ridx (i), j)));
468 }
469 }
470 complex_result.maybe_compress (true);
471 retval = complex_result;
472 }
473 else
474 {
475 SparseMatrix result (nr, nc, 1.0);
476
477 for (octave_idx_type j = 0; j < nc; j++)
478 {
479 for (octave_idx_type i = a.cidx (j); i < a.cidx (j+1); i++)
480 {
481 octave_quit ();
482 result.xelem (a.ridx (i), j) = std::pow (a.data (i),
483 b(a.ridx (i), j));
484 }
485 }
486 result.maybe_compress (true);
487 retval = result;
488 }
489
490 return retval;
491}
492
493// -*- 5 -*-
495elem_xpow (const SparseMatrix& a, const Complex& b)
496{
497 octave_value retval;
498
499 if (b == 0.0)
500 // Can this case ever happen, due to automatic retyping with maybe_mutate?
501 retval = octave_value (NDArray (a.dims (), 1));
502 else
503 {
504 octave_idx_type nz = a.nnz ();
505 SparseComplexMatrix result (a);
506
507 for (octave_idx_type i = 0; i < nz; i++)
508 {
509 octave_quit ();
510 result.data (i) = std::pow (Complex (a.data (i)), b);
511 }
512
513 result.maybe_compress (true);
514
515 retval = result;
516 }
517
518 return retval;
519}
520
521// -*- 6 -*-
524{
525 octave_idx_type nr = a.rows ();
526 octave_idx_type nc = a.cols ();
527
528 octave_idx_type b_nr = b.rows ();
529 octave_idx_type b_nc = b.cols ();
530
531 if (a.numel () == 1 && b.numel () > 1)
532 return scalar_xpow (a(0), b);
533
534 if (nr != b_nr || nc != b_nc)
535 octave::err_nonconformant ("operator .^", nr, nc, b_nr, b_nc);
536
537 SparseComplexMatrix result (nr, nc, Complex (1.0, 0.0));
538 for (octave_idx_type j = 0; j < nc; j++)
539 {
540 for (octave_idx_type i = a.cidx (j); i < a.cidx (j+1); i++)
541 {
542 octave_quit ();
543 result.xelem (a.ridx(i), j) = std::pow (a.data (i), b(a.ridx (i), j));
544 }
545 }
546
547 result.maybe_compress (true);
548
549 return result;
550}
551
552// -*- 7 -*-
554elem_xpow (const Complex& a, const SparseMatrix& b)
555{
556 octave_idx_type nr = b.rows ();
557 octave_idx_type nc = b.cols ();
558
559 ComplexMatrix result (nr, nc);
560
561 for (octave_idx_type j = 0; j < nc; j++)
562 {
563 for (octave_idx_type i = 0; i < nr; i++)
564 {
565 octave_quit ();
566 double btmp = b (i, j);
567 if (xisint (btmp))
568 result (i, j) = std::pow (a, static_cast<int> (btmp));
569 else
570 result (i, j) = std::pow (a, btmp);
571 }
572 }
573
574 return result;
575}
576
577// -*- 8 -*-
580{
581 octave_idx_type nr = b.rows ();
582 octave_idx_type nc = b.cols ();
583
584 ComplexMatrix result (nr, nc);
585 for (octave_idx_type j = 0; j < nc; j++)
586 for (octave_idx_type i = 0; i < nr; i++)
587 {
588 octave_quit ();
589 result (i, j) = std::pow (a, b (i, j));
590 }
591
592 return result;
593}
594
595// -*- 9 -*-
597elem_xpow (const SparseComplexMatrix& a, double b)
598{
599 octave_value retval;
600
601 if (b <= 0)
602 {
603 octave_idx_type nr = a.rows ();
604 octave_idx_type nc = a.cols ();
605
606 ComplexMatrix result (nr, nc, Complex (std::pow (0.0, b)));
607
608 if (xisint (b))
609 {
610 for (octave_idx_type j = 0; j < nc; j++)
611 for (octave_idx_type i = a.cidx (j); i < a.cidx (j+1); i++)
612 {
613 octave_quit ();
614 result (a.ridx (i), j)
615 = std::pow (a.data (i), static_cast<int> (b));
616 }
617 }
618 else
619 {
620 for (octave_idx_type j = 0; j < nc; j++)
621 for (octave_idx_type i = a.cidx (j); i < a.cidx (j+1); i++)
622 {
623 octave_quit ();
624 result (a.ridx (i), j) = std::pow (a.data (i), b);
625 }
626 }
627
628 retval = result;
629 }
630 else
631 {
632 octave_idx_type nz = a.nnz ();
633
634 SparseComplexMatrix result (a);
635
636 if (xisint (b))
637 {
638 for (octave_idx_type i = 0; i < nz; i++)
639 {
640 octave_quit ();
641 result.data (i) = std::pow (a.data (i), static_cast<int> (b));
642 }
643 }
644 else
645 {
646 for (octave_idx_type i = 0; i < nz; i++)
647 {
648 octave_quit ();
649 result.data (i) = std::pow (a.data (i), b);
650 }
651 }
652
653 result.maybe_compress (true);
654
655 retval = result;
656 }
657
658 return retval;
659}
660
661// -*- 10 -*-
664{
665 octave_idx_type nr = a.rows ();
666 octave_idx_type nc = a.cols ();
667
668 octave_idx_type b_nr = b.rows ();
669 octave_idx_type b_nc = b.cols ();
670
671 if (a.numel () == 1 && b.numel () > 1)
672 return scalar_xpow (a(0), b);
673
674 if (nr != b_nr || nc != b_nc)
675 octave::err_nonconformant ("operator .^", nr, nc, b_nr, b_nc);
676
677 SparseComplexMatrix result (nr, nc, Complex (1.0, 0.0));
678 for (octave_idx_type j = 0; j < nc; j++)
679 {
680 for (octave_idx_type i = a.cidx (j); i < a.cidx (j+1); i++)
681 {
682 octave_quit ();
683 double btmp = b(a.ridx (i), j);
684
685 if (xisint (btmp))
686 result.xelem (a.ridx (i), j) = std::pow (a.data (i),
687 static_cast<int> (btmp));
688 else
689 result.xelem (a.ridx (i), j) = std::pow (a.data (i), btmp);
690 }
691 }
692
693 result.maybe_compress (true);
694
695 return result;
696}
697
698// -*- 11 -*-
701{
702 octave_value retval;
703
704 if (b == 0.0)
705 // Can this case ever happen, due to automatic retyping with maybe_mutate?
706 retval = octave_value (NDArray (a.dims (), 1));
707 else
708 {
709
710 octave_idx_type nz = a.nnz ();
711
712 SparseComplexMatrix result (a);
713
714 for (octave_idx_type i = 0; i < nz; i++)
715 {
716 octave_quit ();
717 result.data (i) = std::pow (a.data (i), b);
718 }
719
720 result.maybe_compress (true);
721
722 retval = result;
723 }
724
725 return retval;
726}
727
728// -*- 12 -*-
731{
732 octave_idx_type nr = a.rows ();
733 octave_idx_type nc = a.cols ();
734
735 octave_idx_type b_nr = b.rows ();
736 octave_idx_type b_nc = b.cols ();
737
738 if (a.numel () == 1 && b.numel () > 1)
739 return scalar_xpow (a(0), b);
740
741 if (nr != b_nr || nc != b_nc)
742 octave::err_nonconformant ("operator .^", nr, nc, b_nr, b_nc);
743
744 SparseComplexMatrix result (nr, nc, Complex (1.0, 0.0));
745 for (octave_idx_type j = 0; j < nc; j++)
746 {
747 for (octave_idx_type i = a.cidx (j); i < a.cidx (j+1); i++)
748 {
749 octave_quit ();
750 result.xelem (a.ridx (i), j) = std::pow (a.data (i),
751 b(a.ridx (i), j));
752 }
753 }
754 result.maybe_compress (true);
755
756 return result;
757}
758
759
760OCTAVE_END_NAMESPACE(octave)
bool all_integers(double &max_val, double &min_val) const
Definition dSparse.cc:7476
bool any_element_is_negative(bool=false) const
Definition dSparse.cc:7378
octave_idx_type cols() const
Definition Sparse.h:351
octave_idx_type * cidx()
Definition Sparse.h:595
T * data()
Definition Sparse.h:573
octave_idx_type * ridx()
Definition Sparse.h:582
octave_idx_type numel() const
Definition Sparse.h:342
Sparse< T, Alloc > maybe_compress(bool remove_zeros=false)
Definition Sparse.h:530
octave_idx_type nnz() const
Actual number of nonzero terms.
Definition Sparse.h:338
octave_idx_type rows() const
Definition Sparse.h:350
T & xelem(octave_idx_type n)
Definition Sparse.h:394
dim_vector dims() const
Definition Sparse.h:370
ComplexMatrix complex_matrix_value(bool frc_str_conv=false) const
Definition ov.h:875
bool iscomplex() const
Definition ov.h:739
Matrix matrix_value(bool frc_str_conv=false) const
Definition ov.h:857
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
void warning(const char *fmt,...)
Definition error.cc:1083
void error(const char *fmt,...)
Definition error.cc:1008
std::complex< double > Complex
Definition oct-cmplx.h:33
F77_RET_T const F77_DBLE * x
octave_value xpow(const SparseMatrix &a, double b)
octave_value elem_xpow(double a, const SparseMatrix &b)
octave_value scalar_xpow(const S &a, const SM &b)