GNU Octave  9.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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