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.jlFiniteDifference
: finite difference approximation using FiniteDiff.jlUserDefined
: 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.@autodiff
— Macro@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