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.
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.