GNU Octave  9.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
pow2.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2022-2024 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 "lo-array-errwarn.h"
33 
34 #include "defun.h"
35 #include "error.h"
36 #include "errwarn.h"
37 
38 // FIXME: According to cppreference.com the implementation of `ldexp (f, e)`
39 // might be less efficient that the corresponding `f * exp2 (e)`. Consider
40 // replacing our implementation with the latter.
41 
42 template <typename T>
43 void
44 map_2_xldexp (Array<T>& y, const Array<T>& f, const Array<T>& e)
45 {
46  if (f.numel () == e.numel () || e.numel () == 1)
47  y = Array<T> (f.dims ());
48  else if (f.numel () == 1)
49  y = Array<T> (e.dims ());
50  else
51  octave::err_nonconformant ("pow2", f.dims (), e.dims ());
52 
53  octave_idx_type f_inc = (f.numel () == 1) ? 0 : 1;
54  octave_idx_type e_inc = (e.numel () == 1) ? 0 : 1;
55 
56  for (octave_idx_type i = 0; i < y.numel (); i++)
57  y.xelem (i) = std::ldexp (f.xelem (i * f_inc),
58  static_cast<int> (e.xelem (i * e_inc)));
59 }
60 
61 void
63  const SparseMatrix& e)
64 {
65  if (e.numel () == 1)
66  {
67  int ee = static_cast<int> (e.data (0));
68  for (octave_idx_type i = 0; i < y.nnz (); i++)
69  y.data (i) = std::ldexp (f.data (i), ee);
70  }
71  else if (f.numel () == e.numel ())
72  {
73  octave_idx_type col = 1;
74  for (octave_idx_type i = 0; i < y.nnz (); i++)
75  {
76  // Determine current column.
77  while (i >= f.cidx (col))
78  col++;
79  int ee = static_cast<int> (e.xelem (f.ridx (i), col - 1));
80  y.data (i) = std::ldexp (f.data (i), ee);
81  }
82  }
83  else
84  octave::err_nonconformant ("pow2", f.dims (), e.dims ());
85 }
86 
88 
89 DEFUN (pow2, args, ,
90  doc: /* -*- texinfo -*-
91 @deftypefn {} {@var{y} =} pow2 (@var{x})
92 @deftypefnx {} {@var{y} =} pow2 (@var{f}, @var{e})
93 With one input argument, compute
94 @tex
95 $y = 2^x$
96 @end tex
97 @ifnottex
98 y = 2 .^ x
99 @end ifnottex
100 for each element of @var{x}.
101 
102 With two input arguments, return
103 @tex
104 $y = f \cdot 2^e$,
105 @end tex
106 @ifnottex
107 y = f .* (2 .^ e).
108 @end ifnottex
109 where for complex inputs only the real part of both inputs is regarded
110 and from @var{e} only the real integer part. This calling form corresponds
111 to C/C++ standard function @code{ldexp()}.
112 @seealso{log2, nextpow2, power}
113 @end deftypefn */)
114 {
115  if (args.length () < 1 || args.length () > 2)
116  print_usage ();
117 
118  if (! args(0).isfloat ())
119  err_wrong_type_arg ("pow2", args(0));
120 
121  // Call exp2(f) where possible for numerical more accurate results.
122  if (args.length () == 1)
123  {
124  if (args(0).iscomplex ())
125  {
126  // The C++ standard does not define exp2 for complex arguments.
127  // Therefore call `2.^x`.
129  2, args(0));
130 
131  // Preserve sparse datatype, but even for sparse input fill-up
132  // is unavoidable `2^0 == 1` thus cast only.
133  if (args(0).issparse ())
134  retval = octave_value (retval.sparse_complex_matrix_value ());
135 
136  return ovl (retval);
137  }
138  else if (args(0).is_single_type ())
139  {
140  FloatNDArray x = args(0).float_array_value ();
141  FloatNDArray y (x.dims ());
142  for (octave_idx_type i = 0; i < y.numel (); i++)
143  y.xelem (i) = std::exp2 (x.xelem (i));
144  return ovl (y);
145  }
146  else
147  {
148  NDArray x = args(0).array_value ();
149  NDArray y (x.dims ());
150  for (octave_idx_type i = 0; i < y.numel (); i++)
151  y.xelem (i) = std::exp2 (x.xelem (i));
152 
153  // Preserve sparse datatype, but even for sparse input fill-up
154  // is unavoidable `2^0 == 1` thus cast only.
155  if (args(0).issparse ())
156  return ovl (SparseMatrix (y));
157  else
158  return ovl (y);
159  }
160  }
161 
162  // For Matlab compatibility, the two argument call `y = pow2 (f, e)`
163  // corresponds to std::ldexp() (see bug #61968). The resulting y is
164  // computed quickly by adding the integer part of e to the floating-point
165  // exponent of f.
166 
167  if (! args(1).isfloat ())
168  err_wrong_type_arg ("pow2", args(1));
169 
170  if (args(0).iscomplex () || args(1).iscomplex ())
171  warning_with_id ("Octave:pow2:imaginary-ignored",
172  "pow2: imaginary part is ignored");
173 
174  // Note: Matlab R2021a errors on `pow2 (sparse (f), single (e))`,
175  // but sparsity in f determines output and can significantly
176  // reduce computation, e.g. `N=1e5; pow2(speye(N),sparse(N,N))`.
177  if (args(0).issparse ())
178  {
179  SparseMatrix f = args(0).sparse_matrix_value ();
180 
181  // Special case: return a sparse zero matrix in size of e.
182  if ((f.numel () == 1) && (f.nnz () == 0))
183  return ovl (SparseMatrix (args(1).rows (), args(1).columns ()));
184 
185  // Only do sparse computation, if it pays off. For scalar f fill-up
186  // is unavoidable even for sparse e because `f * 2^0 == f`. Use dense
187  // code below in this case.
188  if (f.numel () > 1)
189  {
190  SparseMatrix e = args(1).sparse_matrix_value ();
192  map_2_xldexp_sparse (y, f, e);
193  return ovl (y);
194  }
195  }
196 
197  if (args(0).is_single_type () || args(1).is_single_type ())
198  {
199  FloatNDArray f = args(0).float_array_value ();
200  FloatNDArray e = args(1).float_array_value ();
201  FloatNDArray y;
202  map_2_xldexp (y, f, e);
203  return ovl (y);
204  }
205  else
206  {
207  NDArray f = args(0).array_value ();
208  NDArray e = args(1).array_value ();
209  NDArray y;
210  map_2_xldexp (y, f, e);
211 
212  // Preserve sparse datatype.
213  // Cases for efficient use of sparsity were treated above already.
214  if (args(0).issparse ())
215  return ovl (SparseMatrix (y));
216  else
217  return ovl (y);
218  }
219 }
220 
221 /*
222 ## Call `y = pow2 (x)`
223 
224 %!test
225 %! fcns = {@double, @single, @complex};
226 %! x = [3, 0, -3];
227 %! v = [8, 1, .125];
228 %! for i = 1:numel (fcns)
229 %! fcn = fcns{i};
230 %! assert (pow2 (fcn (x)), fcn (v), sqrt (eps));
231 %! endfor
232 
233 %!test
234 %! fcns = {@double, @single, @complex, @sparse};
235 %! x = [3, 1, -3];
236 %! v = [8, 2, .125];
237 %! for i = 1:numel (fcns)
238 %! fcn = fcns{i};
239 %! assert (pow2 (fcn (x)), fcn (v), sqrt (eps));
240 %! endfor
241 
242 %!test
243 %! fcns = {@double, @single, @complex, @sparse};
244 %! x = [1, 1+1i, 1i];
245 %! for i = 1:numel (fcns)
246 %! fcn = fcns{i};
247 %! assert (pow2 (fcn (x)), fcn (2) .^ fcn (x), sqrt (eps));
248 %! endfor
249 
250 ## Call `y = pow2 (f, e)`
251 
252 %!test
253 %! fcns = {@double, @single, @complex, @sparse};
254 %! f = [2 2];
255 %! e = [2 2];
256 %! z = [8 8];
257 %! warning ("off", "Octave:pow2:imaginary-ignored", "local");
258 %! for i = 1:numel (fcns)
259 %! fcn = fcns{i};
260 %! assert (pow2 (fcn (f), fcn (e)), real (fcn (z)));
261 %! endfor
262 
263 ## Only integer part is taken into account.
264 %!test
265 %! f = 2;
266 %! e = [2, 2.1, 2.2, 2.4, 2.5, 2.8];
267 %! z = 8 .* ones (1, length (e));
268 %! assert (pow2 (f, e), z);
269 
270 ## Only real part is taken into account.
271 %!test
272 %! f = [1+1i, 1];
273 %! e = 2;
274 %! z = [4, 4];
275 %! warning ("off", "Octave:pow2:imaginary-ignored", "local");
276 %! assert (pow2 (f, e), z);
277 
278 %!test
279 %! f = 1;
280 %! e = [1+1i, 1];
281 %! z = [2, 2];
282 %! warning ("off", "Octave:pow2:imaginary-ignored", "local");
283 %! assert (pow2 (f, e), z);
284 
285 %!test
286 %! f = [1/2, pi/4, -3/4, 1/2, 1-eps()/2, 1/2];
287 %! e = [1, 2, 2, -51, 1024, -1021];
288 %! z = [1, pi, -3, eps(), realmax(), realmin()];
289 %! assert (pow2 (f, e), z);
290 
291 ## Tests for sparsity.
292 %!assert (pow2 (sparse (0), ones (3)), sparse (3, 3));
293 %!assert (pow2 (sparse (1), ones (3)), 2 .* sparse (ones (3)));
294 %!assert (pow2 (sparse (1), speye (3)), sparse (ones (3) + eye (3)));
295 %!assert (pow2 (sparse (3, 3), ones (3)), sparse (3, 3));
296 %!assert (pow2 (speye (3), ones (3)), 2 .* speye (3));
297 %!assert (pow2 (speye (3), 1), 2 .* speye (3));
298 
299 %!test
300 %! f = speye (3);
301 %! e = sparse (3, 3);
302 %! e(1,1) = 1;
303 %! e(1,3) = 1;
304 %! z = f;
305 %! z(1,1) = 2;
306 %! assert (pow2 (f, e), z);
307 
308 ## Large sparse matrix (only few real elements).
309 %!test
310 %! ## FIXME: `N = 1e5` would be a better test, but `assert` fills-up somehow.
311 %! N = 1e3;
312 %! assert (pow2 (speye (N), sparse (N,N)), speye (N));
313 %! assert (pow2 (sparse (0), speye (N)), sparse(N,N));
314 
315 %!error <Invalid call> pow2 ()
316 %!error <Invalid call> pow2 (1,2,3)
317 %!error <wrong type argument> pow2 (int8 (1))
318 %!error <wrong type argument> pow2 (2, int8 (1))
319 %!warning <imaginary part is ignored> pow2 (i, 2);
320 %!warning <imaginary part is ignored> pow2 (2, i);
321 %!error <pow2: nonconformant arguments> pow2 ([1,2], [3,4,5])
322 %!error <pow2: nonconformant arguments> pow2 (sparse ([1,2]), sparse ([3,4,5]))
323 */
324 
325 OCTAVE_END_NAMESPACE(octave)
const dim_vector & dims() const
Return a const-reference so that dims ()(i) works efficiently.
Definition: Array.h:503
T & xelem(octave_idx_type n)
Size of the specified dimension.
Definition: Array.h:524
octave_idx_type numel() const
Number of elements in the array.
Definition: Array.h:414
T * data()
Definition: Sparse.h:574
octave_idx_type numel() const
Definition: Sparse.h:343
T & xelem(octave_idx_type n)
Definition: Sparse.h:395
octave_idx_type nnz() const
Actual number of nonzero terms.
Definition: Sparse.h:339
dim_vector dims() const
Definition: Sparse.h:371
@ op_el_pow
Definition: ov.h:107
SparseComplexMatrix sparse_complex_matrix_value(bool frc_str_conv=false) const
Definition: ov.h:904
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
void print_usage(void)
Definition: defun-int.h:72
#define DEFUN(name, args_name, nargout_name, doc)
Macro to define a builtin function.
Definition: defun.h:56
void warning_with_id(const char *id, const char *fmt,...)
Definition: error.cc:1078
void err_wrong_type_arg(const char *name, const char *s)
Definition: errwarn.cc:166
void err_nonconformant(const char *op, octave_idx_type op1_len, octave_idx_type op2_len)
double exp2(double x)
Definition: lo-mappers.h:98
F77_RET_T const F77_DBLE * x
F77_RET_T const F77_DBLE const F77_DBLE * f
return octave_value(v1.char_array_value() . concat(v2.char_array_value(), ra_idx),((a1.is_sq_string()||a2.is_sq_string()) ? '\'' :'"'))
octave_value binary_op(type_info &ti, octave_value::binary_op op, const octave_value &a, const octave_value &b)
octave_value_list ovl(const OV_Args &... args)
Construct an octave_value_list with less typing.
Definition: ovl.h:219
void map_2_xldexp(Array< T > &y, const Array< T > &f, const Array< T > &e)
Definition: pow2.cc:44
void map_2_xldexp_sparse(SparseMatrix &y, const SparseMatrix &f, const SparseMatrix &e)
Definition: pow2.cc:62