[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
- Re: element-wise exponent operator,
John W. Eaton <=