Search code examples
juliaautomatic-differentiation

Restricting function signatures while using ForwardDiff in Julia


I am trying to use ForwardDiff in a library where almost all functions are restricted to only take in Floats. I want to generalise these function signatures so that ForwardDiff can be used while still being restrictive enough so functions only take numeric values and not things like Dates. I have alot of functions with the same name but different types (ie functions that take in "time" as either a float or a Date with the same function name) and do not want to remove the type qualifiers throughout.

Minimal Working Example

using ForwardDiff
x = [1.0, 2.0, 3.0, 4.0 ,5.0]
typeof(x) # Array{Float64,1}
function G(x::Array{Real,1})
    return sum(exp.(x))
end
function grad_F(x::Array)
  return ForwardDiff.gradient(G, x)
end
G(x) # Method Error
grad_F(x) # Method error

function G(x::Array{Float64,1})
    return sum(exp.(x))
end
G(x) # This works
grad_F(x) # This has a method error

function G(x)
    return sum(exp.(x))
end
G(x) # This works
grad_F(x) # This works
# But now I cannot restrict the function G to only take numeric arrays and not for instance arrays of Dates.

Is there are a way to restict functions to only take numeric values (Ints and Floats) and whatever dual number structs that ForwardDiff uses but not allow Symbols, Dates, etc.


Solution

  • ForwardDiff.Dual is a subtype of the abstract type Real. The issue you have, however, is that Julia's type parameters are invariant, not covariant. The following, then, returns false.

    # check if `Array{Float64, 1}` is a subtype of `Array{Real, 1}`
    julia> Array{Float64, 1} <: Array{Real, 1}
    false
    

    That makes your function definition

    function G(x::Array{Real,1})
        return sum(exp.(x))
    end
    

    incorrect (not suitable for your use). That's why you get the following error.

    julia> G(x)
    ERROR: MethodError: no method matching G(::Array{Float64,1})
    

    The correct definition should rather be

    function G(x::Array{<:Real,1})
        return sum(exp.(x))
    end
    

    or if you somehow need an easy access to the concrete element type of the array

     function G(x::Array{T,1}) where {T<:Real}
         return sum(exp.(x))
     end
    

    The same goes for your grad_F function.

    You might find it useful to read the relevant section of the Julia documentation for types.


    You might also want to type annotate your functions for AbstractArray{<:Real,1} type rather than Array{<:Real, 1} so that your functions can work other types of arrays, like StaticArrays, OffsetArrays etc., without a need for redefinitions.