Hi Alex, What do you intend to support as a "Matrix"? Is it for 2D or ND? What functionality already exists for complex matrix operations such as add and multiply in for example EJML?
This may require some expansion. a) I reviewed EJML data naming conventions, this is what they follow: https://github.com/lessthanoptimal/ejml#procedural-api-matrix-and-class-names Should we follow this approach as well? I wasn't thinking about implementing ND right now, maybe I can during Phase 2? b) EJML only supports 2D matrices with vector (1XN) being a special case, should I do that too? c) EJML uses a mutable ComplexResult, they use a single mutable class for both input and output and return void Here is the link to their implementation: https://github.com/lessthanoptimal/ejml/blob/SNAPSHOT/main/ejml-core/src/org/ejml/ops/ComplexMath_F64.java Their functional interface looks like this void apply(ComplexDouble in, MutableComplexDouble out) d) For dense Matrix internal storage, I'm planning on using separate arrays (or a single array where the first half of the array will be real and the second half will be imaginary) instead of alternating real and imaginary because it allows us to optimize space for pure imaginary or real matrices. Is that ok? I am still wondering how these functions can be composed. Here are a few ideas.... This may need more work.. I have provided an approach below using an intermediate ComplexResultInterceptor class that is thread-safe and minimizes object creation using thread local and stacks. It also doesn't require ComplexDouble constraint for the generic R result type. @FunctionalInterface public interface ComplexFunction<R> { default R apply(Complex c, ComplexResult<R> result) { return apply(c.real(), c.imag(), result); } R apply(double r, double i, ComplexResult<R> result); default <U> ComplexFunction<U> thenApply(ComplexFunction<U> afterFunction) { return (x, y, afterResult) -> { ComplexResultInterceptor<R> interceptor = ComplexResultInterceptor.TLOCAL_ResultInterceptor.get(); interceptor.pushResultProvider(afterFunction, afterResult); apply(x, y, interceptor); return interceptor.popResult(); }; } } public class ComplexResultInterceptor<R> implements ComplexResult<R> { private Stack<ComplexResult<?>> afterResultProviderStack = new Stack<>(); private Stack<ComplexFunction<?>> afterFunctionStack= new Stack<>(); private Stack<Object> afterResult = new Stack<>(); public static final ThreadLocal<ComplexResultInterceptor> TLOCAL_ResultInterceptor = ThreadLocal.withInitial(() -> new ComplexResultInterceptor()); private static final AtomicLong counter = new AtomicLong(); private ComplexResultInterceptor() { System.out.println("Allocating ComplexResultInterceptor # " + counter.incrementAndGet()); } public <U> void pushResultProvider(ComplexFunction<U> func, ComplexResult<U> provider) { afterFunctionStack.push(func); afterResultProviderStack.push(provider); } @Override public R apply(double r, double i) { ComplexFunction after = afterFunctionStack.pop(); ComplexResult resultProvider = afterResultProviderStack.pop(); afterResult.push(after.apply(r, i, resultProvider)); return null; } public <U> U popResult() { return (U) afterResult.pop(); } } Here is a link to the unit test class that ran successfully and the ComplexResultInterceptor class: https://github.com/sumanth-rajkumar/commons-numbers/blob/sumanth-gsoc-22-array_refactor/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexComposeTest.java https://github.com/sumanth-rajkumar/commons-numbers/blob/sumanth-gsoc-22-array_refactor/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/ComplexResultInterceptor.java > 4) Refactor existing instance methods in Complex class as static functions > in ComplexDoubleFunctions class using functional interface signatures > I would start with this. It can always be updated if the functional interface is later modified. I'm working on this right now, I've refactored most of the existing instance methods and I should be done by tomorrow Thanks, Sumanth On Mon, 13 Jun 2022 at 06:26, Alex Herbert <alex.d.herb...@gmail.com> wrote: > On Mon, 13 Jun 2022 at 07:47, Sumanth Rajkumar <rajkumar.suma...@gmail.com > > > wrote: > > > > > > > For Phase 1, I propose to do the following > > > > > > 1) Introduce ComplexDouble, ComplexDoubleVector and ComplexDoubleMatrix > > interfaces > > > > What do you intend to support as a "Matrix"? Is it for 2D or ND? What > functionality already exists for complex matrix operations such as add and > multiply in for example EJML? > > This may require some expansion. > > > > The interfaces to have methods for applying below unary and binary > > double functions > > The interfaces to have methods to convert to/from primitive double > > arrays (both separate arrays for real/imaginary and single interleaved > > array) > > The interfaces to have methods to convert to/from DoubleBuffer (both > > separate arrays for real/imaginary and single interleaved buffer) > > > > 2) Introduce generic (item based) functional interfaces for unary and > > binary double functions > > > > @FunctionalInterface > > public interface ComplexDoubleFunction<R> { > > > > R apply(double real, double imaginary, ComplexDoubleResult<R> result); > > } > > > > @FunctionalInterface > > public interface ComplexDoubleBiFunction<R> { > > > > R apply(double real1, double imaginary1, double real2, double imaginary2, > > ComplexDoubleResult<R> result); > > } > > > > @FunctionalInterface > > public interface ComplexDoubleResult<R> { > > > > R apply(double r, double i); > > > > } > > > > > I am still wondering how these functions can be composed. Here are a few > ideas based on requiring that the functional interface generic type is a > ComplexDouble. This allows the result single item R to be decomposed again > into real and imaginary parts to pass to the next method. > > public static <R> R conj(double r, double i, ComplexResult<R> result) { > return result.apply(r, -i); > } > > public static <R> R multiplyImaginary(double r, double i, ComplexResult<R> > result) { > return result.apply(-i, r); > } > > @FunctionalInterface > public interface ComplexDoubleFunction<R extends ComplexDouble> { > > default R apply(ComplexDouble c, ComplexResult<R> result) { > return apply(c.real(), c.imag(), result); > } > > R apply(double r, double i, ComplexResult<R> result); > > default <V extends ComplexDouble> ComplexDoubleFunction<V> > andThen(ComplexDoubleFunction<V> after, > ComplexResult<R> intermediateResult) { > Objects.requireNonNull(after); > // Requires the intermediate which would be the terminal result if > the function is not composed. > return (r, i, result) -> after.apply(apply(r, i, > intermediateResult), result); > } > > default <V extends ComplexDouble> ComplexDoubleFunction<V> > andThen2(ComplexDoubleFunction<V> after) { > Objects.requireNonNull(after); > return (re, im, result) -> { > // Fabricate the intermediate. Function is not thread safe. > double[] parts = {0, 0}; > ComplexResult<R> intermediateResult = (x, y) -> { > parts[0] = x; > parts[1] = y; > return null; > }; > R t = apply(re, im, intermediateResult); > return after.apply(t, result); > }; > } > > default ComplexDoubleFunction<R> andThen(ComplexDoubleFunction<R> > after) { > Objects.requireNonNull(after); > // Thread safe. The intermediate is also the terminal result. This > is not optimal if the intermediate/terminal is a list with inefficient > read/write to the current position. > return (r, i, result) -> after.apply(apply(r, i, result), result); > } > } > > public static void example() { > ComplexDoubleFunction<ComplexDouble> fun = > ComplexDoubleFunctions::conj; > ComplexDoubleFunction<ComplexDouble> fun2 = > ComplexDoubleFunctions::multiplyImaginary; > ComplexDoubleFunction<Complex> fun3 = > ComplexDoubleFunctions::multiplyImaginary; > // Not allowed as Void does not extend ComplexDouble > //ComplexDoubleFunction<Void> fun4 = > ComplexDoubleFunctions::multiplyImaginary; > > ComplexDoubleFunction<ComplexDouble> funA = fun.andThen(fun2); > // Not allowed > //ComplexDoubleFunction<Complex> funA2 = fun.andThen(fun2); > ComplexDoubleFunction<Complex> funB = fun.andThen2(fun3); > ComplexDoubleFunction<Complex> funC = fun.andThen(fun3, > Complex::ofCartesian); > } > > However you cannot create a ComplexDoubleFunction<Void> which will not > return a result as the interface is typed to ComplexDouble. Not returning a > final result is the usage required by list operations that do not return > results but write back to storage in ComplexResult<Void>. > > Composite function requires an intermediate to hold the result to pass to > the next function. If this is the same object then the function will not be > thread-safe. > > This may need more work... > > > > 4) Refactor existing instance methods in Complex class as static > functions > > in ComplexDoubleFunctions class using functional interface signatures > > > > I would start with this. It can always be updated if the functional > interface is later modified. > > Alex >