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 >