JuliaPhysics / Measurements.jl

Error propagation calculator and library for physical measurements. It supports real and complex numbers with uncertainty, arbitrary precision calculations, operations with arrays, and numerical integration.

Home Page:https://juliaphysics.github.io/Measurements.jl/stable/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Interplay with ForwardDiff - defining *(::Measurement, ::ForwardDiff.Dual)

touste opened this issue · comments

Hi, I was trying to plug uncertainty into a simple calculation of mechanical stress, which is obtained by differentiating an energy potential with ForwardDiff.
Unfortunately it leads to an error:


using LinearAlgebra
using ForwardDiff
using Measurements

# The energy potential
function Ψ_holzapfel(F, m₀, C10, k1, k2, κ)
	C = F'*F
	I1 = tr(C)
	I4 = m₀'*C*m₀
	Ef = κ*(I1-3) + (1-3κ)*(I4-1)
    return C10*(I1-3) + k1/(2*k2) * (exp(k2*Ef^2) - 1)
end

# Deformation gradient
λ = 1.1
F = Diagonal([λ, 1/sqrt(λ), 1/sqrt(λ)])

# Parameters
m₀ = [1,0,0]
C10 = 1.0
k1 = 1.0
k2 = 1.0
κ = 0.2

# Derivative of energy potential wrt deformation gradient
dΨdF(F) = ForwardDiff.gradient(F->Ψ_holzapfel(F, m₀, C10, k1, k2, κ), F)

# Stress - this works fine
σ = dΨdF(F) * F'

# Parameters with uncertainty
C10 = 1.0 ± 0.1
k1 = 1.0 ± 0.1
k2 = 1.0 ± 0.1
κ = 0.2 ± 0.05

# This throws an error
σ = dΨdF(F) * F'

The error is:

ERROR: MethodError: *(::Measurement{Float64}, ::ForwardDiff.Dual{ForwardDiff.Tag{var"#7#8", Float64}, Float64, 9}) is ambiguous. Candidates:
  *(a::Measurement, b::Real) in Measurements at /home/me/.julia/packages/Measurements/7lfsB/src/math.jl:194
  *(x::AbstractFloat, y::ForwardDiff.Dual{Ty, V, N} where N where V) where Ty in ForwardDiff at /home/me/.julia/packages/ForwardDiff/sdToQ/src/dual.jl:140
  *(x::Real, y::ForwardDiff.Dual{Ty, V, N} where N where V) where Ty in ForwardDiff at /home/me/.julia/packages/ForwardDiff/sdToQ/src/dual.jl:140
Possible fix, define
  *(::Measurement, ::ForwardDiff.Dual{Ty, V, N} where N where V) where Ty

Should this method *(::Measurement{Float64}, ::ForwardDiff.Dual) be defined in the Measurements package?

I tried to define it:

import Base.*
*(a::Measurement, b::ForwardDiff.Dual{Ty, V, N} where N where V) where Ty = Measurements.result(a.val*b, b, a)

but this leads to further errors:
ERROR: MethodError: no method matching Float64(::ForwardDiff.Dual{ForwardDiff.Tag{var"#7#8", Float64}, Float64, 9})

Thanks

Following up on this: I ended up defining the following rules:

import Base.*, Base.+

function *(a::ForwardDiff.Dual{Td,V,N} where {N,V}, b::Measurements.Measurement) where Td 
    ForwardDiff.Dual{Td}(b*a.value, (b*p for p in a.partials)...)
end
*(a::Measurements.Measurement, b::ForwardDiff.Dual) = b*a

function +(a::ForwardDiff.Dual{Td,V,N} where {N,V}, b::Measurements.Measurement) where Td 
    ForwardDiff.Dual{Td}(b+a.value, a.partials)
end
+(a::Measurements.Measurement, b::ForwardDiff.Dual) = b+a

which work well and were checked against Calculus' finite difference.

On second thought, I'm not sure whether this has its place in this package, as it would require a dependency on ForwardDiff. However, I think it's still a valuable addition as it allows interfacing with it. Perhaps make an additional package similar to DiffRules?
Note that these rules are actually already defined in ForwardDiff for the Real type, but because the Dual type is also a Real this causes ambiguity.

I didn't have the time to look into this, yet, but I'm pretty sure that this issue is due to the fact that Measurement <: AbstractFloat and a limitation of ForwardDiff.jl:

  • The target function must be written generically enough to accept numbers of type T<:Real as input (or arrays of these numbers). The function doesn't require a specific type signature, as long as the type signature is generic enough to avoid breaking this rule. This also means that any storage assigned used within the function must be generic as well (see this comment for an example).

which honestly is a rather severe limitation.

I don't think we can do anything here (apart from changing subtyping of the Measurement type, which I'm quite opposed to) and doing an ad-hoc fix for all possible AD libraries doesn't look a good solution to me. Note that the documentation shows that other libraries that don't have such limitations, like Autograd.jl, can do AD out-of-the-box with Measurements.jl

Thanks for the reply. I understand the limitation of ForwardDiff.jl, however Autograd.jl also has several of its own.
I'll keep the fix for now, at least it's working with it!