On Thu, 26 May 2022 at 15:04, Gilles Sadowski <gillese...@gmail.com> wrote:

>
> > Next, I wanted to mention how I plan to start this project and was hoping
> > to get some feedback.
> >
> > As per my proposal, the first thing I wanted to start with was the API
> > design which would have interfaces to represent complex numbers, methods
> to
> > convert to/from linear primitive arrays, Java 8 functional interfaces for
> > unary/binary operators and for functions for complex operations and
> > transforms involving: complex number and real numbers, complex vectors
> and
> > scalars, complex matrix, vectors and scalars.
>

There are many items here. I would suggest breaking it down. Perhaps:

1.
interfaces to represent complex numbers
unary/binary operators and for functions for complex operations

2.
methods to convert to/from linear primitive arrays

3.
complex vectors and scalars, complex matrix, vectors and scalars.


Although not completely independant, we could discuss each in turn and see
what functionality is required.

I will start with topic 1. Currently we have a single object, Complex, that
represents a complex number in cartesian form. It has a full set of
operations specified in ISO C99. I would suggest you have a look at the
specification as it has a lot of information about this [1].

There is a benchmark for these operations in the examples JMH module:
org.apache.commons.numbers.examples.jmh.complex.ComplexPerformance

Ideally any changes to extract all the methods into a static class should
not impact performance. Many of the methods are quite involved and
therefore slow. However some methods such as those for
add/subtract/multiply/divide with real or imaginary scalars will be fast.
It would be interesting to see if abstraction to a static class impacts
their performance. These operations are not in the JMH benchmark so this
could be added to provide a reference point for these.


>From your GH code you have the following interface:

public interface ComplexFunction {
    <R> R apply(double r, double i, ComplexResult<R> result);
}

I cannot create a lambda function for this as the method has a generic type
parameter. This fails to compile.

ComplexFunction f = (r, i, result) -> {
    // conjugate
    return result.apply(r, -i);
};

This can be solved by moving <R> to the interface declaration:

public interface ComplexFunction<R> {
    R apply(double r, double i, ComplexResult<R> result);
}

But then all use of ComplexFunction has to be typed which can get noisy. It
is however explicit in what the function output will be (and we assume the
input is a complex number of some sort).


Q. Do we wish to support effectively duplication of operations by accepting
primitives and also a ComplexNumber type in the static methods:

interface ComplexNumber {
    double real();
    double imag();
}

class ComplexFunctions {
    <R extends ComplexNumber> R sin(ComplexNumber c, ComplexResult<R> r) {
        return sin(c.real(), c.imag(), r);
    }
    <R extends ComplexNumber> R sin(double r, double i, ComplexResult<R>) {
        // ...
    }
}

There are various options for chaining methods together for sequential
operations on the same complex. Should this avoid repeat object allocation
by providing a MutableComplex holder:

class MutableComplex implements ComplexNumber,
ComplexResult<MutableComplex> {
   // allows read, write   from/to  real, imaginary parts
}



Q. How to manipulate ComplexList:

class ComplexList implements List<Complex> {
    private double[] r;
    private double[] i;
}

You have forEach methods (note this is without the type parameter):

void forEach(ComplexFunction fun);

So how do I declare a function to pass to the list, that accepts the real
and imaginary parts and saves them back to the list. Currently you have the
ComplexFunction accept the real and imaginary parts. But what if it
accepted a ComplexNumber:

public interface ComplexFunction<R> {
    R apply(ComplexNumber c, ComplexResult<R> result);
}


The ComplexList need only provide a single object that acts as a read/write
cursor over the data by implementing both interfaces ComplexNumber and
ComplexResult:

// Internal class
class ComplexCursor implements ComplexNumber, ComplexResult<Void> {
    // Directly manipulated by the enclosing list
    int index;

    @Override
    public Void apply(double r, double i) {
        ComplexList.this.r[index] = r;
        ComplexList.this.i[index] = i;
        return null;
    }

    @Override
    public double real() {
        return ComplexList.this.r[index];
    }

    @Override
    public double imag() {
        return ComplexList.this.i[index];
    }
}

I can write the method:

void forEach(ComplexFunction<Void> fun) {
    ComplexCursor cursor = new ComplexCursor();
    while (cursor.index < r.length) {
        fun.apply(cursor, cursor);
        cursor.index++;
    }
}

And call it like this:

class ComplexFunctions {
    static <R> R conj(ComplexNumber c, ComplexResult<R> r) {
        return r.apply(c.real(), -c.imag());
    }
}


ComplexList l;
l.forEach(ComplexFunctions::conj);

// Using a lambda
l.forEach((c, result) -> {
    return result.apply(c.real(), -c.imag());
});


I've provided a few ideas there to get started. In summary here are some
requirements we should keep in mind:

- Complex functions should be able to be declared with lambda functions
- The ComplexList allows read/write type operations on elements with
complex functions

Alex


[1] http://www.open-std.org/JTC1/SC22/WG14/www/standards, specifically WG14
N1256 sections 7.3 (p 170) and Annex G (p 467).

Reply via email to