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
>

Reply via email to