octave-maintainers
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: element-wise exponent operator


From: John W. Eaton
Subject: Re: element-wise exponent operator
Date: Tue, 10 Mar 2020 13:10:31 -0400
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Thunderbird/68.5.0

On 2/28/20 1:09 PM, Rik wrote:

I started trying to understand bug #57671 (Power operator operating on
complex numbers results in NaN when broadcasting) a week ago.  The problem
behavior is

([1;0]*j) .^ [1, 0]
ans =

    0.0000 + 1.0000i   1.0000 +      0i
         0 +      0i      NaN -    NaNi

[...]
>
My first request is for an explanation of the convoluted mapping strategy
that Octave uses for operators.  There are tons of C++ files in
libinterp/operators/ which seem to map all of the various combinations of a
scalar, vector, or matrix object with another scalar, vector, or matrix
object.

This is a very old part of Octave from before C++ had templates and before Octave hadt classes (for dispatching based on type, which could also be used for built-in types) or broadcasting.

Since

  scalar .* matrix

behaves differently from

  matrix .* matrix

I decided to have actual scalar types in Octave so that we could write operators that did special things for mixed-type operations like the above. Then each operator function could be simpler (no special check is needed for scalar-by-matrix operations if matrices are always narrowed to scalars by the interpreter before dispatching to specific operators. But you have more operator functions, and when you add in complex versions, a LOT more. But I thought it was worth it to avoid wasting memory and time expanding (possibly large) matrices from real to complex if we had special operations for mixed types where possible and only resort to type conversions when mixed type operations won't work.

To determine which function to call, we have lookup tables for operators based on the type of both operands and we may also apply type conversion operators. I agree this is all a bit complicated. Maybe we can simplify some of it.

And this structure is also echoed in liboctave/operators.

I've been thinking about that a little bit recently while looking at the MEX interface. We have things like Matrix, ComplexMatrix, etc. in liboctave and a wrappers for them so they can be octave_value objects with a common interface for the interpreter. Matlab appears to just have mxArray that contains various types. We could do the same by moving the array storage directly into an object in the octave_value hierarchy, but I thought it would be more convenient for interfacing with and writing numerical code if we had separate array types that were independent of the Octave interpreter.

And
then there are exceptions to this structure such as
libinterp/corefcn/xpow.cc which slots in to the architecture as it gets
called from libinterp/operators, but isn't stored there.

The operators directory was originally intended to be a place to store the wrapper functions that all had the same signatures (for the table of operators in the interpreter) and xpow.cc was for the type-specific operations with Matrix, ComplexMatrix, etc. arguments. It's in libinterp instead of liboctave because the return type is octave_value because pow operations on two real values can sometimes produce complex.

Why aren't we
just mapping the '+' operator to a BLAS call?  Or maybe we are and I don't
understand how we get there.

Are there BLAS functions for vector addition, subtraction, multiplication, or division, or for mixed-type operations?

For bug #57671, the code which ends up getting called is in xpow.cc case #10.

// -*- 10 -*-
octave_value
elem_xpow (const ComplexNDArray& a, const NDArray& b)
{
   dim_vector a_dims = a.dims ();
   dim_vector b_dims = b.dims ();

   if (a_dims != b_dims)
     {
       if (! is_valid_bsxfun ("operator .^", a_dims, b_dims))
         octave::err_nonconformant ("operator .^", a_dims, b_dims);

       return bsxfun_pow (a, b);
     }

   ComplexNDArray result (a_dims);

   for (octave_idx_type i = 0; i < a.numel (); i++)
     {
       octave_quit ();
       double btmp = b(i);
       if (xisint (btmp))
         result(i) = std::pow (a(i), static_cast<int> (btmp));
       else
         result(i) = std::pow (a(i), btmp);
     }

   return result;
}

For the non-broadcasting case, which works, the key is to note the check
for an integer power (xisint (btmp)).  If this is found it calls std::pow
from the C++ library with a different function signature.

Should we be using a wrapper for pow and burying this logic inside a template function?

When
broadcasting is used it calls bsxfun_pow and this ends up calling code from
liboctave.  In particular, the individual operation is in
liboctave/operators/mx-inlines.cc

template <typename R, typename X, typename Y>
inline void
mx_inline_pow (size_t n, R *r, const X *x, const Y *y)
{
   using std::pow;

   for (size_t i = 0; i < n; i++)
     r[i] = pow (x[i], y[i]);
}
> This calls std::pow with a function signature of two reals which
results in
NaN + NaNi.

At a minimum, all the uses of std::pow in xpow.cc and mx-inlines.cc should
be checked to see if they need to install a test for an integer and then
perform the same cast as is done for case 10.

It seems like we should be doing this with a wrapper function for pow to get the behavior we want.

There may also be better ways to write the templates for brodcasting.

Handling Complex values will be a little more difficult.  The function
signatures available for pow are

template<class T> complex<T> pow (const complex<T>& x, int y);
template<class T> complex<T> pow (const complex<T>& x, const complex<T>& y);
template<class T> complex<T> pow (const complex<T>& x, const T& y);
template<class T> complex<T> pow (const T& x, const complex<T>& y);

There is no signature for pow (complex<real>, complex<int>) which means we
likely need to check first for whether the exponent can be narrowed to
real, and then checked subsequently for whether it is an integer.

To do this correctly everywhere would probably be easiest if we provided our own Complex class and used it consistently or at least had a wrapper function for pow that we used instead of using the ones from std::complex directly.

All of this will be tedious, because there are so many cases, which makes
one wonder whether the code in xpow.cc can't make better use of templates.

I'm sure it can. The functions there were originally written long before C++ had useful templates.

Regardless, once fixed we also need to add BIST tests for all the weird
corner cases.

Yes.

jwe





reply via email to

[Prev in Thread] Current Thread [Next in Thread]