algorithms
Successive convexification algorithms for trajectory optimization.
This module provides implementations of SCvx (Successive Convexification) algorithms for solving non-convex trajectory optimization problems through iterative convex approximation.
All algorithms inherit from :class:Algorithm, enabling pluggable algorithm
implementations and custom SCvx variants. An algorithm builds its fused,
JAX-pure iteration body (build_iteration), stores it along with the
diagnostics emitter (initialize), drives it one step at a time on the
Python path (step), and owns its convergence policy (converged) —
honored identically by solve(), solve_jax(), and solve_batched().
The iterate carry is split into two objects:
-
:class:
AlgorithmState— a frozen, JAX-registered pytree holding only the current iterate (x,u, weights, propagated states, diagnostic scalars). Every leaf is ajnp.ndarrayso the state composes withjax.vmap/jax.jit/jax.grad. -
:class:
AlgorithmHistory— a CPU-side mutable record of every iteration's trajectories, discretizations, weights, and diagnostics. Grown by the SCP loop viarecord_iteration; never crosses the JAX boundary.
Autotuners follow the same contract: :class:AutotuningBase declares
update_weights as a pure functional update on the :class:AlgorithmState
pytree (no mutation, no string returns, no list appends, no Python-level
branching on iterate values). The next-iterate adaptive state is reported via
the :class:AdaptiveStateCode IntEnum and converted to a human-readable string
on the printing path with :func:adaptive_state_code_to_str.
Current Implementations:
- :class:
PenalizedTrustRegion: Penalized Trust Region (PTR) algorithm
AcceptanceRatioAutotuner
¶
Bases: AutotuningBase
Four-bucket acceptance-ratio update on lam_prox.
The acceptance ratio :math:\rho (actual over predicted nonlinear
reduction) selects one of four buckets — REJECT / ACCEPT_HIGHER /
ACCEPT_CONSTANT / ACCEPT_LOWER — via a jnp.where cascade so the
whole update traces under jax.jit. Subclasses supply the multiplier
rule (how lam_vc / lam_vb_* respond on acceptance) through
:meth:_update_multipliers, and must assign an
:class:AcceptanceRatioHyper (or subclass) instance to self.hyper in
__init__ — the shared body reads its knobs off state.hyper.
update_weights is a pure functional update on the
:class:AlgorithmState pytree; see the base-class contract.
Source code in openscvx/algorithms/autotuner/acceptance_ratio.py
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 | |
citation() -> List[str]
¶
Return BibTeX citations for the SCvx acceptance-ratio update rule.
Source code in openscvx/algorithms/autotuner/acceptance_ratio.py
update_weights(state: AlgorithmState, candidate: CandidateIterate, nodal_constraints: LoweredJaxConstraints, settings: Config, params: dict) -> AlgorithmState
¶
Return the next-iterate state per the acceptance-ratio rules.
Pure functional update — see class docstring.
Source code in openscvx/algorithms/autotuner/acceptance_ratio.py
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 | |
AcceptanceRatioHyper
¶
Bases: LamCostRelaxHyper
Knobs shared by every acceptance-ratio autotuner.
Extends the shared :class:LamCostRelaxHyper lam_cost relaxation knobs
with the acceptance-ratio decision thresholds and the lam_prox clips.
Source code in openscvx/algorithms/autotuner/acceptance_ratio.py
AdaptiveProximalWeight
¶
Bases: AcceptanceRatioAutotuner
PTR-style proximal adaptation with fixed virtual-penalty weights.
Inherits the four-bucket acceptance-ratio update on lam_prox from
:class:~openscvx.algorithms.autotuner.acceptance_ratio.AcceptanceRatioAutotuner
and keeps the default multiplier hook, so the virtual-control and
virtual-buffer weights are carried unchanged.
update_weights is a pure functional update on the
:class:AlgorithmState pytree; see the base-class contract.
Source code in openscvx/algorithms/autotuner/adaptive_proximal_weight.py
AdaptiveStateCode
¶
Bases: IntEnum
Outcome of one autotuner update_weights call.
The autotuner stores its decision as an int32 on
attr:
AlgorithmState.adaptive_state_code so the pytree remains
JAX-traceable. The SCP printing path maps the code back to a human-readable
string via :func:adaptive_state_code_to_str.
Source code in openscvx/algorithms/state.py
Algorithm
¶
Bases: ABC
Abstract base class for successive convexification algorithms.
An Algorithm owns the SCP iteration end to end: it builds the
JAX-pure iteration body (:meth:build_iteration), stores it
(:meth:initialize), and drives it one step at a time (:meth:step).
step takes the current :class:AlgorithmState and a CPU-side
:class:AlgorithmHistory, returns the next state plus a convergence flag,
and appends to history for diagnostics.
Statefullness
Avoid storing mutable iteration state on self. All iteration
state lives on :class:AlgorithmState (JAX-traceable) and
:class:AlgorithmHistory (Python-side), threaded explicitly through
step().
The surface :class:~openscvx.problem.Problem relies on, beyond the
abstract methods below:
autotuner— the :class:AutotuningBasewhose declaredhyperfields :class:~openscvx.problem.Problemenumerates to build the per-solve / batched override channel.ep_tr/ep_vb/ep_vc— convergence thresholds :meth:Problem._sync_scp_constantssnapshots onto :class:AlgorithmStatebefore each solve.- :meth:
get_columns— the iteration-table columns to print. weights.build_vb_arrays— called once in :meth:Problem.initializeto size the virtual-buffer weight arrays.- :meth:
_hash_into— contributes the algorithm's identity to thesolve_batchedexport cache key.
t_max is honored only on the Python solve() path; the JAX
lax.while_loop behind solve_jax / solve_batched terminates on
k_max and the convergence predicate alone (no wall-clock probe inside a
trace).
Source code in openscvx/algorithms/base.py
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 | |
__init__(weights: Weights, autotuner: AutotuningBase, k_max: int, t_max: Optional[float], ep_tr: float, ep_vb: float, ep_vc: float)
¶
Record the SCP weights, autotuner, and convergence parameters.
Subclasses build the user-facing defaults (PTR owns them) and end their
own __init__ with super().__init__(...); every parameter here is
required so the ABC is not a fourth place those defaults are declared.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
Weights
|
SCP weights used by the algorithm and autotuner. |
required |
autotuner
|
AutotuningBase
|
The penalty-weight update rule (:class: |
required |
k_max
|
int
|
Maximum number of SCP iterations. |
required |
t_max
|
Optional[float]
|
Optional wall-clock time limit in seconds ( |
required |
ep_tr
|
float
|
Trust-region convergence threshold. |
required |
ep_vb
|
float
|
Virtual-buffer convergence threshold. |
required |
ep_vc
|
float
|
Virtual-control convergence threshold. |
required |
Source code in openscvx/algorithms/base.py
build_iteration(dis_continuous: Callable, dis_impulsive: Callable, jax_constraints: LoweredJaxConstraints, solver_callback: Callable, settings: Config) -> Callable
abstractmethod
¶
Build the JAX-pure SCP iteration body for this algorithm.
:class:~openscvx.problem.Problem assembles the discretization
solvers, lowered constraints, and convex-solver callback, then asks
the algorithm to fuse them into one step. The algorithm owns this
because the fusion is algorithm-specific (which autotuner runs, which
penalty terms are assembled); Problem stays algorithm-agnostic.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dis_continuous
|
Callable
|
Continuous-dynamics discretization solver,
|
required |
dis_impulsive
|
Callable
|
Impulsive/discrete-dynamics discretization solver,
|
required |
jax_constraints
|
LoweredJaxConstraints
|
Lowered JAX constraints the body operates over. |
required |
solver_callback
|
Callable
|
The convex backend's |
required |
settings
|
Config
|
Problem configuration. |
required |
Returns:
| Type | Description |
|---|---|
Callable
|
The JAX-pure |
Callable
|
iteration body. It must advance |
Callable
|
both |
Callable
|
behind |
Callable
|
class: |
Callable
|
and hands it back via :meth: |
Source code in openscvx/algorithms/base.py
citation() -> List[str]
abstractmethod
¶
converged(state: AlgorithmState) -> jnp.ndarray
¶
Boolean SCP convergence test from the metrics and thresholds on state.
The default — every metric below its threshold — is algorithm-agnostic:
:class:AlgorithmState carries J_tr / J_vb / J_vc and the
ep_* tolerances generically, so it serves any SCP algorithm. Override
to change the convergence policy; the override is honored on all three
solve paths (solve / solve_jax / solve_batched), since
:meth:step and the lax.while_loop harness both route through it.
Must be JAX-traceable: it runs inside the lax.while_loop cond of
solve_jax / solve_batched (where it is jax.vmap'd per batch
element) as well as on the Python solve() path.
Under save_compiled, the predicate is baked into the exported
solve_batched artifact, but the cache key sees only the algorithm's
class name (:meth:_hash_into): introducing an override — a new
subclass — is a clean cache miss, while editing the body of an
existing override is invisible to the key and silently loads the stale
artifact. Clear the solver cache (or rename the class) when iterating
on a predicate with save_compiled enabled.
Source code in openscvx/algorithms/base.py
get_columns(verbosity: int) -> List[Column]
abstractmethod
¶
Return the iteration-table columns to print at verbosity.
:class:~openscvx.problem.Problem calls this once per solve to lay
out the progress table. Implementations typically concatenate the
algorithm's own columns with the autotuner's COLUMNS and filter by
each column's min_verbosity.
Source code in openscvx/algorithms/base.py
initialize(iteration_fn: Callable, emitter: Callable) -> None
abstractmethod
¶
Store the fused SCP iteration body and per-iteration infrastructure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
iteration_fn
|
Callable
|
The JAX-pure |
required |
emitter
|
Callable
|
Per-iteration diagnostics sink (printing queue / no-op). |
required |
Source code in openscvx/algorithms/base.py
step(state: AlgorithmState, history: AlgorithmHistory, params: dict, settings: Config) -> Tuple[AlgorithmState, bool]
abstractmethod
¶
Execute one iteration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
AlgorithmState
|
Current-iterate pytree. |
required |
history
|
AlgorithmHistory
|
CPU-side append-only iteration log. |
required |
params
|
dict
|
Problem parameters (may change between steps). |
required |
settings
|
Config
|
Configuration object (may change between steps). |
required |
Returns:
| Type | Description |
|---|---|
AlgorithmState
|
|
bool
|
and a flag indicating whether the SCP convergence criteria are |
Tuple[AlgorithmState, bool]
|
satisfied. |
Source code in openscvx/algorithms/base.py
AlgorithmHistory
dataclass
¶
Append-only iteration log, populated by the SCP loop.
Mirrors the lists that previously lived directly on AlgorithmState.
Never appears on the JAX boundary; the SCP loop appends to it after each
iteration via meth:
record_iteration. Purely diagnostic — the
subproblem solver consumes SubproblemData in-trace, not the history.
Source code in openscvx/algorithms/history.py
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 | |
V_history: List[np.ndarray]
property
¶
View of the raw discretization matrices recorded so far.
from_settings(settings: 'Config') -> 'AlgorithmHistory'
classmethod
¶
Construct an empty history sized to the problem.
record_iteration(state: AlgorithmState, *, V: Optional[np.ndarray] = None, W: Optional[np.ndarray] = None, VC: Optional[np.ndarray] = None, TR: Optional[np.ndarray] = None, J_lin: Optional[float] = None, record_diagnostics: bool = True) -> Tuple[dict, np.ndarray]
¶
Append per-iteration data based on state.adaptive_state_code.
The accepted iterate (x / u / weights / convergence scalars) is
read off state; the raw host-side diagnostics the SCP loop already
synced — discretization matrices (V / W), virtual control
(VC), trust region (TR), and the linearized cost (J_lin) —
come in as explicit keyword arrays so the generic history never has to
know the algorithm's diagnostics type.
Reproduces the old accept_solution / reject_solution behavior:
- REJECT: append
lam_prox(the bumped weight that drives the next subproblem), plus the predicted/actual/acceptance diagnostics whenrecord_diagnosticsis set. - INITIAL: append every trajectory / weight history entry, but skip the predicted/actual/acceptance diagnostics (the autotuner didn't compute them on iter 1).
- ACCEPT_*: append everything, including diagnostics when
record_diagnosticsis set (the SCP loop turns this off for autotuners that don't compute them — :class:ConstantProximalWeight, :class:RampProximalWeight).
Every device-resident leaf this method needs is pulled with one
jax.device_get call (a single CPU<->device round trip) and the
host-side numpy arrays are then partitioned onto the history lists.
The SCP loop also reads the same scalars for printing + convergence,
so the bundle is returned alongside the lam_prox array to avoid a
second sync.
Returns:
| Type | Description |
|---|---|
dict
|
|
ndarray
|
( |
Tuple[dict, ndarray]
|
|
Tuple[dict, ndarray]
|
|
Tuple[dict, ndarray]
|
the numpy copy of |
Tuple[dict, ndarray]
|
these directly without re-syncing. |
Source code in openscvx/algorithms/history.py
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 | |
AlgorithmState
dataclass
¶
Current-iterate pytree for SCP iterations.
Every field is a JAX array (or scalar). The state moves between iterations
by way of pure functional updates: meth:
replace returns a new state
rather than mutating in place. Registered as a JAX pytree so the SCP body
composes with jax.jit / jax.vmap / jax.grad.
Append-only iteration histories (X, U, discretizations, ...) live on
:class:AlgorithmHistory, which is CPU-side and grown by the SCP loop.
Attributes:
| Name | Type | Description |
|---|---|---|
x |
ndarray
|
Nodal state trajectory, shape |
u |
ndarray
|
Nodal control trajectory, shape |
x_prop |
ndarray
|
Continuous-time propagation of |
x_prop_plus |
ndarray
|
Impulsive/discrete dynamics evaluated at |
lam_prox |
ndarray
|
Trust-region weight, shape |
lam_vc |
ndarray
|
Virtual-control penalty weight, shape |
lam_cost |
ndarray
|
Cost weight, shape |
lam_cost_init |
ndarray
|
Initial cost weight ( |
lam_vb_nodal |
ndarray
|
Nodal virtual-buffer weights, shape |
lam_vb_cross |
ndarray
|
Cross-node virtual-buffer weights, shape |
k |
ndarray
|
Iteration counter (starts at 1). |
J_tr |
ndarray
|
Current trust-region cost (scalar). |
J_vb |
ndarray
|
Current virtual-buffer cost (scalar). |
J_vc |
ndarray
|
Current virtual-control cost (scalar). |
J_nonlin |
ndarray
|
Nonlinear-objective value for the current accepted iterate. |
predicted_reduction |
ndarray
|
Predicted reduction in |
actual_reduction |
ndarray
|
Actual reduction in |
acceptance_ratio |
ndarray
|
|
adaptive_state_code |
ndarray
|
:class: |
x_init_pin |
ndarray
|
Initial-state boundary condition, shape |
x_term_pin |
ndarray
|
Terminal-state boundary condition, shape |
ep_tr |
ndarray
|
Convergence threshold on |
ep_vb |
ndarray
|
Convergence threshold on |
ep_vc |
ndarray
|
Convergence threshold on |
k_max |
ndarray
|
SCP iteration cap (scalar, |
hyper |
HyperParams
|
Autotuner-declared hyperparameters — an instance of the
autotuner's :class: |
Source code in openscvx/algorithms/state.py
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 | |
from_settings(settings: Config, weights: Weights, *, ep_tr: float, ep_vb: float, ep_vc: float, k_max: int, hyper: HyperParams) -> AlgorithmState
classmethod
¶
Construct the initial iterate from configuration.
Copies trajectory guesses, expands weights to dense arrays, and seeds
the diagnostic scalars to zero / attr:
AdaptiveStateCode.INITIAL.
x_prop and x_prop_plus are zero-initialized; the autotuner
fills them when it carries the first accepted candidate onto the
state (see :meth:AutotuningBase.update_weights).
The SCP loop constants (ep_tr / ep_vb / ep_vc / k_max)
and the autotuner's declared hyperparameters (hyper, from
:attr:AutotuningBase.hyper) are snapshotted onto the pytree here —
the caller passes them off the algorithm and its autotuner (see
Problem._default_state), and per-solve overrides land via
state.replace.
Source code in openscvx/algorithms/state.py
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 | |
replace(**changes) -> AlgorithmState
¶
AugmentedLagrangian
¶
Bases: AcceptanceRatioAutotuner
Augmented Lagrangian autotuner.
Uses an acceptance-ratio :math:\rho between predicted and actual
reduction in the nonlinear objective to drive a four-bucket update of
the trust-region weight lam_prox (inherited from
:class:~openscvx.algorithms.autotuner.acceptance_ratio.AcceptanceRatioAutotuner)
and to update the virtual-control / virtual-buffer weights from constraint
violations via the multiplier hook.
update_weights is a pure functional update on the
:class:AlgorithmState pytree; see the base-class contract.
Source code in openscvx/algorithms/autotuner/augmented_lagrangian.py
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 | |
__init__(rho_init: float = 1.0, rho_max: float = 100.0, gamma_1: float = 2.0, gamma_2: float = 0.5, eta_0: float = 0.01, eta_1: float = 0.1, eta_2: float = 0.8, ep: float = 0.99, eta_lambda: float = 10.0, lam_vc_max: float = 100000.0, lam_prox_min: float = 0.001, lam_prox_max: float = 10000.0, lam_cost_drop: int = -1, lam_cost_relax: float = 1.0)
¶
Initialize Augmented Lagrangian autotuning parameters.
Every numeric knob is a declared hyperparameter: it lives on the frozen
:class:AugmentedLagrangianHyper container at self.hyper and is a
per-solve / batched override target (see :class:AutotuningBase).
Knobs may still be read and written as plain attributes
(autotuner.lam_prox_max = 1e6); the attribute proxy on
:class:AutotuningBase routes the access into hyper.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rho_init
|
float
|
Reserved for a future penalty-growth rule; currently unused. Passing a non-default value warns. Defaults to 1.0. |
1.0
|
rho_max
|
float
|
Reserved for a future penalty-growth rule; currently unused. Passing a non-default value warns. Defaults to 1e2. |
100.0
|
gamma_1
|
float
|
Factor to increase trust region weight when ratio is low. Defaults to 2.0. |
2.0
|
gamma_2
|
float
|
Factor to decrease trust region weight when ratio is high. Defaults to 0.5. |
0.5
|
eta_0
|
float
|
Acceptance ratio threshold below which solution is rejected. Defaults to 1e-2. |
0.01
|
eta_1
|
float
|
Threshold above which solution is accepted with constant weight. Defaults to 1e-1. |
0.1
|
eta_2
|
float
|
Threshold above which solution is accepted with lower weight. Defaults to 0.8. |
0.8
|
ep
|
float
|
Threshold for virtual control weight update (nu > ep vs nu <= ep). Must lie in (0, 1). Defaults to 0.99. |
0.99
|
eta_lambda
|
float
|
Step size for virtual control weight update. Defaults to 1e1. |
10.0
|
lam_vc_max
|
float
|
Maximum virtual control penalty weight. Defaults to 1e5. |
100000.0
|
lam_prox_min
|
float
|
Minimum trust region (proximal) weight. Defaults to 1e-3. |
0.001
|
lam_prox_max
|
float
|
Maximum trust region (proximal) weight. Defaults to 1e4. |
10000.0
|
lam_cost_drop
|
int
|
Iteration after which cost relaxation applies
( |
-1
|
lam_cost_relax
|
float
|
Factor applied to lam_cost after lam_cost_drop. Defaults to 1.0. |
1.0
|
Source code in openscvx/algorithms/autotuner/augmented_lagrangian.py
citation() -> List[str]
¶
Return BibTeX citations for the augmented-Lagrangian multiplier updates.
Extends the inherited SCvx acceptance-ratio references with Luo's dissertation, whose penalty-weight updates this autotuner implements.
Source code in openscvx/algorithms/autotuner/augmented_lagrangian.py
AutotuningBase
¶
Bases: ABC
Base class for autotuning strategies in SCP algorithms.
Subclasses implement meth:
update_weights as a pure functional
update on an :class:AlgorithmState pytree. Concretely, an autotuner
must:
- Not mutate
stateorcandidate. - Not return strings, raise on data-dependent conditions, or append to Python lists at trace time.
- Express all branching on iterate values via
jax.lax.cond/jnp.where.
The autotuner's per-iteration outcome is encoded as an
:class:AdaptiveStateCode int32 on the returned state; the SCP loop
converts that back to a human-readable label only on the Python-loop
printing path.
The returned state IS the next iterate. The SCP loop discards
everything except what update_weights returns, so accepting the
subproblem's candidate means carrying its trajectory onto the returned
state — x / u / x_prop / x_prop_plus from candidate —
while rejecting means keeping the previous fields and adjusting only the
weights. An update that never copies the candidate produces a solver that
runs to k_max without the iterate ever moving, silently.
Declaring tunable hyperparameters. A numeric knob a user might sweep
(a penalty ramp, a relaxation iteration) must not be read off self
inside update_weights — a Python attribute is baked into the trace
and invisible to every override channel. Instead, declare it on a
:class:HyperParams subclass, assign an instance to self.hyper, and
read it from state.hyper::
class MyHyper(HyperParams):
ramp: float = 2.0
class MyAutotuner(AutotuningBase):
def __init__(self, ramp: float = 2.0):
self.hyper = MyHyper(ramp=ramp)
def update_weights(self, state, candidate, nodal_constraints, settings, params):
return state.replace(
# accept the candidate: it becomes the next iterate
x=candidate.x,
u=candidate.u,
x_prop=candidate.x_prop,
x_prop_plus=candidate.x_prop_plus,
lam_prox=state.lam_prox * state.hyper.ramp,
adaptive_state_code=...,
)
The declaration is the registration: the field becomes a per-solve
override (solve_jax(algorithm={"ramp": ...})), a batchable sweep
target (solve_batched(algorithm={"ramp": jnp.linspace(...)})), and a
runtime input of the exported batched artifact — with zero core edits.
Every numeric knob of the built-in autotuners is declared this way, so the
only configuration left outside hyper is structural choices that select
code paths (class attributes like COMPUTES_ACCEPTANCE_METRICS); they
are part of the traced program, not data. For ergonomics, declared knobs
are still readable and writable as bare attributes
(autotuner.ramp = 3.0): the proxy below routes the access into
hyper so a constructor — or a test — may keep assigning knobs by name.
Assign self.hyper first in __init__; a knob assigned before
hyper exists lands as a plain instance attribute the proxy cannot see.
Class Attributes
COLUMNS: List of Column specs for autotuner-specific metrics to display.
Subclasses override this to add their own columns.
COMPUTES_ACCEPTANCE_METRICS: Whether update_weights computes a
predicted/actual reduction and the resulting acceptance ratio. The
SCP loop records and prints those diagnostics only when this is
True (the default); tuners that never reject an iterate — e.g.
:class:~openscvx.algorithms.autotuner.constant_proximal_weight.ConstantProximalWeight
and :class:~openscvx.algorithms.autotuner.ramp_proximal_weight.RampProximalWeight
— set it False.
hyper: The autotuner's declared hyperparameters — a
:class:HyperParams instance carrying plain-Python defaults
(the empty base when it declares none). Snapshotted onto
AlgorithmState.hyper with array leaves (see
:meth:AlgorithmState.from_settings; int fields get k's
integer dtype, float fields the float dtype), which is what
update_weights reads at trace time.
Source code in openscvx/algorithms/autotuner/base.py
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 | |
citation() -> List[str]
¶
Return BibTeX citations for the published update rule this autotuner implements.
Autotuners implementing a published scheme override this so that
:meth:openscvx.Problem.citation can credit it alongside the
algorithm, solver, and discretization references. Unpublished
heuristics keep the default.
Returns:
| Type | Description |
|---|---|
List[str]
|
List of BibTeX citation strings. Empty for uncited autotuners. |
Source code in openscvx/algorithms/autotuner/base.py
update_weights(state: AlgorithmState, candidate: CandidateIterate, nodal_constraints: LoweredJaxConstraints, settings: Config, params: dict) -> AlgorithmState
abstractmethod
¶
Return the next-iterate :class:AlgorithmState.
Must be JAX-traceable. See the class docstring for the contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
AlgorithmState
|
Current-iterate pytree. Initial weights live on
|
required |
candidate
|
CandidateIterate
|
Subproblem result (read-only here). On acceptance its trajectory fields must be carried onto the returned state — see the class docstring. |
required |
nodal_constraints
|
LoweredJaxConstraints
|
Lowered JAX constraints. |
required |
settings
|
Config
|
Configuration object. |
required |
params
|
dict
|
Problem parameter dictionary. |
required |
Returns:
| Type | Description |
|---|---|
AlgorithmState
|
The next-iterate :class: |
AlgorithmState
|
nothing else, so an accepting update carries |
AlgorithmState
|
|
AlgorithmState
|
rejecting update keeps the previous fields and adjusts only the |
AlgorithmState
|
weights. Its |
AlgorithmState
|
the autotuner's decision; the SCP loop records that into history |
AlgorithmState
|
and maps it to a printable label. |
Source code in openscvx/algorithms/autotuner/base.py
CandidateIterate
dataclass
¶
Subproblem candidate the fused iteration hands to the autotuner.
Built once, in-trace, by the SCP iteration body
(scvx/iteration.py): it carries the just-solved subproblem trajectory
(x / u), its propagation (x_prop / x_prop_plus), and the
linearized cost (J_lin) into meth:
AutotuningBase.update_weights,
which copies the accepted fields onto the next :class:AlgorithmState. It
is the autotuner's input and nothing else — host-side history recording
takes its arrays through meth:
AlgorithmHistory.record_iteration's
explicit keyword arguments rather than through this type.
Source code in openscvx/algorithms/state.py
ConstantProximalWeight
¶
Bases: AutotuningBase
Hold lam_prox constant; relax lam_cost after lam_cost_drop.
Useful when you want a fixed trust-region size without adaptation.
update_weights is a pure functional update on the
:class:AlgorithmState pytree; see the base-class contract.
Source code in openscvx/algorithms/autotuner/constant_proximal_weight.py
update_weights(state: AlgorithmState, candidate: CandidateIterate, nodal_constraints: LoweredJaxConstraints, settings: Config, params: dict) -> AlgorithmState
¶
Return the next-iterate state.
Pure functional update — see class docstring.
Source code in openscvx/algorithms/autotuner/constant_proximal_weight.py
DiscretizationResult
dataclass
¶
Unpacked discretization data from a multi-shot discretization matrix.
The discretization solver returns a matrix V that stores multiple blocks
(propagated state and linearization matrices) across nodes/time. Historically,
we stored the raw V matrices and re-unpacked them repeatedly via slicing.
This dataclass unpacks once and makes access trivial.
Source code in openscvx/algorithms/history.py
from_V(V: np.ndarray, n_x: int, n_u: int, N: int) -> 'DiscretizationResult'
classmethod
¶
Unpack the final timestep of a raw discretization matrix V.
Source code in openscvx/algorithms/history.py
from_VW(V: np.ndarray, W: np.ndarray, n_x: int, n_u: int, N: int) -> 'DiscretizationResult'
classmethod
¶
Unpack continuous and impulsive discretization blocks from V and W.
Source code in openscvx/algorithms/history.py
HyperParams
¶
Base for autotuner hyperparameter containers.
Subclassing is the entire integration. Declare each tunable knob as an
annotated field with its default — bare annotations, no @dataclass
decorator — and the base class does the rest:
- applies the frozen, keyword-only dataclass transform, so instances are
immutable value objects constructed by knob name (mixin inheritance
reorders fields, so positional construction would be a silent
mis-binding) and updated with :func:
dataclasses.replace; - registers the subclass as a JAX pytree (and for
jax.exporttreedef serialization), so instances ride :attr:AlgorithmState.hyperthroughjit/vmap/ the exported batchedsolve_batchedartifact; - wires dtype handling off the field annotations when
:meth:
AlgorithmState.from_settingssnapshots the instance onto the state —intfields get the iteration counter's dtype,floatfields the problem float dtype. Any other annotation is rejected at class definition.
Example::
class MyHyper(HyperParams):
ramp: float = 2.0
drop: int = -1
The empty base is the "no declared hyperparameters" container: it flattens to zero pytree leaves.
Source code in openscvx/algorithms/hyperparams.py
MultishotPropagation
dataclass
¶
Unpacked SCP multishot integration matrix V.
V shape: (n_segments * segment_size, n_substeps) where
segment_size = n_x + n_x**2 + 2 * n_x * n_u.
Source code in openscvx/algorithms/history.py
chronological() -> tuple[np.ndarray, np.ndarray]
¶
Time-ordered stitched full states and times.
Skips duplicated segment-boundary samples (j0 = 0 on seg 0, else 1).
Returns:
| Type | Description |
|---|---|
ndarray
|
|
ndarray
|
|
Source code in openscvx/algorithms/history.py
segment_states(seg_idx: int) -> np.ndarray
¶
Integrated states for one segment, shape (n_substeps, n_x).
Source code in openscvx/algorithms/history.py
segments() -> list[np.ndarray]
¶
slice_states(state_slice: slice) -> tuple[np.ndarray, np.ndarray]
¶
chronological() then apply state_slice to the state dimension.
state(name_or_state: StateLike) -> tuple[np.ndarray, np.ndarray]
¶
Chronological trajectory for one symbolic state.
name_or_state may be a :class:~openscvx.symbolic.expr.state.State
instance or a string name matching State.name in self.states.
Source code in openscvx/algorithms/history.py
OptimizationResults
dataclass
¶
Structured container for optimization results from the Successive Convexification (SCP) solver.
This class provides a type-safe and organized way to store and access optimization results, replacing the previous dictionary-based approach. It includes core optimization data, iteration history for convergence analysis, post-processing results, and flexible storage for plotting and application-specific data.
Attributes:
| Name | Type | Description |
|---|---|---|
converged |
bool
|
Whether the optimization successfully converged. |
t_final |
float
|
Final time of the optimized trajectory. |
x_guess |
ndarray
|
Optimized state trajectory at discretization nodes, shape (N, n_states). |
u_guess |
ndarray
|
Optimized control trajectory at discretization nodes, shape (N, n_controls). |
nodes |
dict[str, ndarray]
|
Dictionary mapping state/control names to arrays at optimization nodes. Includes both user-defined and augmented variables. |
trajectory |
dict[str, ndarray]
|
Dictionary mapping state/control names to arrays along the propagated trajectory. Added by post_process(). |
x_history |
list[ndarray]
|
State trajectories from each SCP iteration. |
u_history |
list[ndarray]
|
Control trajectories from each SCP iteration. |
discretization_history |
list[ndarray]
|
Time discretization from each iteration. |
J_tr_history |
list[ndarray]
|
Trust region cost history. |
J_vb_history |
list[ndarray]
|
Virtual buffer cost history. |
J_vc_history |
list[ndarray]
|
Virtual control cost history. |
t_full |
Optional[ndarray]
|
Full time grid for interpolated trajectory. Added by propagate_trajectory_results. |
x_full |
Optional[ndarray]
|
Interpolated state trajectory on full time grid. Added by propagate_trajectory_results. |
u_full |
Optional[ndarray]
|
Interpolated control trajectory on full time grid. Added by propagate_trajectory_results. |
cost |
Optional[float]
|
Total cost of the optimized trajectory. Added by propagate_trajectory_results. |
ctcs_violation |
Optional[ndarray]
|
Continuous-time constraint violations. Added by propagate_trajectory_results. |
parameters |
dict[str, ndarray]
|
Parameter values the solve used, recorded
by :meth: |
plotting_data |
dict[str, Any]
|
Flexible storage for plotting and application data. |
JAX pytree registration
OptimizationResults is a registered JAX pytree, so the output of
:meth:~openscvx.problem.Problem.solve_jax composes with
jax.vmap / jax.jit / jax.grad. The pytree children are
converged, t_final, nodes, trajectory, X, U,
and every *_history field — the data the user actually consumes.
Post-process fields (t_full, x_full, u_full, cost,
ctcs_violation), parameters, plotting_data, and the
internal _states / _controls metadata are stashed in the
treedef's aux instead, so
a batched solve_jax doesn't force callers to handle None
leaves and post_process() outputs stay outside the vmap surface
(post-process per element after the batched solve).
The two construction paths produce different histories:
- :meth:
from_history(solve()) populates the per-iterationX/U/*_historylists from the Python loop's :class:~openscvx.algorithms.history.AlgorithmHistory. - :meth:
from_final_state(solve_jax()) wraps only the final iterate:X = [state.x]andU = [state.u](single-element lists soresult.x/result.ucontinue to return the final iterate), and every*_historyfield is empty — per-iteration history requires Python-side list growth that can't run insidelax.while_loop.
For Developers
The metadata={"npz": ...} parameter on each field below is a built-in feature of
dataclasses.field. It attaches a read-only mapping to the field definition,
not to instances. Instances still only have the normal attributes (result.X,
result.converged, etc.).
The save() / load() methods iterate over field metadata to
determine how to serialize each field, so adding a new field only
requires tagging it here, no separate field-name lists to maintain.
Fields without an "npz" metadata entry are skipped during
serialization (e.g. _states, _controls).
Source code in openscvx/algorithms/optimization_results.py
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 | |
u: np.ndarray
property
¶
Optimal control trajectory at discretization nodes.
Returns the final converged solution from the SCP iteration history.
Returns:
| Type | Description |
|---|---|
ndarray
|
Control trajectory array, shape (N, n_controls) |
x: np.ndarray
property
¶
Optimal state trajectory at discretization nodes.
Returns the final converged solution from the SCP iteration history.
Returns:
| Type | Description |
|---|---|
ndarray
|
State trajectory array, shape (N, n_states) |
from_final_state(state: AlgorithmState, *, problem: Problem) -> OptimizationResults
classmethod
¶
JAX-pure construction from the final SCP iterate.
Used by :meth:~openscvx.problem.Problem.solve_jax. Every leaf is a
jnp.ndarray derived from state via static slicing, so the
result composes with jax.vmap / jax.jit / jax.grad over a
batched state. X = [state.x] and U = [state.u] are
single-element lists so result.x / result.u continue to
return the final iterate; every *_history field is empty — the
Python-loop .solve() is the path that populates them.
Source code in openscvx/algorithms/optimization_results.py
from_history(history: AlgorithmHistory, final_state: AlgorithmState, *, problem: Problem, converged: bool) -> OptimizationResults
classmethod
¶
Host-side construction from the Python-loop iteration history.
Used by :meth:~openscvx.problem.Problem.solve to package the full
per-iteration log produced by the Python while loop. Symmetric
with :meth:from_final_state — same return type, populated history.
Source code in openscvx/algorithms/optimization_results.py
get(key: str, default: Any = None) -> Any
¶
Get a value from the results, similar to dict.get().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The key to look up |
required |
default
|
Any
|
Default value if key is not found |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
The value associated with the key, or default if not found |
Source code in openscvx/algorithms/optimization_results.py
load(path: Union[str, Path]) -> OptimizationResults
classmethod
¶
Load results from a .npz file previously created by :meth:save.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Union[str, Path]
|
Path to the |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Reconstructed |
OptimizationResults
|
class: |
Source code in openscvx/algorithms/optimization_results.py
multishot_propagation(iteration: int = -1, *, t_nodes: Optional[np.ndarray] = None) -> Optional[MultishotPropagation]
¶
Return unpacked multishot data for SCP iteration iteration (default: final).
Returns None when discretization_history is empty (e.g. solve_jax).
Attaches self._states so .state("q") works without passing slices manually.
Source code in openscvx/algorithms/optimization_results.py
save(path: Union[str, Path]) -> None
¶
Save results to a compressed .npz file.
Serialization behaviour is driven by the "npz" key in each
dataclass field's metadata. Fields without that key are
skipped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Union[str, Path]
|
Output file path. |
required |
Source code in openscvx/algorithms/optimization_results.py
to_dict() -> dict[str, Any]
¶
Convert the results to a dictionary for backward compatibility.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dictionary representation of the results |
Source code in openscvx/algorithms/optimization_results.py
tree_flatten()
¶
Split into JAX-traceable children and host-side aux.
See :attr:_PYTREE_CHILDREN for the children list. Aux carries the
symbolic metadata, scratch dict, and post-process fields verbatim so
the round-trip tree_unflatten(*tree_flatten(self)) preserves the
instance.
Source code in openscvx/algorithms/optimization_results.py
update(other: dict[str, Any])
¶
Update the results with additional data from a dictionary.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
dict[str, Any]
|
Dictionary containing additional data |
required |
Source code in openscvx/algorithms/optimization_results.py
update_plotting_data(**kwargs: Any) -> None
¶
Update the plotting data with additional information.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**kwargs
|
Any
|
Key-value pairs to add to plotting_data |
{}
|
PenalizedTrustRegion
¶
Bases: Algorithm
Penalized Trust Region (PTR) successive convexification algorithm.
PTR solves non-convex trajectory optimization problems through iterative convex approximation. Each subproblem balances competing cost terms:
- Trust region penalty: Discourages large deviations from the previous iterate, keeping the solution within the region where linearization is valid.
- Virtual control: Relaxes dynamics constraints, penalized to drive defects toward zero as the algorithm converges.
- Virtual buffer: Relaxes non-convex constraints, similarly penalized to enforce feasibility at convergence.
- Problem objective and other terms: The user-defined cost (e.g., minimum fuel, minimum time) and any additional penalty terms.
The interplay between these terms guides the optimization: the trust region anchors the solution near the linearization point while virtual terms allow temporary constraint violations that shrink over iterations.
Example
Using PTR with a Problem::
from openscvx.algorithms import PenalizedTrustRegion
problem = Problem(dynamics, constraints, states, controls, N, time)
problem.initialize()
result = problem.solve()
Source code in openscvx/algorithms/scvx/penalized_trust_region.py
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 | |
__init__(autotuner: AutotuningBase = None, k_max: int = 200, t_max: float | None = None, lam_prox: Union[float, Dict[str, Union[float, list]]] = 0.1, lam_vc: Union[float, Dict[str, Union[float, list]]] = 1.0, lam_cost: Union[float, Dict[str, float]] = 0.01, lam_vb: float = 0.0, ep_tr: float = 0.0001, ep_vb: float = 0.0001, ep_vc: float = 1e-08, states: List[State] = None, controls: List[Control] = None)
¶
Initialize PTR with algorithm parameters and optional autotuner.
Source code in openscvx/algorithms/scvx/penalized_trust_region.py
build_iteration(dis_continuous: Callable, dis_impulsive: Callable, jax_constraints: LoweredJaxConstraints, solver_callback: Callable, settings: Config) -> Callable
¶
Fuse the discretizers, constraints, and solver into the PTR step.
Thin wrapper around
:func:~openscvx.algorithms.scvx.iteration.make_scp_iteration,
threading this algorithm's :attr:autotuner into the fused body.
Source code in openscvx/algorithms/scvx/penalized_trust_region.py
citation() -> List[str]
¶
Return BibTeX citations for the PTR algorithm.
Source code in openscvx/algorithms/scvx/penalized_trust_region.py
get_columns(verbosity: int = Verbosity.STANDARD) -> List[Column]
¶
Get the columns to display for iteration output.
Source code in openscvx/algorithms/scvx/penalized_trust_region.py
initialize(iteration_fn: Callable, emitter: Callable) -> None
¶
Store the fused SCP iteration body and per-iteration infrastructure.
iteration_fn is built and JIT-warmed by :meth:Problem.initialize;
:meth:step is a thin Python wrapper that calls it, records history,
and emits diagnostics. The boundary conditions the subproblem pins are
carried on :class:AlgorithmState (x_init_pin / x_term_pin), so
there is no solver state to prime here.
Source code in openscvx/algorithms/scvx/penalized_trust_region.py
step(state: AlgorithmState, history: AlgorithmHistory, params: dict, settings: Config) -> Tuple[AlgorithmState, bool]
¶
Execute one PTR iteration and return (next_state, converged).
Calls the fused JAX iteration body, records the per-iteration
diagnostics it returns into history, emits progress, and reports
convergence from the metrics on the next state.
Source code in openscvx/algorithms/scvx/penalized_trust_region.py
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 | |
PenalizedTrustRegionConfig
¶
Bases: BaseModel
Validates PenalizedTrustRegion configuration from dict input.
The autotuner field accepts:
None— defaults to :class:AugmentedLagrangian.- A string — class name only, default parameters.
- A dict — class name via
"type"key plus overrides. - An instance — already-constructed autotuner (pass-through).
Source code in openscvx/algorithms/__init__.py
RampProximalWeight
¶
Bases: AutotuningBase
Ramp lam_prox toward lam_prox_max then hold.
update_weights is a pure functional update on the
:class:AlgorithmState pytree; see the base-class contract.
Source code in openscvx/algorithms/autotuner/ramp_proximal_weight.py
update_weights(state: AlgorithmState, candidate: CandidateIterate, nodal_constraints: LoweredJaxConstraints, settings: Config, params: dict) -> AlgorithmState
¶
Return the next-iterate state.
Pure functional update — see class docstring.
Source code in openscvx/algorithms/autotuner/ramp_proximal_weight.py
Weights
dataclass
¶
Penalty weights for the SCP objective function.
Each SCP subproblem minimizes a weighted sum of the original cost, trust region penalty, virtual control penalty, and virtual buffer penalty. This dataclass holds the weights for each term.
Weights can be accessed directly (weights.lam_prox) or through
the algorithm's convenience properties (algorithm.lam_prox).
During SCP iteration, the autotuner may mutate these fields; those
changes are tracked in :class:AlgorithmState weight histories.
Use :meth:build to construct from user-friendly inputs (floats or
{name: weight} dicts). Use :meth:build_vb_arrays to populate
lam_vb_nodal and lam_vb_cross once symbolic constraints are
available.
Attributes:
| Name | Type | Description |
|---|---|---|
lam_prox |
Union[float, ndarray]
|
Trust region (proximal) weight. Scalar (uniform) or
array of shape |
lam_vc |
Union[float, ndarray]
|
Virtual control penalty weight. Scalar (uniform) or
array of shape |
lam_cost |
Union[float, ndarray]
|
Cost weight. Scalar (uniform across all
minimize/maximize states) or array of shape |
lam_vb |
float
|
Global virtual buffer penalty weight. Scalar default
applied to every constraint unless overridden via
|
lam_vb_nodal |
Optional[ndarray]
|
Per-node weights for nodal constraints, shape
|
lam_vb_cross |
Optional[ndarray]
|
Weights for cross-node constraints, shape
|
Source code in openscvx/algorithms/weights.py
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 | |
build(lam_prox: Union[float, Dict[str, Union[float, list, np.ndarray]]] = 0.1, lam_vc: Union[float, Dict[str, Union[float, list, np.ndarray]]] = 1.0, lam_cost: Union[float, Dict[str, float]] = 0.01, lam_vb: float = 0.0, states: Optional[List[State]] = None, controls: Optional[List[Control]] = None) -> Weights
classmethod
¶
Construct Weights from user-friendly inputs.
Accepts floats (applied uniformly) or dicts mapping state/control
names to per-variable weights. Dict inputs are expanded to dense
arrays via the resolve_lam_* methods.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lam_prox
|
Union[float, Dict[str, Union[float, list, ndarray]]]
|
Trust region weight. Float or |
0.1
|
lam_vc
|
Union[float, Dict[str, Union[float, list, ndarray]]]
|
Virtual control weight. Float or |
1.0
|
lam_cost
|
Union[float, Dict[str, float]]
|
Cost weight. Float or |
0.01
|
lam_vb
|
float
|
Virtual buffer default weight (scalar). |
0.0
|
states
|
Optional[List[State]]
|
Symbolic State objects (required when any weight is a dict). |
None
|
controls
|
Optional[List[Control]]
|
Symbolic Control objects (required when lam_prox is a dict). |
None
|
Returns:
| Type | Description |
|---|---|
Weights
|
A new Weights instance with resolved numeric values. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a dict weight is given without the required states/controls. |
Source code in openscvx/algorithms/weights.py
build_vb_arrays(N: int, nodal_constraints: list, cross_node_constraints: list, n_byof_nodal: int = 0, n_byof_cross: int = 0) -> None
¶
Build per-constraint virtual buffer weight arrays.
Inspects each symbolic constraint's shape (to account for vector
decomposition) and .weight() overrides, then populates
self.lam_vb_nodal and self.lam_vb_cross.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
N
|
int
|
Number of trajectory nodes. |
required |
nodal_constraints
|
list
|
Symbolic |
required |
cross_node_constraints
|
list
|
Symbolic |
required |
n_byof_nodal
|
int
|
Number of byof nodal constraints (each adds one column with the default weight). |
0
|
n_byof_cross
|
int
|
Number of byof cross-node constraints (each adds one entry with the default weight). |
0
|
Source code in openscvx/algorithms/weights.py
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 | |
resolve_lam_cost(lam_cost: Union[float, Dict[str, Union[float, list, np.ndarray]]], states: Optional[List[State]] = None) -> Union[float, np.ndarray]
staticmethod
¶
Resolve a lam_cost spec to a numeric value.
If lam_cost is a float it is returned as-is. If it is a dict
mapping state names to weights, it is expanded to a dense array
of shape (n_states,) using each state's _slice. States
without a minimize/maximize objective receive weight 0. States
with an objective must appear in the dict.
Dict values may be scalars (broadcast to every component) or
arrays matching the state's shape for per-component weighting,
e.g. {"position": [0, 0, 1e-6]}.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lam_cost
|
Union[float, Dict[str, Union[float, list, ndarray]]]
|
Scalar weight or |
required |
states
|
Optional[List[State]]
|
Symbolic State objects (required when lam_cost is a dict). |
None
|
Returns:
| Type | Description |
|---|---|
Union[float, ndarray]
|
float or np.ndarray of shape |
Raises:
| Type | Description |
|---|---|
ValueError
|
If lam_cost is a dict and states is |
Source code in openscvx/algorithms/weights.py
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 | |
resolve_lam_prox(lam_prox: Union[float, Dict[str, Union[float, list, np.ndarray]]], states: Optional[List[State]] = None, controls: Optional[List[Control]] = None) -> Union[float, np.ndarray]
staticmethod
¶
Resolve a lam_prox spec to a numeric value.
If lam_prox is a float it is returned as-is. If it is a dict
mapping state/control names to weights, it is expanded to a dense
array using each variable's _slice. Variables not in the dict
default to 1.0.
Dict values may be scalars, 1-D arrays (per-component), or 2-D
arrays of shape (K, n_components) for per-node-per-component
weighting. All 2-D entries must agree on K.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lam_prox
|
Union[float, Dict[str, Union[float, list, ndarray]]]
|
Scalar weight or |
required |
states
|
Optional[List[State]]
|
Symbolic State objects (required when lam_prox is a dict). |
None
|
controls
|
Optional[List[Control]]
|
Symbolic Control objects (required when lam_prox is a dict). |
None
|
Returns:
| Type | Description |
|---|---|
Union[float, ndarray]
|
float or np.ndarray of shape |
Union[float, ndarray]
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If lam_prox is a dict and states/controls is |
Source code in openscvx/algorithms/weights.py
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | |
resolve_lam_vc(lam_vc: Union[float, Dict[str, Union[float, list, np.ndarray]]], states: Optional[List[State]] = None) -> Union[float, np.ndarray]
staticmethod
¶
Resolve a lam_vc spec to a numeric value.
If lam_vc is a float it is returned as-is. If it is a dict
mapping state names to weights, it is expanded to a dense array
using each state's _slice. States not in the dict default
to 1.0.
Dict values may be scalars, 1-D arrays (per-component), or 2-D
arrays of shape (K, n_components) for per-node-per-component
weighting. All 2-D entries must agree on K.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lam_vc
|
Union[float, Dict[str, Union[float, list, ndarray]]]
|
Scalar weight or |
required |
states
|
Optional[List[State]]
|
Symbolic State objects (required when lam_vc is a dict). |
None
|
Returns:
| Type | Description |
|---|---|
Union[float, ndarray]
|
float or np.ndarray of shape |
Raises:
| Type | Description |
|---|---|
ValueError
|
If lam_vc is a dict and states is |
Source code in openscvx/algorithms/weights.py
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 | |
adaptive_state_code_to_str(code: Union[int, jnp.ndarray, np.ndarray]) -> str
¶
Map an :class:AdaptiveStateCode value (int or 0-d array) to its label.
unpack_multishot_V(V: np.ndarray, *, n_x: int, n_u: int, t_nodes: np.ndarray, states: Sequence = ()) -> MultishotPropagation
¶
Validate V layout and return :class:MultishotPropagation.
Raises:
| Type | Description |
|---|---|
ValueError
|
invalid |