JuliaDiff / FiniteDifferences.jl

High accuracy derivatives, estimated via numerical finite differences (formerly FDM.jl)
MIT License
298 stars 25 forks source link

Type Conversion Error with Measurements #211

Open lstagner opened 1 year ago

lstagner commented 1 year ago
using FiniteDifferences
using Measurements

f(x) = cos(x + (1.0 ± 0.1)*sin(x))
m = central_fdm(5,1)

m(f, 0.0)

MethodError: no method matching Float64(::Measurement{Float64})
Closest candidates are:
  (::Type{T})(::Real, ::RoundingMode) where T<:AbstractFloat at rounding.jl:200
  (::Type{T})(::T) where T<:Number at boot.jl:772
  (::Type{T})(::AbstractChar) where T<:Union{AbstractChar, Number} at char.jl:50
  ...

Stacktrace:
 [1] _eval_function(m::FiniteDifferences.AdaptedFiniteDifferenceMethod{5, 1, FiniteDifferences.UnadaptedFiniteDifferenceMethod{7, 5}}, f::typeof(f), x::Float64, step::Measurement{Float64})
   @ FiniteDifferences ~/.julia/packages/FiniteDifferences/8zye5/src/methods.jl:249
 [2] (::FiniteDifferences.AdaptedFiniteDifferenceMethod{5, 1, FiniteDifferences.UnadaptedFiniteDifferenceMethod{7, 5}})(f::typeof(f), x::Float64, step::Measurement{Float64})
   @ FiniteDifferences ~/.julia/packages/FiniteDifferences/8zye5/src/methods.jl:240
 [3] (::FiniteDifferences.AdaptedFiniteDifferenceMethod{5, 1, FiniteDifferences.UnadaptedFiniteDifferenceMethod{7, 5}})(f::typeof(f), x::Float64)
   @ FiniteDifferences ~/.julia/packages/FiniteDifferences/8zye5/src/methods.jl:194
 [4] top-level scope
   @ In[6]:7
 [5] eval
   @ ./boot.jl:368 [inlined]
 [6] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
   @ Base ./loading.jl:1428
oxinabox commented 1 year ago

Quick workaround: turn-off adaption.

julia> m2 = central_fdm(5, 1; adapt=0);

julia> m2(f, 0.0)
0.0 ± 0.00014

There are a few places in the code, namely

https://github.com/JuliaDiff/FiniteDifferences.jl/blob/93cd547363ab182ea42ddff7c112824d147d923c/src/methods.jl#L246-L250 and https://github.com/JuliaDiff/FiniteDifferences.jl/blob/93cd547363ab182ea42ddff7c112824d147d923c/src/methods.jl#L263

where we use T(step) to convert the step into the same type as the primal value x. This was done when chasing down allocations from type-instabilities. Mathematically, I don't think this is required. We just need x+step to be the same type as x. However, I don't thing step::Measurement{Float64} satisfies that: x + step gives a result that is a Measurement{Float64}.

But the question then is where is our step coming from? That is coming out of FiniteDifferences.estimate_step

julia> FiniteDifferences.estimate_step(m, f, 0.0)
(0.00097 ± 2500.0, 4.3e-13 ± 1.1e-6)

Something is off somewhere in the adaption logic of https://github.com/JuliaDiff/FiniteDifferences.jl/blob/93cd547363ab182ea42ddff7c112824d147d923c/src/methods.jl#L362-L426

because it is yielding a step that is of the same type as f(x) rather than the type of x. @wesselb might have ideas but otherwise this is going to take a fair bit of digging to uncover.