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

Fallback math methods for ::Real are likely to call themselves #26552

Closed
tkoolen opened this issue Mar 21, 2018 · 10 comments
Closed

Fallback math methods for ::Real are likely to call themselves #26552

tkoolen opened this issue Mar 21, 2018 · 10 comments
Labels
maths Mathematical functions

Comments

@tkoolen
Copy link
Contributor

tkoolen commented Mar 21, 2018

On 0.6.2:

julia> using ForwardDiff

julia> ForwardDiff.derivative(sinpi, 1.)
0.0

on b571283:

julia> using ForwardDiff

julia> ForwardDiff.derivative(sinpi, 1.)
ERROR: StackOverflowError:
Stacktrace:
 [1] sinpi(::ForwardDiff.Dual{ForwardDiff.Tag{typeof(sinpi),Float64},Float64,1}) at ./special/trig.jl:865 (repeats 80000 times)

This is because of

sinpi(x::Real) = sinpi(float(x))

which seems unfortunate. Similarly, log1p required a workaround in DiffRules. I wholeheartedly agree with JuliaDiff/ForwardDiff.jl#261 (comment):

Looks like Base.Math.JuliaLibm.log1p(x::Real) just calls Base.Math.JuliaLibm.log1p(float(x)), which basically guarantees infinite recursion in the general case

log and cospi also have this issue. Maybe others as well?

@StefanKarpinski
Copy link
Member

Do you have a different way to express this in mind that wouldn't have this issue?

@tkoolen
Copy link
Contributor Author

tkoolen commented Mar 21, 2018

How about turning

sinpi(x::T) where T<:AbstractFloat

into

_sinpi(x::T) where T<:AbstractFloat

and have

sinpi(x::Real) = _sinpi(float(x)) 

@mbauman
Copy link
Member

mbauman commented Mar 21, 2018

This is similarly handled in other places with a generic AbstractFloat specialization.

@tkoolen
Copy link
Contributor Author

tkoolen commented Mar 21, 2018

That assumes that float always returns an AbstractFloat. ForwardDiff.Dual doesn't adhere to that, and it probably shouldn't be an AbstractFloat, since the backing type can also be non-AbstractFloat.

@martinholters
Copy link
Member

We could make that assumption more explicit by using convert(AbstractFloat, x) instead of float (x).

@tkoolen
Copy link
Contributor Author

tkoolen commented Mar 21, 2018

That would certainly be an improvement, MethodError is better than StackOverflowError. But the sinpi/log1p/... changes from 0.6.2 to 0.7 would still be breaking, and it's not like the old implementation returned wrong results (as far as I understand). Restoring the old behavior using something like #26552 (comment) seems to me like it would save a lot of hassle for package authors.

@chriscoey
Copy link

any path forward on this? I am struggling with this issue: JuliaDiff/ForwardDiff.jl#324

@tpapp
Copy link
Contributor

tpapp commented Nov 7, 2019

I think that the original intention for Base.float was to provide a fallback to already defined special functions. The implicit assumption in various instances of

my_calculation(x::Real) = my_calculation(float(x))

is that float returns something my_calculation can deal with.

With the combination of user-defined types and functions, this is very difficult to enforce or even explicitly specify. Eg one could require that float(x)::AbstractFloat, but as a package can just define a new <:AbstractFloat type, and a my_calculation in Base or another package may not be equipped to deal with that.

In retrospect, I think

  1. float should have been named Base.basefloat,
  2. with an explicit requirement to return only BigFloat, Float16, Float32, Float64 (ie subtypes(AbstractFloat) in Base,
  3. perhaps not exported.

(1) and (3) are breaking suggestions, but (2) can be addressed in the docstring of float for now.

Packages should not define a method for float unless they can comply with (2). It is tempting to expect an automatic fallback from it, but it almost always violates implicit assumptions and leads to bugs like the above.

@vtjnash
Copy link
Member

vtjnash commented Mar 15, 2021

cf. possible fix #25292 (but ref possible issue with that #30136)

@oscardssmith
Copy link
Member

Yeah. At this point, I think we've fixed this for basically everything:

for f in (:sin, :cos, :tan, :asin, :atan, :acos,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
maths Mathematical functions
Projects
None yet
Development

No branches or pull requests

9 participants