Hi,

Thanks for your interest in Commons Numbers.

On Mon, 14 Mar 2022 at 00:09, Gilles Sadowski <gillese...@gmail.com> wrote:

> >
> > My partial implementation (with TODOs for many operations) is available
> > here.
> >
> https://github.com/sumanth-rajkumar/commons-numbers/blob/sumanth-gsoc-22/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/ComplexList.java


Thanks for the implementation idea. This is a literal implementation of a
List<Complex>. I think we should take a step back and find use cases for a
large set of complex numbers. That should drive the API design.

For example a common operation with complex numbers is to conjugate
multiply the fast fourier transform of two data arrays. The conjugate
multiply in the frequency domain is equivalent to the correlation in the
spatial domain. So I would require:

ComplexList a;
ComplexList b;

a.conj().multiply(b);

But what is the most appropriate method to do this? I do not think we want
to implement full matrix functionality for multiplication of arrays. But we
should allow an API that makes this type of work efficient in terms of
memory usage, i.e. zero object allocation during computation (avoid
creating Complex instances) and ideally no intermediate array allocation.
So in the above I do not want to create an entire list of conjugates before
multiplying by another complex number. I also want the option to write to a
new array or back to the original one.

So should we have some type of generic interface for an operation on a
Complex:

interface ComplexFunction {
   interface ComplexResult {
      void complex(double real, double imag);
   }
   void apply(double re, double im, ComplexResult);
}

Then a list to allow operations on elements in place. For example to
compute the conjugate:

ComplexList a;
a.forEach((re, im, result) -> result.complex(re, -im));

All operations in the Complex class can be rewritten as static methods
using a minimal set of functional interfaces.

static void conj(double re, double im, ComplexResult result) {
    result.complex(re, -im);
}

The operation then becomes:

ComplexList a;
a.forEach(Complex::conj);

Which is a bit less cumbersome to write.

Operations could be chained using a 'andThen' method in the interface:

ComplexList a;
a.forEach(((ComplexFunction) Complex::conj).andThen(Complex::sin));

I've not considered exactly how this will work in practice.

Functions that convert to a real number (such as abs()) could write 0 to
the imaginary.

The specifics will depend on all the operations in Complex. So a start
point may be to refactor the class to expose all the instance methods as
static methods that take input and write the result to a destination. These
can be used by (all) the Complex instance methods. I say (all) as some may
be more efficient to leave as is, namely the simple methods like negate or
conjugate, and the operations with real numbers.

Re: Your code implementation

I notice that the implementation for the expandable array backed list and
hash code generation are "heavily reliant" on the JDK (11?) source for
ArrayList and Arrays. It is good practice to be aware of the license terms
of any open source code project you may wish to use. The Oracle Open
JDK license terms are here [1]. This is under the GNU GPL v2 license which
is not permissible to be used in an ASF project [2]. In general it is fine
to look at the JDK source code to see how the function works or find bugs.
However any new code reimplementing the functionality should be done as if
blind to the code and only using a contractual specification of what the
code is meant to do. The modern javadoc effort by the JDK tries to provide
a very good specification of each method contract. So it is often possible
to write the same function from the javadoc description. An ideal javadoc
should have this as its goal. Effectively this would be someone telling you
what the code does but not showing you how:

1. A default array size of 10
2. Backing array will grow using a factor of 50% up to the maximum array
size
3. Overflow of the maximum array size will result in OutOfMemoryError
4. Index operations outside of the current list size [0, size) will throw
ArrayIndexOutOfBoundException
5. etc

Alex

[1] http://openjdk.java.net/legal/gplv2+ce.html
[2] https://www.apache.org/legal/resolved.html#category-x

Reply via email to