Does ForwardDiff work with Python functions?

Hi all, trying to use ForwardDiff on a Python function and getting errors. Here is a MWE

using PyCall
np = pyimport("numpy")

function npsin(x)
    np.sin(x)
end

function dnpsin(x)
    ForwardDiff.derivative(npsin, x)
end

dnpsin(π/2) gives following error

MethodError: no method matching Float64(::ForwardDiff.Dual{ForwardDiff.Tag{typeof(npsin),Float64},Float64,1})
Closest candidates are:
  Float64(::Real, ::RoundingMode) where T<:AbstractFloat at rounding.jl:200
  Float64(::T) where T<:Number at boot.jl:716
  Float64(::Irrational{:mad_constant}) at irrationals.jl:189
  ...

Stacktrace:
 [1] convert(::Type{Float64}, ::ForwardDiff.Dual{ForwardDiff.Tag{typeof(npsin),Float64},Float64,1}) at ./number.jl:7
 [2] cconvert(::Type{T} where T, ::ForwardDiff.Dual{ForwardDiff.Tag{typeof(npsin),Float64},Float64,1}) at ./essentials.jl:388
 [3] macro expansion at /home/arif/.julia/packages/PyCall/tqyST/src/exception.jl:95 [inlined]
 [4] PyObject(::ForwardDiff.Dual{ForwardDiff.Tag{typeof(npsin),Float64},Float64,1}) at /home/arif/.julia/packages/PyCall/tqyST/src/conversions.jl:23
 [5] _pycall!(::PyObject, ::PyObject, ::Tuple{ForwardDiff.Dual{ForwardDiff.Tag{typeof(npsin),Float64},Float64,1}}, ::Int64, ::Ptr{Nothing}) at /home/arif/.julia/packages/PyCall/tqyST/src/pyfncall.jl:24
 [6] _pycall! at /home/arif/.julia/packages/PyCall/tqyST/src/pyfncall.jl:11 [inlined]
 [7] #_#116 at /home/arif/.julia/packages/PyCall/tqyST/src/pyfncall.jl:86 [inlined]
 [8] (::PyObject)(::ForwardDiff.Dual{ForwardDiff.Tag{typeof(npsin),Float64},Float64,1}) at /home/arif/.julia/packages/PyCall/tqyST/src/pyfncall.jl:86
 [9] npsin(::ForwardDiff.Dual{ForwardDiff.Tag{typeof(npsin),Float64},Float64,1}) at ./In[181]:2
 [10] derivative at /home/arif/.julia/packages/ForwardDiff/sqhTO/src/derivative.jl:14 [inlined]
 [11] dnpsin(::Float64) at ./In[185]:2
 [12] top-level scope at In[187]:1
 [13] include_string(::Function, ::Module, ::String, ::String) at ./loading.jl:1091

From the ForwardDiff docs limitations page:

This is due to the way ForwardDiff works by passing a dual number to the function in order to propagate and store derivative information. This won’t be possible for Python functions as they don’t know how to handle being passed a dual number.

Specifically, what I think is happening in the MWE is Julia wants to call the Python function with its arguments (which in this case is a Dual) and so it wants the inputs to be Floats. It calls Float on the input Dual so Python can use np.sin and then Julia realises that you can’t convert a Dual to a Float and spits out the error.

4 Likes