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 <[email protected]>
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 <[email protected]>
> wrote:
>
>> On Thu, 26 May 2022 at 15:04, Gilles Sadowski <[email protected]>
>> 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).
>>
>