Differentiation API

RobotDynamics allows users to define different methods for obtaining derivatives of their functions. This is accomplished via the DiffMethod trait, which by default has three options:

  • ForwardAD: forward-mode automatic differentiation using ForwardDiff.jl
  • FiniteDifference: finite difference approximation using FiniteDiff.jl
  • UserDefined: analytical function defined by the user.

Almost every Jacobian is queried using the same form:

jacobian!(sig, diff, fun, J, y, z)

where sig is a FunctionSignature, diff is a DiffMethod, fun is an AbstractFunction, J is the Jacobian, y is a vector the size of the output of the function, and z is an [AbstractKnotPoint]. Users are free to add more DiffMethod types to dispatch on their own differentiation method.

By default, no differentiation methods are provided for an AbstractFunction, allowing the user to choose what methods they want defined, and to allow customization of the particular method to their function. However, we do provide the following macro to provide efficient implementations for ForwardAD and FiniteDifference:

RobotDynamics.@autodiffMacro
@autodef struct_def

Sets up methods to automatically evaluate the Jacobian of the function. By default, it defines these methods for a new type NewFun:

jacobian!(::InPlace, ::ForwardAD, fun::NewFun, J, y, z)
jacobian!(::InPlace, ::FiniteDifference, fun::NewFun, J, y, z)
jacobian!(::StaticReturn, ::ForwardAD, fun::NewFun, J, y, z)
jacobian!(::StaticReturn, ::FiniteDifference, fun::NewFun, J, y, z)

These methods are all optimized to be non-allocating.

The method will modify the type definition, adding fields and type parameters to set them up for efficient evaluation of the jacobians. The changes are usually transparent to the user.

If an inner constructor is provided, the signature will be unchanged, but will be modified to initialize the new fields and provide the new type parameters.

Limitations

  • RobotDynamics must be defined the local module (cannot be hidden by an alias)
  • ForwardDiff and FiniteDiff must both be defined in the local module, since the struct is modified to include caches from these modules.
  • If the type has type parameters and no inner constructor, the type parameters must be explicitly provided when constructing the object. No automatic inference will be done. It's best to explicitly provide an inner constructor with the desired behavior.

Examples

using RobotDynamics, ForwardDiff, FiniteDiff
const RD = RobotDynamics
RD.@autodiff struct MyFun <: RD.AbstractFunction end

will define

struct MyFun{CH} <: RobotDynamics.AbstractFunction
    cfg::ForwardDiff.JacobianConfig{Nothing, Float64, CH, Tuple{Vector{ForwardDiff.Dual{Nothing, Float64, CH}}, Vector{(ForwardDiff).Dual{Nothing, Float64, CH}}}}
    cache::FiniteDiff.JacobianCache{Vector{Float64}, Vector{Float64}, Vector{Float64}, UnitRange{Int64}, Nothing, Val{:forward}(), Float64}
    function MyFun()
        model = new{0}()
        _n = input_dim(model)
        _m = output_dim(model)
        cfg = ForwardDiff.JacobianConfig(nothing, zeros(Float64, _m), zeros(Float64, _n))
        model = new{length(cfg).seeds}(cfg)
        _n = input_dim(model)
        _m = output_dim(model)
        cache = FiniteDiff.JacobianCache(zeros(Float64, _n), zeros(Float64, _m))
        model = new{length(cfg).seeds}(cfg, cache)
    end
end

function RobotDynamics.jacobian!(::InPlace, ::ForwardAD, fun::MyFun, J, y, z)
    f_aug!(y, z_) = begin
        RobotDynamics.evaluate!(fun, y, RobotDynamics.setinputs!(z, z_))
        ForwardDiff.jacobian!(J, f_aug!, y, z, fun.cfg)
    end
end
...  # other Jacobian methods