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).