Solver Interface

Solvers are currently organized into the following type tree:

The solver options type is a lightweight container for all of the options the user can specify, such as tolerance values, printing verbosity (highly recommended), Boolean flags, etc. We highly suggest using Parameters.jl to create this and easily specify the default options. All solver options should be mutable (e.g. mutable struct NewSolverOptions{T} <: AbstractSolverOptions{T})

The solver type, on the other hand, is meant to contain all of the variables needed for the solve, including the model, objective, constraints, and other information originally in the Problem. This information is "duplicated" in the solver since oftentimes the solver with perform modifications to these when setting up the solve. For example, the AugmentedLagrangianSolver creates an ALObjective and uses that as it's objective instead. Similarly, ALTRO may convert the model to an InfeasibleModel to leverage an initial state trajectory. Therefore, once the solver is created, the problem is solved by simply calling solve!(solver), which then runs the optimization.

Defining a New Solver

For creating a new solver, e.g. NewSolver, the user must define two new types:

  • NewSolverOptions{T} <: AbstractSolverOptions{T}
  • NewSolver{T} <: AbstractSolver{T}

Below we list the methods needed to implement the different solver interfaces, along with a list of inherited methods (that can be overloaded as needed). The docstrings for these functions are listed in more detail down below.

Unconstrained Solvers

Needed methods:

model = get_model(::NewSolver)
obj = get_objective(::NewSolver)
Z = get_trajectory(::NewSolver)
n,m,N = Base.size(::NewSolver)
x0 = get_initial_state(::NewSolver)
solve!(::NewSolver)

Needed fields (these will likely be replaced by getters in the near future):

  • opts - instance of AbstractSolverOptions
  • stats - mutable struct containing statistics on the solve

Inherited methods:

states(::NewSolver)
controls(::NewSolver)
initial_states!(::NewSolver, X0)
initial_controls!(::NewSolver, U0)
initial_trajectory!(::NewSolver, Z::Traj)
cost(::NewSolver, Z=get_trajectory(::NewSolver))
rollout!(::NewSolver)

Constrained Solvers

Needed methods (in addition to those for Unconstrained Solvers):

get_constraints(::NewSolver)

Inherited methods (in addition to those for Unconstrained Solvers):

num_constraints(::NewSolver)
max_violation(::NewSolver, Z=get_trajectory(::NewSolver))
update_constraints!(::NewSolver, Z=get_trajectory(::NewSolver))
update_active_set!(::NewSolver, Z=get_trajectory(::NewSolver))

Direct Solvers

Currently, direct solvers solve the problem by forming a large, sparse matrix by concatenating the states and controls for all time steps. They need to generate a list of indices that map the constraints to their location in the concatenated array of constraint values, which can be done using gen_con_inds. This must be stored as the con_inds field in the DirectSolver (this will be replaced by a getter method in the near future). With this, all DirectSolvers inherit the following methods for copying values to and from the large, concatenated vectors and matrices:

copy_constraints!(d, ::DirectSolver)
copy_active_set!(a, ::DirectSolver)
copy_jacobians!(D, ::DirectSolver)

Unconstrained Solvers

TrajectoryOptimization.AbstractSolverType
abstract type AbstractSolver <: MathOptInterface.AbstractNLPEvaluator

Abstract solver for trajectory optimization problems

Any type that inherits from AbstractSolver must define the following methods:

model = get_model(::AbstractSolver)::AbstractModel
obj = get_objective(::AbstractSolver)::AbstractObjective
Z = get_trajectory(::AbstractSolver)::Traj
n,m,N = Base.size(::AbstractSolver)
x0 = get_initial_state(::AbstractSolver)::SVector
solve!(::AbstractSolver)
source
TrajectoryOptimization.initial_states!Function
initial_states!(::Union{Problem,AbstractSolver}, X0::Vector{<:AbstractVector})
initial_states!(::Union{Problem,AbstractSolver}, X0::AbstractMatrix)

Copy the state trajectory

source
TrajectoryOptimization.initial_controls!Function
initial_controls!(::Union{Problem,AbstractSolver}, U0::Vector{<:AbstractVector})
initial_controls!(::Union{Problem,AbstractSolver}, U0::AbstractMatrx)

Copy the control trajectory

source
TrajectoryOptimization.costFunction
cost(obj::Objective, Z::Traj)::Float64
cost(obj::Objective, dyn_con::DynamicsConstraint{Q}, Z::Traj)

Evaluate the cost for a trajectory. Calculate the cost gradient for an entire trajectory. If a dynamics constraint is given, use the appropriate integration rule, if defined.

source
cost(::Problem)
cost(::AbstractSolver)

Compute the cost for the current trajectory

source

Constrained Solvers

TrajectoryOptimization.ConstrainedSolverType
abstract type ConstrainedSolver <: TrajectoryOptimization.AbstractSolver{T}

Abstract solver for constrained trajectory optimization problems

In addition to the methods required for AbstractSolver, all ConstrainedSolvers must define the following method

get_constraints(::ConstrainedSolver)::ConstrainSet
source
TrajectoryOptimization.update_active_set!Function
update_active_set!(conSet::ConstraintSet, Z::Traj, ::Val{tol})

Compute the active set for the current constraint values, with tolerance tol. Uses a value type to avoid an allocation down the line.

source

Direct Solvers

Direct solvers often perform similar operations, so the following methods are provided that should work with any direct solver

TrajectoryOptimization.DirectSolverType
abstract type DirectSolver <: ConstrainedSolver{T}

Solve the trajectory optimization problem by computing search directions using the joint state vector, often solving the KKT system directly.

source
TrajectoryOptimization.gen_con_indsFunction
gen_con_inds(conSet)
gen_con_inds(conSet, structure)

Generate the indices into the concatenated constraint vector for each constraint. Determines the bandedness of the Jacobian

source
TrajectoryOptimization.constraint_jacobian_structureFunction
constraint_jacobian_structure(solver)
constraint_jacobian_structure(solver, structure)

Get the constraint Jacobian structure as a sparse array, and fill in the linear indices used for filling a vector of the non-zero elements of the Jacobian

source

The solver must also contain the following fields:

  • opts: Solver options for the solver (e.g. opts::NewSolverOptions)
  • stats::Dict{Symbol,Any}: Dictionary containing pertinent statistics for the solve, such as run time, final max constraint violation, final cost, optimality criteria, number of iterations, etc.