Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Next steps/guidelines for AbstractIrrational #26550

Closed
garrison opened this issue Mar 21, 2018 · 14 comments · Fixed by #26555
Closed

Next steps/guidelines for AbstractIrrational #26550

garrison opened this issue Mar 21, 2018 · 14 comments · Fixed by #26555
Labels
domain:docs This change adds or pertains to documentation domain:maths Mathematical functions

Comments

@garrison
Copy link
Sponsor Member

I am very excited about AbstractIrrational and am hoping to understand the intended guidelines for implementing subtypes of it. The abstract type itself was introduced by @stevengj in #24245, e.g. for representing exact multiples of π. @Jutho also mentioned a use case for square roots of rational numbers.

One concern/confusion I have is that in the both cases, not all possible values are actually irrational. For instance, 0π is rational, as is √4. Are types that derive from AbstractIrrational supposed to error when such values are given?

In my (tentative) opinion, it will be much more elegant for these types to allow any multiple of π, including 0π, as well as the square root of any rational number, including perfect squares. However, this suggests that a value represented by AbstractIrrational need not actually be irrational, and thus some of the overloads introduced in #24245 are not true in general: for instance, it is claimed that ==(x::AbstractIrrational, y::Real) = false because "Irrationals, by definition, can't have a finite representation equal them exactly." Other comparison functions, as well as iszero and isone, have similar issues in general.

Assuming it is agreed that even rational values can be represented by an AbstractIrrational, one path forward is to narrow the definition of some of the above methods to be only for Irrational rather than AbstractIrrational. And in any case, some documentation/guidelines on what methods somebody should overload for a new AbstractIrrational type would be very helpful.

@garrison garrison added the domain:maths Mathematical functions label Mar 21, 2018
@stevengj
Copy link
Member

stevengj commented Mar 21, 2018

On the one hand, it seems like unnecessary purism to restrict AbstractIrrational to irrational numbers when looking at a family of values like square roots or multiples of π. There's no technical requirement on the values except that they must have methods defined to convert them to floating-point types.

However, I think that the default methods like == should stand as-is for AbstractIrrational, since the main purpose of this type is to represent irrational values. Subtypes that extend it to exactly representable rational values can always overload ==.

@stevengj
Copy link
Member

On the other hand, it is also perfectly reasonable to give an error for a √4 "irrational" value. The main purpose of irrational types is to enter irrational constants, and there is no reason anyone should ever need this for √4, since they can always use the exact literal 2.

@stevengj stevengj added the domain:docs This change adds or pertains to documentation label Mar 21, 2018
@stevengj
Copy link
Member

stevengj commented Mar 21, 2018

My understanding is that most irrational types should define:

==(::MyIrrational, ::MyIrrational)
hash(x::MyIrrational, h::UInt)
convert(::Type{F}, x::MyIrrational) # for at least F in {BigFloat,Float32,Float64}

and probably also show(io::IO, x::MyIrrational) for pretty-printing.

