augmentation
State and dynamics augmentation for continuous-time constraint satisfaction.
This module provides utilities for augmenting trajectory optimization problems with additional states and dynamics to handle continuous-time constraint satisfaction (CTCS). The CTCS method enforces path constraints continuously along the trajectory rather than just at discretization nodes.
Key functionality
- CTCS constraint grouping: Sort and group CTCS constraints by time intervals
- Constraint separation: Separate CTCS, nodal, and convex constraints
- Vector decomposition: Decompose vector constraints into scalar components
- Time augmentation: Add time state with appropriate dynamics and constraints
- CTCS dynamics augmentation: Add augmented states and time dilation control
The augmentation process transforms the original dynamics x_dot = f(x, u) into an augmented system with additional states for constraint satisfaction and time dilation.
Architecture
The CTCS method works by:
- Grouping constraints by time interval and assigning index (idx)
- Creating augmented states (one per constraint group)
- Adding penalty dynamics: aug_dot = penalty(constraint_violation)
- Adding time dilation control to slow down near constraint boundaries
Example
Augmenting dynamics with CTCS constraints::
import openscvx as ox
# Define problem
x = ox.State("x", shape=(3,))
u = ox.Control("u", shape=(2,))
# Create dynamics
xdot = u @ A # Some dynamics expression
# Define path constraint
path_constraint = (ox.Norm(x) <= 1.0).over((0, 50)) # CTCS constraint
# Augment dynamics with CTCS
from openscvx.symbolic.augmentation import augment_dynamics_with_ctcs
xdot_aug, xdelta_aug, states_aug, controls_aug = augment_dynamics_with_ctcs(
xdot=xdot,
states=[x],
controls=[u],
constraints_ctcs=[path_constraint],
N=50
)
# xdot_aug now includes augmented state dynamics
# xdelta_aug includes augmented discrete dynamics (if provided)
# states_aug includes original states + augmented states
# controls_aug includes original controls + time dilation
augment_dynamics_with_ctcs(xdot: Expr, states: List[State], controls: List[Control], constraints_ctcs: List[CTCS], N: int, xdelta: Optional[Expr] = None, *, licq_min: Union[float, Dict[int, float]] = 0.0, licq_max: Union[float, Dict[int, float]] = 0.0001) -> Tuple[Expr, Optional[Expr], List[State], List[Control]]
¶
Augment dynamics with continuous-time constraint satisfaction states.
Implements the CTCS method by adding augmented states and time dilation control to the original dynamics. For each group of CTCS constraints, an augmented state is created whose dynamics are the penalty function of constraint violations.
The CTCS method enforces path constraints continuously by: 1. Creating augmented states with dynamics = penalty(constraint_violation) 2. Constraining augmented states to stay near zero (LICQ condition) 3. Adding time dilation control to slow down near constraint boundaries
The augmented dynamics become
x_dot = f(x, u) aug_dot = penalty(g(x, u)) # For each constraint group time_dot = 1.0
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
xdot
|
Expr
|
Original dynamics expression for states |
required |
states
|
List[State]
|
List of state variables (must include a state named "time") |
required |
controls
|
List[Control]
|
List of control variables |
required |
constraints_ctcs
|
List[CTCS]
|
List of CTCS constraints (should be sorted and grouped) |
required |
N
|
int
|
Number of discretization nodes |
required |
licq_min
|
Union[float, Dict[int, float]]
|
Minimum bound for augmented states (default: 0.0).
Either a scalar (applied to all groups) or a dict mapping
CTCS group |
0.0
|
licq_max
|
Union[float, Dict[int, float]]
|
Maximum bound for augmented states (default: 1e-4).
Either a scalar (applied to all groups) or a dict mapping
CTCS group |
0.0001
|
Returns:
| Type | Description |
|---|---|
Tuple[Expr, Optional[Expr], List[State], List[Control]]
|
Tuple of: - Augmented continuous dynamics expression - Augmented discrete dynamics expression (or None if not provided) - Updated states list (original + augmented states) - Updated controls list (original + time dilation control) |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no state named "time" is found in the states list |
Example
Augment dynamics with CTCS penalty states:
x = ox.State("x", shape=(3,))
u = ox.Control("u", shape=(2,))
time = ox.State("time", shape=(1,))
xdot = u @ A # Some dynamics
constraint = (ox.Norm(x) <= 1.0).over((0, 50))
xdot_aug, xdelta_aug, states_aug, controls_aug = augment_dynamics_with_ctcs(
xdot=xdot,
states=[x, time],
controls=[u],
constraints_ctcs=[constraint],
N=50
)
states_aug includes x, time, and _ctcs_aug_0, controls_aug includes u and _time_dilation
Source code in openscvx/symbolic/augmentation.py
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 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 | |
decompose_vector_nodal_constraints(constraints_nodal: List[NodalConstraint]) -> List[NodalConstraint]
¶
Decompose vector-valued nodal constraints into scalar constraints.
Decomposes vector constraints into individual scalar constraints, which is necessary for nonconvex nodal constraints that are lowered to JAX functions. The JAX-to-CVXPY interface expects scalar constraint values at each node.
For example, a constraint with shape (3,) is decomposed into 3 separate scalar constraints using indexing. CTCS constraints don't need decomposition since they handle vector values internally.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
constraints_nodal
|
List[NodalConstraint]
|
List of NodalConstraint objects (must be canonicalized) |
required |
Returns:
| Type | Description |
|---|---|
List[NodalConstraint]
|
List of NodalConstraint objects with vector constraints decomposed into scalars. |
List[NodalConstraint]
|
Scalar constraints are passed through unchanged. |
Note
Constraints are assumed to be in canonical form: residual <= 0 or residual == 0, where residual is the lhs of the constraint.
Example
Decompose vector constraint into 3 constraints:
x = ox.State("x", shape=(3,))
constraint = (x <= 5).at([0, 10, 20]) # Vector constraint, shape (3,)
decomposed = decompose_vector_nodal_constraints([constraint])
# Returns 3 constraints: x[0] <= 5, x[1] <= 5, x[2] <= 5
Source code in openscvx/symbolic/augmentation.py
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 | |
get_nodal_constraints_from_ctcs(constraints_ctcs: List[CTCS]) -> List[tuple[Constraint, tuple[int, int]]]
¶
Extract constraints from CTCS wrappers that should be checked nodally.
Some CTCS constraints have the check_nodally flag set, indicating that the underlying constraint should be enforced both continuously (via CTCS) and discretely at the nodes. This function extracts those underlying constraints along with their node intervals.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
constraints_ctcs
|
List[CTCS]
|
List of CTCS constraint wrappers |
required |
Returns:
| Type | Description |
|---|---|
List[tuple[Constraint, tuple[int, int]]]
|
List of tuples (constraint, nodes) where: - constraint: The underlying Constraint object from CTCS with check_nodally=True - nodes: The (start, end) interval from the CTCS wrapper |
Example
Extract CTCS constraint that should also be checked at nodes:
x = ox.State("x", shape=(3,))
constraint = (x <= 5).over((10, 50), check_nodally=True)
nodal = get_nodal_constraints_from_ctcs([constraint])
Returns [(x <= 5, (10, 50))] to be enforced at nodes 10 through 49
Source code in openscvx/symbolic/augmentation.py
separate_constraints(constraint_set: ConstraintSet, n_nodes: int) -> ConstraintSet
¶
Separate and categorize constraints by type and convexity.
Moves constraints from constraint_set.unsorted into their appropriate
category fields (ctcs, nodal, nodal_convex, cross_node, cross_node_convex).
Bare Constraint objects are automatically categorized: - If they contain NodeReferences (from .at(k) calls), they become CrossNodeConstraint - Otherwise, they become NodalConstraint applied at all nodes
Constraints within CTCS wrappers that have check_nodally=True are also extracted and added to the nodal constraint lists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
constraint_set
|
ConstraintSet
|
ConstraintSet with raw constraints in |
required |
n_nodes
|
int
|
Total number of nodes in the trajectory |
required |
Returns:
| Type | Description |
|---|---|
ConstraintSet
|
The same ConstraintSet with |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a constraint is not one of the expected types |
ValueError
|
If a NodalConstraint contains NodeReferences (use bare Constraint instead) |
ValueError
|
If a CTCS constraint contains NodeReferences |
Example
Separate and categorize constraints::
x = ox.State("x", shape=(3,))
constraint_set = ConstraintSet(unsorted=[
(x <= 5).over((0, 50)), # CTCS
(x >= 0).at([0, 10, 20]), # NodalConstraint
ox.Norm(x) <= 1, # Bare -> all nodes
x.at(5) - x.at(4) <= 0.1, # Bare with NodeRef -> cross-node
])
separate_constraints(constraint_set, n_nodes=50)
assert constraint_set.is_categorized
# Access via: constraint_set.ctcs, constraint_set.nodal, etc.
Source code in openscvx/symbolic/augmentation.py
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 | |
sort_ctcs_constraints(constraints_ctcs: List[CTCS]) -> Tuple[List[CTCS], List[Tuple[int, int]], int]
¶
Sort and group CTCS constraints by time interval and assign indices.
Groups CTCS constraints by their time intervals (nodes) and assigns a unique index (idx) to each group. Constraints with the same time interval can share an augmented state (same idx), while constraints with different intervals must have different augmented states.
Grouping rules
- Constraints with the same node interval can share an idx
- Constraints with different node intervals must have different idx values
- idx values must form a contiguous block starting from 0
- Unspecified idx values are automatically assigned
- User-specified idx values are validated for consistency
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
constraints_ctcs
|
List[CTCS]
|
List of CTCS constraints to sort and group |
required |
Returns:
| Type | Description |
|---|---|
Tuple[List[CTCS], List[Tuple[int, int]], int]
|
Tuple of: - List of CTCS constraints with idx assigned to each - List of node intervals (start, end) in ascending idx order - Number of augmented states needed (number of unique idx values) |
Raises:
| Type | Description |
|---|---|
ValueError
|
If user-specified idx values are inconsistent or non-contiguous |
Example
Sort CTCS constraints by interval and index:
constraint1 = (x <= 5).over((0, 50)) # Auto-assigned idx
constraint2 = (y <= 10).over((0, 50)) # Same interval, same idx
constraint3 = (z <= 15).over((20, 80)) # Different interval, different idx
sorted_ctcs, intervals, n_aug = sort_ctcs_constraints([c1, c2, c3])
# constraint1.idx = 0, constraint2.idx = 0, constraint3.idx = 1
# intervals = [(0, 50), (20, 80)]
# n_aug = 2
Source code in openscvx/symbolic/augmentation.py
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 | |