> > > 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. >
Ok 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). > I have provided both approaches. Functional interfaces that operate on single complex and on Arrays Source available here https://github.com/sumanth-rajkumar/commons-numbers/tree/98fbedeb7ed3e99597f461d88856b4d94c53343d/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex I have summarized the approaches below 1) Functional interfaces Single Pair Approach @FunctionalInterface public interface ComplexFunction<R> { R apply(double r, double i, ComplexResult<R> result); } @FunctionalInterface public interface ComplexResult<R> { R apply(double r, double i); } Array Approach @FunctionalInterface public interface ComplexDoubleUnaryOperator { ComplexDoubleArray apply(ComplexDoubleArray input, ComplexDoubleArray result); } @FunctionalInterface public interface ComplexArrayResult<R> { R set(int index, double r, double i); } interface ComplexDoubleArray extends Iterable<ComplexDouble>, ComplexArrayResult<ComplexDoubleArray> { ... } 2) Implementation for conjugate example in ComplexFunctions https://github.com/sumanth-rajkumar/commons-numbers/blob/98fbedeb7ed3e99597f461d88856b4d94c53343d/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/ComplexFunctions.java#L779 Single Pair Approach public static <R> R conj(double r, double i, ComplexResult<R> result) { return result.apply(r, -i); } Array Approach (the implementation below can be different in ComplexParallelFunctions for example) public static ComplexDoubleArray conj(ComplexDoubleArray input, ComplexDoubleArray out) { final int len = input.size(); for (int i = 0; i < len; i++) { ComplexDouble c = input.get(i); out = arrayConj(c.real(), c.imag(), out, i); } return out; } public static ComplexDoubleArray arrayConj(double r, double i, ComplexArrayResult<ComplexDoubleArray> resultConsumer, int index) { return resultConsumer.set(index, r, -i); } 3) Usage of conj in Complex class https://github.com/sumanth-rajkumar/commons-numbers/blob/98fbedeb7ed3e99597f461d88856b4d94c53343d/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/Complex.java#L1127 Single Pair Approach Complex implements Serializable, ComplexDouble { public Complex conj() { return this.applyUnaryOperator(ComplexFunctions::conj); } public Complex applyUnaryOperator(ComplexFunction<Complex> operator) { return operator.apply(this.real, this.imaginary, Complex::ofCartesian); } } Array Approach Complex implements Serializable, ComplexDoubleArray, ComplexDouble { public Complex conj() { return this.apply(ComplexFunctions::conj); } } 4) Usage of conj in ComplexList<ComplexDouble> (implements ComplexDoubleArray) Single Pair Approach ComplexList { public ComplexList conj() { return this.forEach(ComplexFunctions::conj); } public ComplexList forEach(ComplexFunction<Void> operator) { . . . Cursor cursor = new Cursor(..); for (int i = 0; i < len; i++) { cursor.setIndex(i); operator.apply(cursor.getReal(), cursor.getImginary(), cursor); } } private final class Cursor implements ComplexDouble, ComplexResult<Void> { . . . } } Array Approach ComplexList implements ComplexDoubleArray { public ComplexList conj() { return this.apply(ComplexFunctions::conj); } } Here the implementation including iteration/cursors has moved to the array based Lambda. For Complex arrays, I think it is beneficial to move the iteration logic from data storage (ComplexList) to the Lambda (ComplexFunctions::conj) This would allow developers to use different Lambdas for the same operation/on the same list What are you streaming? ComplexDoubleArray sub-arrays? Would the > ComplexParrallelStreamFunctions class be responsible for creating the > Spliterator for this to control sub-ranges? > Yes. The parallelization details would be abstracted by ComplexParrallelFunctions ComplexFunctions could provide the sequential cursor implementation as you had suggested before The different implementations abstracted by functional interface would only be possible with Array based approach The developer could choose explicitly to call list.apply(ComplexFunctions::conj) or list.apply(ComplexParallelFunctions::conj) ComplexList.conj() could choose to apply sequential or parallel etc based on some config. https://github.com/sumanth-rajkumar/commons-numbers/blob/1f013bfcf7d92c4daa160284c3d6a2ec26fd6464/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/ComplexList.java#L683 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 > I have included both implementations as explained above. I have tried to avoid excess object creation in the Array approach, Even when using Array approach for single Complex instance, object creation overhead is same as Single number approach Could you please take a quick look and confirm? The unit test for both approaches for conj operation on ComplexList is here https://github.com/sumanth-rajkumar/commons-numbers/blob/1f013bfcf7d92c4daa160284c3d6a2ec26fd6464/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexListTest.java#L94 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. > I see parallelizations in jtransform... https://github.com/wendykierp/JTransforms/blob/master/src/main/java/org/jtransforms/fft/DoubleFFT_1D.java#L688 It is possible someone comes up with algorithm to implement FFT using SIMD ( Vector API/GPUs) and then we could adapt into Functional interface similar to Jtransform The ComplexDoubleArray approach with the methods to convert to/from interleaved double[] for complex pairs is specifically done for extracting data in Jtransforms formats https://github.com/wendykierp/JTransforms/blob/7084c209d7be7952f7bdde7af2fb8eec7cbc17cd/src/main/java/org/jtransforms/fft/DoubleFFT_1D.java#L228 We can add more methods to ComplexDoubleArray to convert/to from for other providers as needed such as DoubleBuffer formats etc.. > Note: Vector functions on a single complex number is not applicable. > > Yes, in general there won't be any ComplexVectorFunctions for the single pair functional interface. We provide ComplexVectorFunctions operations only for List based operation that would benefit from Vector based SIMD parallelization However if we pass a List of size 1, it would still work, but possibly not perform as well. Thanks Sumanth