If MyIrrational can also represent rational values (which I wouldn't recommend in general, but it may be useful in special cases), then it should also implement isinteger, iszero, isone, == with Real.

@Jutho
Copy link
Contributor

Jutho commented Mar 21, 2018

Thanks, that's a useful definition of the interface. My use case is returning exact Clebsch Gordan coefficients, which is the square root of a rational number. I thought AbstractIrrational was a good starting point to inherit from, but if you think it is better that only truly irrational numbers should be a subtype, then that is fine with me. But it seems that what I need then amounts to defining a whole new Number type which mimics (=copies) a lot of the AbstractIrrational functionality.

@stevengj
Copy link
Member

stevengj commented Mar 21, 2018

No, in that use-case I think AbstractIrrational makes sense, since you need to return a single type in all cases for type-stability, but in that case you may need to overload four more methods.

@garrison
Copy link
Sponsor Member Author

If MyIrrational can also represent rational values (which I wouldn't recommend in general, but it may be useful in special cases)

I think it's best to focus concretely on the intended use cases. So far, those mentioned mentioned include rational multiples or roots of an Irrational. I can think of other logical extensions, such as linear combinations of these as well. In each one of these cases, it is possible for the quantity to represent a value that is actually rational. To me, these actually seem like the most common use cases; in fact, I can't think of any reason to use AbstractIrrational (but not Irrational itself) for a type that will always be irrational. Can you think of one?

Because I believe types that can also represent rational values will be the most common use case for AbstractIrrational, I don't think it makes sense to say that this is not recommended in general.

Other thoughts

It seems we agree that it would be undesirable for AbstractIrrational to be forced to error instead of representing a rational value. So I will focus here on implications of allowing this.

My main concern can be summarized as follows: I believe many of the methods should not have been generalized to AbstractIrrational in #24245.

  • For instance, make Irrational <: AbstractIrrational #24245 assumes the equivalency of < and <= for AbstractIrrationals, but this is not true in general.
  • As mentioned, the default implementations of isinteger, isone, and iszero are not correct in general for such types.
  • Much more important, the fallback ==(::AbstractIrrational, ::AbstractIrrational) = false is not true in general, and there is no good way of preventing this fallback from being called. For instance, if one package implements square roots of rationals while another implements multiples of pi, both types can represent the value 0, but the authors of these types will have no way of providing the correct behavior when one is compared to the other, without the packages knowing about the other type. Comparisons of such types will get hairy very quickly when there is a fallback method that can potentially be wrong. One thing this suggests is that all such irrationals should perhaps be in the same package, but even better IMO would be to eliminate these fallback methods that are not generally true, and allow them to be implemented explicitly (else MethodError) rather than with a fallback method that can be wrong. Yes, it will mean that those implementing such types will need to do a bit more work, but I expect this will happen rather infrequently, and I think correctness is worth it.

@garrison
Copy link
Sponsor Member Author

Ping @stevengj (any thoughts on my post above?)

@stevengj
Copy link
Member

I think that if you subtype AbstractIrrational to implement a value that is not irrational, it is not unreasonable that you may have to provide specialized versions of a few more methods like isinteger. For ==, a conservative fallback of false seems much more useful to me than a MethodError — false is the default return value of == anyway for objects of different types.

@garrison
Copy link
Sponsor Member Author

I think that if you subtype AbstractIrrational to implement a value that is not irrational, it is not unreasonable that you may have to provide specialized versions of a few more methods like isinteger.

On this we agree. However, the some of the fallback methods are incorrect for all potential use cases mentioned to date (other than for Irrational itself), and thus they will need to be specialized in all cases considered so far. Why, then, should these fallback implementations exist?

For ==, a conservative fallback of false seems much more useful to me than a MethodError — false is the default return value of == anyway for objects of different types.

Very good point.

@oscardssmith
Copy link
Member

Could we make a new type called SymbolicNumber or something similar to replace AbstractIrrational? There is no reason to restrict symbolic computation to irrational numbers. One clear use case would be something like SymbolicInt which would represent an integer symbolically allowing the compiler to do more complex optimizations.

@KristofferC
Copy link
Sponsor Member

I would prefer keeping the interface here as small as possible and defer more elaborate symbolic manipulations to packages.

@oscardssmith
Copy link
Member

The reason to want a SymbolicNumber object is that it is what anything of the others should be subtypes of which can not happen if it is not in base.

@Jutho
Copy link
Contributor

Jutho commented Jul 25, 2019

I agree with @garrison that I would think that most useful subtypes of AbstractIrrational could include numbers that are not irrational. Irrational{:...} is special since it just represents a number of isolated constants. But for any type that represents a complete family of numbers, there will likely be subsets which are simpler, i.e. just like Rational can represent integers and Complex can represent real numbers. Suppose there would be an AbstractComplex of which both Base's Complex and some PolarRepresentation would be subtypes, then, just to make a comparison, I don't think a ==(::AbstractComplex, ::Real) = false fallback would be very useful.

That's just my 2 cents, I don't actually have a strong opinion as long as it is clear what needs to be implemented.

@oscardssmith
Copy link
Member

oscardssmith commented Jul 26, 2019

I would like to resay my point that if we want to allow these numbers to sure rationals, we really should not have the type be named AbstractIrational. We can still fix this non breaking fassion by making AbstractIrational a subtype of a new symbolic type. Having 2 be a valid irrational, seems like a bad idea

ViralBShah pushed a commit that referenced this issue Apr 30, 2020
* document AbstractIrrational interface

Fixes #26550.

* note hash
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain:docs This change adds or pertains to documentation domain:maths Mathematical functions
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants