Multi-Phase Optimal-Control Architecture¶
When to use multiple phases¶
A single phase is sufficient when one set of dynamics and constraints applies over one continuous interval. Use multiple phases when the physical model or event structure changes. Typical examples include staging, mode or catalyst switches, road segments with different limits, contacts, and idealized state resets.
Multiple phases can also isolate regions with very different time scales, but they should encode a real modeling or numerical boundary. A smooth problem does not need extra phases merely because it needs a finer mesh.

System and phase objects¶
A Pockit System contains \(n_p\) phases and a vector of shared static
variables:
The static variables are optimization variables, not time histories. They can represent event times, shared boundary values, design parameters, or any other quantity needed by more than one phase.
Phase \(i\) has its own time interval, state, control, dynamics, path constraints, and integral vector:
Its continuous-time relationships have the form
Each endpoint state and time may be fixed, free, or expressed as a function of the shared static variables.
Objective and system constraints¶
The system-level objective combines all phase integrals and shared variables:
System constraints impose algebraic relationships that span phases:
They are useful for total budgets, integral targets, and relationships that cannot be expressed as one phase's endpoint alone.
Connecting an event¶
For two adjacent phases separated at an unknown event time \(t_s\), introduce \(t_s\) as a shared variable and set
For a continuous state,
For an instantaneous reset, use a reset map instead:
Do not impose continuity on a component that physically jumps. The two-stage rocket keeps altitude and velocity continuous while applying a discrete mass reset. The batch reactor and electric-vehicle example show continuous event connections in other domains.
Implementation pattern¶
system = System(number_of_static_variables)
phase_0 = system.new_phase(state_names_0, control_names_0)
phase_1 = system.new_phase(state_names_1, control_names_1)
# Configure each phase independently, then connect endpoints through
# shared static variables and system-level equations.
system.set_phase([phase_0, phase_1])
system.set_system_constraint(expressions, lower_bounds, upper_bounds)
system.set_objective(objective)
After solving, validate each phase densely and check both sides of every event. Report continuity residuals and reset residuals separately; a solver success code does not replace those physical checks.