Constraint Interface
All constraints inherit from AbstractConstraint
.
TrajectoryOptimization.AbstractConstraint
— TypeAbstractConstraint <: RobotDynamics.AbstractFunction
Abstract vector-valued constraint for a trajectory optimization problem. Defined using the AbstractFunction
interface from RobotDynamics.jl which allows for a flexible API for using in-place or out-of-place evaluation, multiple methods for evaluating Jacobians, etc.
The "sense" of a constraint is specified by defining the ConstraintSense
trait on the type, accessed via TrajectoryOptimization.sense
, which defines whether the constraint is an equality, inequality, or conic constraint.
Interface: Any constraint must implement the following interface:
n = RobotDynamics.state_dim(::MyCon)
m = RobotDynamics.control_dim(::MyCon)
p = RobotDynamics.output_dim(::MyCon)
TrajectoryOptimization.sense(::MyCon)::ConstraintSense
c = RobotDynamics.evaluate(::MyCon, x, u)
RobotDynamics.evaluate!(::MyCon, c, x, u)
Constraints
All constraints are categorized into the following type tree:
AbstractConstraint
↓
StageConstraint
↙ ↘
StageConstraint ControlConstraint
The state and control dimensions (where applicable) can be queried using state_dim(::AbstractConstraint)
and control_dim(::AbstractConstraint)
. The dimensions of a constraint can be verified using check_dims
.
The width of the Jacobian is specified by whether the constraint inherits from StageConstraint
, StateConstraint
, or ControlConstraint
. It can be generated automatically using gen_jacobian
.
Evaluation
All constraints can be evaluated by using one of these methods using a KnotPoint
:
RobotDynamics.evaluate(::MyCon, z::AbstractKnotPoint)
RobotDynamics.evaluate!(::MyCon, c, z::AbstractKnotPoint)
Alternatively, a StageConstraint
can be evaluated using
RobotDynamics.evaluate(::MyCon, x, u)
RobotDynamics.evaluate!(::MyCon, c, x, u)
a StateConstraint
can be evaluated using
RobotDynamics.evaluate(::MyCon, x)
RobotDynamics.evaluate!(::MyCon, c, x)
and a ControlConstraint can be evaluated using
RobotDynamics.evaluate(::MyCon, u)
RobotDynamics.evaluate!(::MyCon, c, u)
Jacobian Evaluation
The Jacobian for all types of constraints can be evaluated using
RobotDynamics.jacobian!(::FunctionSignature, ::DiffMethod, ::MyCon, J, c, z)
where z
is an AbstractKnotPoint
. To define custom Jacobians, one of the following methods must be defined:
RobotDynamics.jacobian!(::MyCon, J, c, z) # All constraints
RobotDynamics.jacobian!(::MyCon, J, c, x, u) # StageConstraint only
RobotDynamics.jacobian!(::MyCon, J, c, x) # StateConstraint only
RobotDynamics.jacobian!(::MyCon, J, c, u) # ControlConstraint only
The most specific methods are preferred over those that accept only an AbstractKnotPoint
.
Constraint Sense
TrajectoryOptimization.jl assumes equality constraints are of the form , and that all other constraints are constrained to lie with a specified cone. This is referred to as the ConstraintSense
. The following are currently implemented:
TrajectoryOptimization.ConstraintSense
— Type" ConstraintSense
Specifies the type of the constraint, or in which convex cone it is to be enforced. Valid subtypes are Equality
⟺ ZeroCone
, Inequality
⟺ NegativeOrthant
, and SecondOrderCone
.
The sense of a constraint can be queried using sense(::AbstractConstraint)
The following operations are supported:
Base.in(::ConstraintSense, x::StaticVector)
. i.e.x ∈ cone
projection(::ConstraintSense, x::StaticVector)
∇projection(::ConstraintSense, J, x::StaticVector)
∇²projection(::ConstraintSense, J, x::StaticVector, b::StaticVector)
dualcone(::ConstraintSense)
TrajectoryOptimization.Equality
— TypeEquality
Equality constraints of the form . Equivalent to ZeroCone
.
TrajectoryOptimization.Inequality
— TypeInequality
Inequality constraints of the form . Equivalent to NegativeOrthant
.
TrajectoryOptimization.ZeroCone
— TypeZeroCone
The cone whose valid set is only the origin. Equivalent to Equality
.
TrajectoryOptimization.NegativeOrthant
— TypeNegativeOrthant
Inequality constraints of the form . Equivalent to Inequality
.
TrajectoryOptimization.SecondOrderCone
— TypeSecondOrderCone
The second-order cone is defined as where and are both part of the cone. TrajectoryOptimization assumes the scalar part is the last element in the vector.
Evaluating Constraints
The following methods are used to evaluate a constraint:
TrajectoryOptimization.evaluate_constraints!
— Functionevaluate_constraints!(sig, con, vals, Z, inds)
Evaluate the constraint con
using the sig
FunctionSignature
for the time steps in inds
along trajectory Z
, storing the output in vals
.
The vals
argument should be a vector with the same length as inds
, where each element is a mutable vector of length RD.output_dim(con)
.
TrajectoryOptimization.constraint_jacobians!
— Functionconstraint_jacobians!(sig, diffmethod, con, vals, Z, inds)
Evaluate the constraint con
using the sig
FunctionSignature
for the time steps in inds
along trajectory Z
, storing the output in vals
.
The vals
argument should be a vector with the same length as inds
, where each element is a mutable vector of length RD.output_dim(con)
.
Methods
The following methods are defined for all AbstractConstraint
s
TrajectoryOptimization.sense
— FunctionGet constraint sense (Inequality vs Equality)
TrajectoryOptimization.upper_bound
— Functionupper_bound(constraint)
Upper bound of the constraint, as a vector. This is zero for inequality and equality constraints, and +Inf for SecondOrderCone
.
TrajectoryOptimization.lower_bound
— Functionlower_bound(constraint)
Lower bound of the constraint, as a vector. This is zero for equality constraints and -Inf for SecondOrderCone
and inequality constraints.
TrajectoryOptimization.is_bound
— Functionis_bound(constraints)
Returns true if the constraint can be represeted as either
or
i.e. simple bound constraints on the states and controls.
TrajectoryOptimization.check_dims
— Functioncheck_dims(con, n, m)
Check whether the constraint is consistent with the specified state and control dimensions.
Adding a New Constraint
See interface description in documentation for AbstractConstraint
. The interface allows for a lot of flexibility, but let's do a simple example. Let's say we have a 2-norm constraint on the controls at each time step, e.g. . We can do this with just a few lines of code:
using TrajectoryOptimization
using RobotDynamics
using ForwardDiff
using FiniteDiff
RobotDynamics.@autodiff struct ControlNorm{T} <: TrajectoryOptimization.ControlConstraint
m::Int
val::T
function ControlNorm(m::Int, val::T) where T
@assert val ≥ 0 "Value must be greater than or equal to zero"
new{T}(m,val,sense,inds)
end
end
RobotDynamics.control_dim(con::ControlNorm) = con.m
RobotDynamics.output_dim(::ControlNorm) = 1
TrajectoryOptimization.sense(::ControlNorm) = Inequality()
RobotDynamics.evaluate(con::ControlNorm, u) = SA[norm(u) - con.a] # needs to be a vector output
RobotDynamics.evaluate!(con::ControlNorm, c, u) = SA[norm(u) - con.a]
function RobotDynamics.jacobian!(con::ControlNorm, J, c, u) # optional
J[1,:] .= u'/norm(u)
end
Importantly, note that the inheritance specifies the constraint applies only to individual controls.
Constraint Types
The ConstraintType
defines the whether the constraint is a function of just the state, control, or both the state and control. This automatically defines the RobotDynamics.FunctionInputs
trait for the constraint.
TrajectoryOptimization.StageConstraint
— TypeOnly a function of states and controls at a single knotpoint
TrajectoryOptimization.StateConstraint
— TypeOnly a function of states at a single knotpoint
TrajectoryOptimization.ControlConstraint
— TypeOnly a function of controls at a single knotpoint