Hipparchus-Math / hipparchus

An efficient, general-purpose mathematics components library in the Java programming language
Apache License 2.0
139 stars 41 forks source link

Is a FDSFactory<T> for type Complex available? #67

Closed axkr closed 3 years ago

axkr commented 4 years ago

Is there something similar like a FDSFactory for type Complex available?

maisonobe commented 4 years ago

Unfortunately, no. The reason is that Complex implements FieldElement but not 'RealFieldElement`. There are several methods that cannot be implemented (floor, ceil, copysign, remainder, rint, round). There are also other missing methods that could be implemented but we never did (for example inverse hyperbolic sine, using some choice for branch cut since the function is multivalued).

I have been thinking about introducing an intermediate interface between FieldElement and 'RealFieldElementso we could haveComplex` implement it and share more algorithms with reals like differentiation. I have not yet found a way to do it.

axkr commented 4 years ago

For example Mathematica has the convention that real and imaginary parts are applied separately for complex numbers, at least for Floor, Ceiling. Mod is also working for complex arguments. Maybe this "conventions" can be implemented in hipparchus in the same way?

maisonobe commented 4 years ago

I was not aware of such conventions and would not have proposed this as it seems a little weird to me. However, if it is already accepted elsewhere, then yes, it makes sense to implement it in Hipparchus too.

psteitz commented 4 years ago

Unfortunately, no. The reason is that Complex implements FieldElement but not 'RealFieldElement`. There are several methods that cannot be implemented (floor, ceil, copysign, remainder, rint, round). There are also other missing methods that could be implemented but we never did (for example inverse hyperbolic sine, using some choice for branch cut since the function is multivalued).

I have been thinking about introducing an intermediate interface between FieldElement and 'RealFieldElementso we could haveComplex` implement it and share more algorithms with reals like differentiation. I have not yet found a way to do it.

An intermediary interface would be preferable. I have a basic question though. Are you sure Rall's setup lifts directly to complex-valued functions? How exactly are the differentials evaluated? How will these things be used? Sorry, I am probably missing something basic here.

maisonobe commented 4 years ago

OK for an intermediate interface. I think extending the Ralls numbers to complex will work. They work by applying directly the chain rule at each operation, so they require only having a proper add, subtract, multiply... In fact, we already did extend differentiation when we created FieldDerivativeStructure in december 2016, which was the more heavyweight work to do. Now that this is up and working properly since a few years, the rest should be easier. There are however, quite a number of methods to implement as our Complex class is currently rather basic.

maisonobe commented 4 years ago

I'm working on this issue. It is harder than I first thought. Up to now, I have created an intermediate CalculusFieldElement interface between FieldElement and RealFieldElement and have Complex implement it. This means Complex now have all calculus related functions implemented (including all inverse hyperbolic and all linear combinations). None of this is tested yet! Testing will be the next step. I am not sure however that this will be sufficient for derivatives. Perhaps instead of allowing Complex to be used inside FDSFactory we will need a reversed implementation (i.e. something the FieldComplex or DerivativeComplex). I really did not think thoroughly about it as I am focusing on a complete and tested Complex class for now.

maisonobe commented 4 years ago

I have pushed a complex-field branch. It is a first step (not complete yet) to solve this issue. The branch contains a new intermediate interface and implementation of all calculus-related functions for complex. There is a fair number of tests, including tests for branch cuts on some functions. I still need to add some tests for atan2 and all the linear combinations functions, but thought it can wait a little. No work has been done yet on the differentiation aspect, so I still don't know if this extension will be sufficient or if a sister class devoted to complex-with-derivatives will be needed. If someone could review the current implementation, I would be happy to get some feedback.

maisonobe commented 4 years ago

