On Sun, 12 Jun 2022 at 04:37, Sumanth Rajkumar <rajkumar.suma...@gmail.com>
wrote:

> On Sat, Jun 11, 2022, 18:02 Gilles Sadowski <gillese...@gmail.com> wrote:
>
> > I have a hard time figuring out whether these bits of code are
> > intended to become the application developer API...
> > What data-structure(s) will be visible (from the application)?
> > What will be hidden ("implementation details")?
> >
>
> Developer API will look something like this
>
> //1) Static methods to initialize array from external forms
>
> ComplexDoubleArray a = ComplexDoubleArray.fromBuffer(Double Buffer);
> ComplexDoubleArray b = ComplexDoubleArray.fromArray(double[]);
>
> ComplexDoubleArray c = ComplexDoubleArray.parseFile(File);
>
>
> //2)  Methods on Array interface for complex operations. One for each
> functional interface type.. unary, binary, scalar operations
>
> @FunctionalInterface
> interface ComplexDoubleUnaryOperator {
>
> ComplexDoubleArray apply(ComplexDoubleArray input, ComplexDoubleArray
> result);
> }
>
> interface ComplexDoubleArray {
>
> int size();
>
> //MutableComplexDoubleArray extends ComplexDoubleArray with setter,
> mutation and static methods to allocate capacity
>
> MutableComplexDoubleArray asMutable();
>
> ComplexDoubleArray asImmutable();
>
> default boolean isImmutable() { return true;}
>
>
>   default ComplexDoubleArray apply(ComplexDoubleUnaryOperator op){
>
> return op.apply(this, result);
>
> }
>
> . . .
>
> }
>

Some thoughts:

- an array should be mutable by default. It would be made immutable using a
Collections.immutableX(...) type wrapper.
- an array should have get and set methods for complex, real and imaginary
using an index within the array size.
- a functional interface should be created that allows generic
specification of the operation to perform on each atomic unit (i.e. a
single complex number) as a lambda function.
- two arrays (a and b) should be able to be paired for an operation on all
pairs of (a, b), specified by a functional interface that can be written as
a lambda function. This method will write in-place to a or an optional
output array c. This could be preallocated, supplied dynamically using a
T[]::new lambda function or some other variant.

The last two requirements bring us back to the requirement for a generic
way to write the result of a complex number computation (i.e. a single
[real, imaginary] pair).


>
> //Function interfaces behavior - If output array is immutable, operations
> return a copy, else result is applied in place on the passed in output and
> returned
>
> //3 Developer API usage of complex operations on complex arrays
>
> ComplexDoubleArray a = init();
> a = a.asImmutable();
>
> ComplexDoubleArray r1  = a.apply(ComplexFunctions::exp);
>
> ComplexDoubleArray r2  = a.apply(ComplexParrallelStreamFunctions::exp);
>

What are you streaming? ComplexDoubleArray sub-arrays? Would the
ComplexParrallelStreamFunctions class be responsible for creating the
Spliterator for this to control sub-ranges?


>
> ComplexDoubleArray r3  = a.apply(ComplexVectorFunctions::exp);
>
> ComplexDoubleArray r4  = a.apply(ComplexJTransformFunctions::forward_fft);
>
>
> // 4  ComplexFunctions can provide commons C99 reference implementations
> for supported complex operations as static methods that match the
> functional interfaces. ( Refactored methods from existing Complex class).
> Third parties can provide alternate implementations such as parallel stream
> or vector based implementations and used by developers as above.
>
>
> //5 the above functions also reused with existing Complex class?
>
> Complex implements ComplexDouble, ComplexDoubleArray {
>
> @Override
> public int size() {return 1;}
>
> @Override
> double [] toDoubleArray() { . . . }
>
> . . .
>
> //Refactored instance methods of Complex class
> public Complex exp() {
> return apply(ComplexFunctions::exp);
>
> }
> . . .
>
> }
>
> Complex c = Complex.ofCartesian(r,i);
>
> //Backward compatible C99 implementation
> Complex expc = c.exp();
>
> Complex thirdparty_exp = c.apply(SomeThirdPartyFunctions::exp);
>

My concern with this approach is that if we store the minimum of a final
real and imaginary part then any operation will have to:

- create a ComplexDoubleArray for the result
- Write to the ComplexDoubleArray
- Convert the result ComplexDoubleArray back to a Complex

If you look at how this can be used then there is a lot of excess object
overhead here when you wish to write code for complex number computation in
an OO way:
See org.apache.commons.math4.legacy.analysis.solvers.LaguerreSolver

IIUC the reason to have ComplexFunctions operate on arrays is to allow
Vector optimisations. In this case the data would have to be extracted into
a Vector<Double> species from a double[]. This would be more easily served
by allowing the ComplexBuffer to be written to a double[]:

DoubleComplexBuffer {
    // May be a reference to underlying data, or new allocation
    double[] getReal();
    // Copied to the provided array
    double[] getReal(double[] b);
}

By providing a ComplexBuffer implementation that uses separate real and
imaginary double[] arrays this is just a pass through method. The
ComplexVectorFunctions can be written to act on double[] directly as IIUC
the ISO c99 functions would have to be rewritten to use the Vector API
functions for multiply, add, etc. You cannot reuse existing functions that
take in and return double, e.g. Math.exp.

Is there any other reason to operate on arrays or buffers as abstractions?
Otherwise operating on a single complex number seems more appropriate for
other use cases which would ultimately loop over the data and compute on
single input complex numbers.

Bulk operations such as FFT would require the entire data to be operated
on. So this would not work in parallel streams using sub-ranges. Using a
3rd party such as JTransforms would require extracting the data for their
API. So it is only any new functionality we are to write based on more than
one complex number that would require the API to be designed around ranges
of input and output numbers.

So shall we revisit Gille's question:

Do we have use-cases of non-trivial processing of N-dimensional cubes of
complex numbers?

- Transforms such as FFT
- Multiply of two arrays of complex numbers (e.g. conjugate multiply of FFT
data is a correlation, multiply is a convolution)

Any others?


> //Require Java16+
> Complex vectorexp = c.apply(Complex VectorFunctions::exp)
>

Note: Vector functions on a single complex number is not applicable.


>
> Thanks,
> Sumanth
>

Reply via email to