2) ComplexFunction and ComplexResult functional interfaces Following up on my previous email, another alternative for ComplexFunction without using generic ComplexResult is as follows
@FunctionalInterface public interface ComplexFunction3 { void apply(Complex input, int offset, double[] result); } Example Conjugate implementation public static void conj(Complex in, int offset, double[] result) { result[offset] = in.getReal(); result[offset+1] = in.getImaginary(); } ComplexCartesianImpl data structure will change to double[] realAndImgPair with static factory method as below Complex { static Complex ofCartesian(double[] realAndImgPair); } And the complex functions used like below in Complex and ComplexList Complex { // default implementation are immutable always returning new instance to maintain b/w compatibility default Complex applyFunction(ComplexFunction function) { double[] result = new double[2]; return Complex.ofCartesian(function.apply(this,0,result); } } } ComplexList { .. // applies changes in place void forEach(ComplexFunction fun) { ComplexCursor cursor = new ComplexCursor(); while (cursor.index < r.length) { cursor.apply(realFunc.applyAsDouble(cursor), imgFunc .applyAsDouble(cursor)); cursor.index++; } } ... } On Fri, 10 Jun 2022 at 07:55, Sumanth Rajkumar <rajkumar.suma...@gmail.com> wrote: > Hi Alex and Giles, > > Thanks for the feedback. > > 1) Backward Compatibility and Complex Interface > Yes. I understand the backward compatibility requirement and my goal is to > be fully backward compatible. > > Fortunately, the existing Complex class has private constructors and so it > is possible to refactor it as an interface. > I was able to make the change along with a ComplexCartesianImpl class and > run all unit tests successfully. I did not have to make any changes to unit > tests. > "mvn test" runs successfully on my local machine after the changes > > > https://github.com/sumanth-rajkumar/commons-numbers/blob/sumanth-gsoc-22/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/Complex.java > > https://github.com/sumanth-rajkumar/commons-numbers/blob/sumanth-gsoc-22/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/ComplexCartesianImpl.java > > This I assume should meet the backward compatibility requirements? > > The proposed functional interface changes introduces a new interface > ComplexNumber. > I think we could reuse the refactored Complex interface instead of a new > ComplexNumber interface and still maintain full backward compatibility. > > This would provide flexibility to older applications to work with new > implementations of Complex such as MutableComplexImpl or ComplexPolarImpl > or even ComplexStructImpl in the future whenever Java supports value types. > > Please let me know what you think. > > 2) ComplexFunction and ComplexResult functional interfaces > Yes the generic <R> type introduces noise and can be solved as you > suggested. > > I was also thinking about an alternative that avoids the ComplexResult and > the generic type <R>. > > We could split complex unary operators into two primitive functions ( > ToDoubleFunction<Complex>) one returning the real part of result and other > for imaginary part > > interface ComplexFunction { > ToDoubleFunction<Complex> getReal() ; > ToDoubleFunction<Complex> getImaginary() ; > } > > And for example the Conjugate implementation would look like this > ComplexFunction conj = new ComplexFunction2() { > @Override > public ToDoubleFunction<Complex> getReal() { > return complex -> complex.real(); > } > @Override > public ToDoubleFunction<Complex> getImaginary() { > return complex -> -complex.imag(); > } > > }; > }; > > And the functions used like below in Complex and ComplexList > > Complex { > // default implementation are immutable always returning new instance > to maintain b/w compatibility > default Complex applyFunction(ComplexFunction function) { > return > Complex.ofCartesian(function.getReal().applyAsDouble(this),function.getImaginary().applyAsDouble(this)); > } > } > } > > ComplexList { > .. > // applies changes in place > void forEach(ComplexFunction fun) { > ToDoubleFunction<Complex> realFunc = fun.getReal(); > ToDoubleFunction<Complex> imgFunc = fun.getImaginary(); > ComplexCursor cursor = new ComplexCursor(); > while (cursor.index < r.length) { > cursor.apply(realFunc.applyAsDouble(cursor), > imgFunc .applyAsDouble(cursor)); > cursor.index++; > } > } > ... > } > > Does this make sense or we just stick to the original interface that > includes ComplexResult<R>? > > > 3) Naming convention for the Functional Interfaces > > On reviewing the functions in java.util.functions package, the convention > is > "Function" name is used for interfaces that can accept inputs of > different types and return result of different type > "Operator" are specialization of "Function" that take same type for > all inputs and result > > Should we follow a similar naming convention for Complex functional > interfaces? > > Thanks > > > > > On Fri, 27 May 2022 at 21:34, Alex Herbert <alex.d.herb...@gmail.com> > wrote: > >> 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). >> >