Skip to content

Setting Up an Optimal-Control Problem

Introduction

Pockit turns a continuous-time optimal-control model into a finite-dimensional nonlinear program. You define dynamics, costs, constraints, and endpoints with the SymPy symbols supplied by a System and its phases. Pockit then constructs the collocation equations and their sparse derivatives.

This page shows the core modeling calls through one valid minimum-time model. For a complete solve and validation workflow, continue with Getting Started. The multi-phase guide defines phase linkage, shared variables, and state resets.

Choose a transcription

Radau and Lobatto expose the same modeling API. Import one family for a model; do not import both System classes under the same name.

from pockit.radau import System
from pockit.lobatto import System

Use Radau and Lobatto Interpolation to choose a node family. The minimum-time model below uses Radau because its optimal acceleration has a jump at a known mesh boundary.

Build a minimal model

The following code defines a double integrator that starts one meter from its target at rest. Acceleration is bounded by one meter per second squared, and the final time is free:

from pockit.radau import System

system = System(0)
phase = system.new_phase(
    ["position", "velocity"],
    ["acceleration"],
)

position, velocity = phase.x
(acceleration,) = phase.u

phase.set_dynamics([velocity, acceleration])
phase.set_integral([1.0])
phase.set_phase_constraint(
    [acceleration],
    [-1.0],
    [1.0],
    True,
)
phase.set_boundary_condition(
    [1.0, 0.0],
    [0.0, 0.0],
    0.0,
    None,
)
phase.set_discretization([0.0, 0.5, 1.0], [6, 6])

system.set_phase([phase])
system.set_objective(phase.I[0])

System(0) declares that the problem has no static parameters. System.new_phase() creates the state, control, and time symbols; the returned symbols must be used in every phase expression. Passing None as the terminal time makes it an optimization variable. The integral of one is the phase duration, so minimizing phase.I[0] minimizes final time.

The final True argument marks the acceleration constraint as bang-bang for Pockit's discontinuity checks. It does not prove that an arbitrary model has a bang-bang optimum.

Phase configuration reference

Configure a phase before attaching it to a system:

Modeling task Method Required relationship
State derivatives Phase.set_dynamics One expression for each state, in Phase.x order
Running integrands Phase.set_integral Expressions later available through Phase.I
Path bounds Phase.set_phase_constraint Expression, lower bound, and upper bound lists of equal length
State and time endpoints Phase.set_boundary_condition None for free, a number for fixed, or a static-parameter expression
Mesh and polynomial points Phase.set_discretization Uniform counts or normalized mesh coordinates and per-interval counts

Phase expressions may use Phase.x, Phase.u, Phase.t, and Phase.s. One-sided constraints use -numpy.inf or numpy.inf for the open side. A bare state, control, time, or static-parameter symbol is promoted to a variable bound; a general SymPy expression remains an algebraic path constraint.

Static parameters and multiple phases

Static parameters are optimization variables that do not vary with time. Name them when constructing the system, then use the resulting symbols in endpoint, objective, or cross-phase expressions:

system = System(["event_time"])
(event_time,) = system.s
phase = system.new_phase(["position", "velocity"], ["acceleration"])

# The same symbol can close one phase and open the next.
phase.set_boundary_condition(
    [1.0, 0.0],
    [0.0, 0.0],
    0.0,
    event_time,
)

A multi-phase system receives its phases in physical order:

system.set_phase([phase_0, phase_1])
system.set_objective(objective)
system.set_system_constraint(expressions, lower_bounds, upper_bounds)

The names in this last fragment stand for already configured phase objects and SymPy expressions; the multi-phase guide provides a complete mathematical pattern and links to three runnable applications.

Updating a configured model

A set_* call differentiates symbolic expressions and compiles numerical functions, so it can take noticeable time. In a notebook, keep major setup calls in separate cells and rerun only the relationships that changed. If a phase has already been attached through System.set_phase(), call System.update() after changing that phase so system-level indices and derivatives are refreshed.

After configuration, construct an initial guess, solve the nonlinear program, and validate the reconstructed trajectory between collocation nodes. The optimizer guide documents both solver backends, and Error Checking and Mesh Refinement covers supported refinement.