I have completed a first pass on this and merged the complex-branch back to master. Now Complex implements CalculusFieldElement<Complex> which provides a lot of calculus-related functions. Validating this allowed to find a number of bugs in existing classes (#76 and #82). Some existing functions have been rewritten, either to improve efficiency or to improve accuracy (for example multiplication now uses linear combinations with high accuracy).

Next step is to look at the derivatives.

maisonobe commented 4 years ago

We have added quite a large number of functions so Complex now implements the new CalculusFieldElement interface. There are four remaining functions that belong to RealFieldElement but not to CalculusFieldElement : the remainder function with two signatures, the round function and the abs function.

The two first functions (remainder with two signatures) do not really make sense for Complex. We could try to use a different integer factor for real and imaginary parts, but that seems really awkward to me and I cannot see a use case.

The round function could be implemented ignoring the imaginary part, but it it really useful or should users simply call FastMath.round(z.getReal()) in that case?

The abs function should definitely be moved upward in the hierarchy, at CalculusFieldElement level, but this is not possible without breaking compatibility as Complex already as an abs method but with a different return type (double instead of Complex). I think the existing Complex.abs should be renamed so we can add a new abs with the Complex return type compatible with the interface. This however cannot be done for version 1.7, it will have to wait for version 2.0 for compatibility sake.

I have prepared work for derivatives with new tests that can be easily adapted for any field. I think that we also need to rethink the DerivativeStructure and FieldDerivativeStructure so they refer to CalculusFieldElementand not RealFieldElement. This would imply we cannot use remainder anymore in derivatives, and perhaps not round (but this is moot for the round as in any case derivatives are zero for rounded integers). So here again, we have to wait for 2.0.

So I think the work that can be done for 1.7 is already there and the improvements made to the Complex class are already important, but this issue can be completely solved only with 2.0.

axkr commented 4 years ago

For the remainder function see function QuotientRemainder in MMA. It is also defined for complex numbers:

IMO round should be applied separately to real and imaginary parts of complex numbers.

maisonobe commented 4 years ago

For the remainder function see function QuotientRemainder in MMA

I'm sorry, I don't get it. Could you tell me what would be expected as the result of new Complex(14, -5).remainder(new Complex(3, 4))?

IMO round should be applied separately to real and imaginary parts of complex numbers.

OK, so I moved the round method back to RealFieldElement. There is already a method in CalculusFieldElement and therefore already implemented in Complex that you could use: rint().

axkr commented 4 years ago

@maisonobe: I created a GIST which should show the quotient and remainder idea:

maisonobe commented 4 years ago

Thanks a lot! I finally got it. I was stupid and stubbornly focused on classical integer, I forgot about Gaussian integers.

I have implemented the remainder method. I used a different implementation than the one in your GIST, both to comply to existing API (returning only remainder) and to ensure consistent behaviour by using rint() rather than round().

This allowed to move the remainder implementation up one level in the interfaces hierarchy. This change has been pushed to the master branch.

The only problem left is abs(), and here we cannot do anything before 2.0. I did however implement something beforehand. It is in a new calculus-field branch. I removed the abs() method from Complex, added a norm() method to CalculusFieldElement (which is implemented by Complex) and replaced almost everywhere the uses of RealFieldElement bu CalculusFieldElement. This now allows to use FieldDerivativeStructure<Complex>! As I wrote before, this cannot be integrated in the 1.x series, so the calculus-field ranch will not be merged immediately. You can however check it out and play with it already, by compiling it by yourself. I don't consider the complex derivatives are sufficiently validated, so more work is needed on it, but most of the heavy lifting is done. I would be happy if you could give it a try and give me some feedback.

axkr commented 3 years ago

Do you have a time schedule for 2.0?

maisonobe commented 3 years ago

Not yet. I confess I have been slow on this (still have to look at the bug you identified in eigen decomposition). I would like to take the 2.0 opportunity to introduce another major change, related to contrained optimisation, but this takes a lot of time and delays 2.0 a lot.

axkr commented 3 years ago

OK thanks for your response.

maisonobe commented 3 years ago

Well, we are making some progress and heading for a 2.0 release. The calculus-field branch has been merged into master yesterday. Beware that as started above (comment from 2020-06-02) it introduces incompatibilities (this is the reason why it had to wait for a major release before being merged). I hope we could publish 2.0 in about a month.

wardev commented 3 years ago

Looking at RealFieldElement do we still need it since it only has two methods left? The abs() method just delegates to nom() which Complex implements. The round() method was already extended to the complex numbers with rint(). So both those methods seem to be defined for complex numbers.

From the inheritance perspective I think the case can be made either way. A complex number "is a" real number augmented with an imaginary number. A real number "is a" complex number with zero imaginary part. One can make these statements again about quaternions, e.g. a quaternion is a complex number augmented with two more imaginary numbers. I think your idea of the calculus field interface is a good one to avoid that issue, but it will make it harder for users to update to the new version.

maisonobe commented 3 years ago

OK to mode the two methods upward and remove the RealFieldElement interface, which would finally correspond to having renamed it.

wardev commented 3 years ago

yes or just remove the two methods left in RFE since abs() just delegates to norm() and round() just delegates to FastMath.round(). Although it is nice to have an abs() method even if it is the same as norm().

maisonobe commented 3 years ago

I'll move the methods upward.

maisonobe commented 3 years ago

I forgot a few renames that should be added IMHO: RealField{Univariate|Bivariate|Vector|Matrix}Function.

However, I don't think renaming also RealVector or RealMatrix would be wise. Any thought?

axkr commented 3 years ago

I have no problems with "renaming", but I don't know how other dependent projects think about it?

If you do a "general renaming" then IMO you should think about all possible renaming suggestions:

Otherwise it will probably be better to keep the names as "compatible" as possible to older releases.

wardev commented 3 years ago

I forgot a few renames that should be added IMHO: RealField{Univariate|Bivariate|Vector|Matrix}Function.

+1 to rename since the interface to which they refer is going away

However, I don't think renaming also RealVector or RealMatrix would be wise. Any thought?

-1 for renaming these since they never used the RFE interface in the first place.

maisonobe commented 3 years ago

I recall the change about renaming the add method. The fact is that add is a method name rather than an interface name, so it is used in many places. In my workspace, a quick count showed 581 different calls to the "add" method defined by this interface, among a total number of 15726 calls to other "add" methods. So in my own IDE (eclipse), I could do the rename on Hipparchus and have it propagated to my dependent projects automatically. External users on the other hand that would just download the updated would need to sort out the calls just by fixing compiler errors, most probably one by one. Given the ubiquity of "add", this is much more difficult than renaming something very specific as "RealFieldElement".

maisonobe commented 3 years ago

I will close this issue and open a new one, closely related.

axkr commented 3 years ago

Do you have a unit test or example how to use FDSFactory for a UnivariateDifferentiableFunction?

maisonobe commented 3 years ago

You can look at FunctionsWithFastMathVersionTest.testCos, which calls doTestFn which it self call 'checkFnEqualities`.