expr
Symbolic expression package for trajectory optimization.
This package provides a comprehensive symbolic expression system for building optimization problems in openscvx. It implements an Abstract Syntax Tree (AST) framework that allows you to write optimization problems using natural mathematical notation.
Example
Import the package through the main openscvx module::
import openscvx as ox
# Create symbolic variables
x = ox.Variable("x", shape=(3,))
u = ox.Control("u", shape=(2,))
# Build expressions
cost = ox.Norm(x - [1, 2, 3])**2 + 0.1 * ox.Norm(u)**2
constraint = x[0] <= 5.0
Module Organization
The package is organized into the following modules:
Core Expressions (expr.py):
Base classes and utilities including Expr, Leaf, Parameter, Constant,
and helper functions to_expr and traverse.
Arithmetic Operations (arithmetic.py):
Fundamental arithmetic operations including Add, Sub, Mul, Div,
MatMul, Neg, and Power.
Array Operations (array.py):
Array manipulation operations including Index, Concat, Stack, Hstack,
and Vstack for indexing, slicing, and combining arrays.
Constraints (constraint.py):
Constraint types including Constraint, Equality, Inequality,
NodalConstraint, and CTCS (Continuous-Time Constraint Satisfaction).
Optimization Variables (variable.py, state.py, control.py):
Variable for general optimization variables, State for time-varying state
in trajectory problems, and Control for control inputs.
Mathematical Functions (math.py):
Trigonometric functions (Sin, Cos), exponential functions (Exp, Log,
Sqrt, Square), and nonlinear functions (PositivePart, Huber,
SmoothReLU, Max).
Linear Algebra (linalg.py):
Matrix operations (Transpose, Diag) and reductions (Sum, Norm).
Spatial Operations (spatial.py):
6-DOF operations for aerospace and robotics including QDCM (Quaternion to
Direction Cosine Matrix), SSMP (4×4 skew-symmetric matrix for quaternion
dynamics), and SSM (3×3 skew-symmetric matrix for cross products).
Lie Algebra Operations (lie.py):
Lie algebra operations for rigid body dynamics. Built-in operators include
AdjointDual (coadjoint for Coriolis/centrifugal forces) and Adjoint
(Lie bracket). jaxlie-backed operators (requires pip install openscvx[lie])
include SO3Exp, SO3Log, SE3Exp, and SE3Log for exponential and
logarithm maps on rotation and rigid transformation groups.
Constraint Specifications (constraint.py):
NodalConstraint for enforcing constraints at discrete nodes and CTCS for
continuous-time constraint satisfaction.
Signal Temporal Logic (stl.py):
Or for logical disjunction in task specifications.
Abs
¶
Bases: Expr
Element-wise absolute value function for symbolic expressions.
Computes the absolute value (|x|) of each element in the operand. Preserves the shape of the input expression. The absolute value function is convex and DCP-compliant in CVXPy.
Attributes:
| Name | Type | Description |
|---|---|---|
operand |
Expression to apply absolute value to |
Example
Define an Abs expression:
x = Variable("x", shape=(3,))
abs_x = Abs(x) # Element-wise |x|
Source code in openscvx/symbolic/expr/math.py
Add
¶
Bases: Expr
Addition operation for symbolic expressions.
Represents element-wise addition of two or more expressions. Supports broadcasting following NumPy rules. Can be created using the + operator on Expr objects.
Attributes:
| Name | Type | Description |
|---|---|---|
terms |
List of expression operands to add together |
Example
Define an Add expression:
x = ox.State("x", shape=(3,))
y = ox.State("y", shape=(3,))
z = x + y + 5 # Creates Add(x, y, Constant(5))
Source code in openscvx/symbolic/expr/arithmetic.py
__init__(*args: Union[Expr, float, int, np.ndarray])
¶
Initialize an addition operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*args
|
Union[Expr, float, int, ndarray]
|
Two or more expressions to add together |
()
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If fewer than two operands are provided |
Source code in openscvx/symbolic/expr/arithmetic.py
canonicalize() -> Expr
¶
Canonicalize addition: flatten, fold constants, and eliminate zeros.
Returns:
| Name | Type | Description |
|---|---|---|
Expr |
Expr
|
Canonical form of the addition expression |
Source code in openscvx/symbolic/expr/arithmetic.py
check_shape() -> Tuple[int, ...]
¶
Check shape compatibility and compute broadcasted result shape like NumPy.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
The broadcasted shape of all operands |
Raises:
| Type | Description |
|---|---|
ValueError
|
If operand shapes are not broadcastable |
Source code in openscvx/symbolic/expr/arithmetic.py
Adjoint
¶
Bases: Expr
Adjoint operator ad (Lie bracket) for twist-on-twist action.
Computes the adjoint action ad_ξ₁(ξ₂) which represents the Lie bracket [ξ₁, ξ₂] of two twists. This is used for velocity propagation in kinematic chains and acceleration computations.
For se(3), given twists ξ₁ = [v₁; ω₁] and ξ₂ = [v₂; ω₂]:
ad_ξ₁(ξ₂) = [ξ₁, ξ₂] = [ ω₁ × v₂ - ω₂ × v₁ ]
[ ω₁ × ω₂ ]
Equivalently using the adjoint matrix:
ad_ξ = [ [ω]× 0 ]
[ [v]× [ω]× ]
where [·]× denotes the 3x3 skew-symmetric (cross product) matrix.
Attributes:
| Name | Type | Description |
|---|---|---|
twist1 |
First 6D twist vector [v₁; ω₁] |
|
twist2 |
Second 6D twist vector [v₂; ω₂] |
Example
Compute the Lie bracket of two twists::
import openscvx as ox
twist1 = ox.State("twist1", shape=(6,))
twist2 = ox.State("twist2", shape=(6,))
bracket = ox.lie.Adjoint(twist1, twist2)
Velocity propagation in a kinematic chain::
# Child link velocity includes parent velocity plus relative motion
# V_child = Ad_T @ V_parent + joint_twist * q_dot
Note
The adjoint satisfies the Jacobi identity and is antisymmetric: ad_ξ₁(ξ₂) = -ad_ξ₂(ξ₁)
See Also
AdjointDual: The coadjoint operator for momentum dynamics
Source code in openscvx/symbolic/expr/lie/adjoint.py
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 | |
__init__(twist1: Union[Expr, float, int, np.ndarray], twist2: Union[Expr, float, int, np.ndarray])
¶
Initialize an adjoint operator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
twist1
|
Union[Expr, float, int, ndarray]
|
First 6D twist vector [v; ω] with shape (6,) |
required |
twist2
|
Union[Expr, float, int, ndarray]
|
Second 6D twist vector [v; ω] with shape (6,) |
required |
Source code in openscvx/symbolic/expr/lie/adjoint.py
check_shape() -> Tuple[int, ...]
¶
Check that inputs are 6D vectors and return output shape.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Shape (6,) for the resulting Lie bracket |
Raises:
| Type | Description |
|---|---|
ValueError
|
If either twist does not have shape (6,) |
Source code in openscvx/symbolic/expr/lie/adjoint.py
AdjointDual
¶
Bases: Expr
Coadjoint operator ad* for computing Coriolis and centrifugal forces.
Computes the coadjoint action ad*_ξ(μ) which represents the rate of change of momentum due to body rotation. This is the key term in Newton-Euler dynamics that captures Coriolis and centrifugal effects.
For se(3), given twist ξ = [v; ω] and momentum μ = [f; τ]:
ad*_ξ(μ) = [ ω × f + v × τ ]
[ ω × τ ]
This appears in the Newton-Euler equations as:
M @ ξ_dot = F_ext - ad*_ξ(M @ ξ)
where M is the spatial inertia matrix and F_ext is the external wrench.
Attributes:
| Name | Type | Description |
|---|---|---|
twist |
6D twist vector [v; ω] (linear velocity, angular velocity) |
|
momentum |
6D momentum vector [p; L] or [f; τ] (linear, angular) |
Example
Compute the bias force (Coriolis + centrifugal) for rigid body dynamics::
import openscvx as ox
twist = ox.State("twist", shape=(6,))
M = ox.Parameter("M", shape=(6, 6), value=inertia_matrix)
momentum = M @ twist
bias_force = ox.lie.AdjointDual(twist, momentum)
# In dynamics: twist_dot = M_inv @ (wrench - bias_force)
Note
The coadjoint is related to the adjoint by: ad*_ξ = -(ad_ξ)^T
For the special case of pure rotation (v=0) with diagonal inertia, the angular part reduces to the familiar ω × (J @ ω) term.
See Also
Adjoint: The adjoint operator for twist-on-twist action SSM: 3x3 skew-symmetric matrix for cross products
Source code in openscvx/symbolic/expr/lie/adjoint.py
__init__(twist: Union[Expr, float, int, np.ndarray], momentum: Union[Expr, float, int, np.ndarray])
¶
Initialize a coadjoint operator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
twist
|
Union[Expr, float, int, ndarray]
|
6D twist vector [v; ω] with shape (6,) |
required |
momentum
|
Union[Expr, float, int, ndarray]
|
6D momentum vector [p; L] with shape (6,) |
required |
Source code in openscvx/symbolic/expr/lie/adjoint.py
check_shape() -> Tuple[int, ...]
¶
Check that inputs are 6D vectors and return output shape.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Shape (6,) for the resulting coadjoint vector |
Raises:
| Type | Description |
|---|---|
ValueError
|
If twist or momentum do not have shape (6,) |
Source code in openscvx/symbolic/expr/lie/adjoint.py
All
¶
Bases: Expr
Logical AND reduction over predicates. Wraps jnp.all.
Reduces one or more Inequality predicates to a single scalar boolean using AND semantics. This is useful for:
- Combining multiple scalar predicates:
All([x >= 0, x <= 10]) - Reducing a vector predicate:
All(position >= lower_bound)
After evaluation, returns True only if ALL predicates are satisfied.
Attributes:
| Name | Type | Description |
|---|---|---|
predicates |
List of Inequality constraints to combine with AND. |
Example
Combining scalar predicates::
in_range = ox.All([x >= 0.0, x <= 10.0])
ox.Cond(in_range, 1.0, 0.0)
Reducing a vector predicate::
all_positive = ox.All(position >= 0.0) # position is shape (3,)
ox.Cond(all_positive, safe_value, unsafe_value)
Note
This operation is only supported for JAX lowering. CVXPy lowering will raise NotImplementedError since logical reductions are not DCP-compliant.
Source code in openscvx/symbolic/expr/logic.py
__init__(predicates: Union[Inequality, List[Inequality]])
¶
Initialize an All expression.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
predicates
|
Union[Inequality, List[Inequality]]
|
Single Inequality or list of Inequalities to combine. For a single vector Inequality, reduces across all elements. For a list, combines all predicates with AND. |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
If predicates is not an Inequality or list of Inequalities |
ValueError
|
If predicates list is empty |
Source code in openscvx/symbolic/expr/logic.py
canonicalize() -> Expr
¶
check_shape() -> Tuple[int, ...]
¶
Check shape and return scalar output shape.
All always reduces to a scalar boolean.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Empty tuple () representing scalar output |
Source code in openscvx/symbolic/expr/logic.py
Any
¶
Bases: Expr
Logical OR reduction over predicates. Wraps jnp.any.
Reduces one or more Inequality predicates to a single scalar boolean using OR semantics. This is useful for:
- Combining multiple scalar predicates:
Any([in_region_a, in_region_b]) - Reducing a vector predicate:
Any(position >= threshold)
After evaluation, returns True if ANY predicate is satisfied.
Attributes:
| Name | Type | Description |
|---|---|---|
predicates |
List of Inequality constraints to combine with OR. |
Example
Combining scalar predicates (OR logic)::
in_any_region = ox.Any([in_region_a, in_region_b])
ox.Cond(in_any_region, region_value, default_value)
Reducing a vector predicate::
any_above = ox.Any(position >= threshold) # position is shape (3,)
ox.Cond(any_above, triggered_value, normal_value)
Note
This operation is only supported for JAX lowering. CVXPy lowering will raise NotImplementedError since logical reductions are not DCP-compliant.
Source code in openscvx/symbolic/expr/logic.py
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 | |
__init__(predicates: Union[Inequality, List[Inequality]])
¶
Initialize an Any expression.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
predicates
|
Union[Inequality, List[Inequality]]
|
Single Inequality or list of Inequalities to combine. For a single vector Inequality, reduces across all elements. For a list, combines all predicates with OR. |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
If predicates is not an Inequality or list of Inequalities |
ValueError
|
If predicates list is empty |
Source code in openscvx/symbolic/expr/logic.py
canonicalize() -> Expr
¶
check_shape() -> Tuple[int, ...]
¶
Check shape and return scalar output shape.
Any always reduces to a scalar boolean.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Empty tuple () representing scalar output |
Source code in openscvx/symbolic/expr/logic.py
Bilerp
¶
Bases: Expr
2D bilinear interpolation for symbolic expressions.
Performs bilinear interpolation on a regular 2D grid. Given grid points (xp, yp) and corresponding values fp, computes the bilinearly interpolated value at query point (x, y). For values outside the grid, boundary values are returned (clamping, no extrapolation).
This is useful for incorporating 2D tabulated data (e.g., engine thrust as a function of altitude and Mach number, aerodynamic coefficients as a function of angle of attack and sideslip) into trajectory optimization.
Attributes:
| Name | Type | Description |
|---|---|---|
x |
Query x-coordinate (symbolic expression) |
|
y |
Query y-coordinate (symbolic expression) |
|
xp |
1D array of x grid coordinates (must be increasing), length N |
|
yp |
1D array of y grid coordinates (must be increasing), length M |
|
fp |
2D array of values with shape (N, M), where fp[i, j] is the value at grid point (xp[i], yp[j]) |
Example
Interpolate engine thrust from altitude and Mach number::
import openscvx as ox
import numpy as np
# Grid coordinates
alt_grid = np.array([0, 5000, 10000, 15000, 20000]) # meters
mach_grid = np.array([0.0, 0.5, 1.0, 1.5, 2.0])
# Thrust values: thrust_table[i, j] = thrust at (alt_grid[i], mach_grid[j])
thrust_table = np.array([...]) # shape (5, 5)
altitude = ox.State("altitude", shape=(1,))
mach = ox.State("mach", shape=(1,))
thrust = ox.Bilerp(altitude[0], mach[0], alt_grid, mach_grid, thrust_table)
Note
- xp and yp must be strictly increasing
- fp must have shape (len(xp), len(yp))
- For query points outside the grid, boundary values are returned
- This node is only supported in JAX lowering (dynamics/cost), not CVXPy
Source code in openscvx/symbolic/expr/math.py
799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 | |
__init__(x: Union[Expr, float, int, np.ndarray], y: Union[Expr, float, int, np.ndarray], xp: Union[Expr, float, int, np.ndarray], yp: Union[Expr, float, int, np.ndarray], fp: Union[Expr, float, int, np.ndarray])
¶
Initialize a 2D bilinear interpolation node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Union[Expr, float, int, ndarray]
|
Query x-coordinate. Can be a scalar symbolic expression. |
required |
y
|
Union[Expr, float, int, ndarray]
|
Query y-coordinate. Can be a scalar symbolic expression. |
required |
xp
|
Union[Expr, float, int, ndarray]
|
1D array of x grid coordinates. Must be increasing. |
required |
yp
|
Union[Expr, float, int, ndarray]
|
1D array of y grid coordinates. Must be increasing. |
required |
fp
|
Union[Expr, float, int, ndarray]
|
2D array of values with shape (len(xp), len(yp)). |
required |
Source code in openscvx/symbolic/expr/math.py
canonicalize() -> Expr
¶
Canonicalize by canonicalizing all operands.
Source code in openscvx/symbolic/expr/math.py
check_shape() -> Tuple[int, ...]
¶
Output shape is scalar (single interpolated value).
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Empty tuple (scalar output) |
Raises:
| Type | Description |
|---|---|
ValueError
|
If grid arrays have invalid shapes |
Source code in openscvx/symbolic/expr/math.py
Block
¶
Bases: Expr
Block matrix/tensor construction from nested arrays of expressions.
Assembles a block matrix (or N-D tensor) from a nested list of expressions, analogous to numpy.block(). Each inner list represents a row of blocks, and blocks within the same row are concatenated horizontally, while rows are stacked vertically.
This provides a convenient way to construct matrices from sub-expressions without manually nesting Stack/Hstack/Vstack operations.
Attributes:
| Name | Type | Description |
|---|---|---|
blocks |
Nested list of expressions forming the block structure (each expression can be a scalar, 1D, 2D, or N-D tensor) |
Example
Build a 2D rotation matrix::
import openscvx as ox
from openscvx.symbolic.expr.array import Block
theta = ox.Variable("theta", shape=(1,))
R = Block([
[ox.Cos(theta), -ox.Sin(theta)],
[ox.Sin(theta), ox.Cos(theta)]
]) # Result shape (2, 2)
Build a block diagonal matrix::
A = ox.State("A", shape=(2, 2))
B = ox.State("B", shape=(3, 3))
zeros_23 = ox.Constant(np.zeros((2, 3)))
zeros_32 = ox.Constant(np.zeros((3, 2)))
block_diag = Block([
[A, zeros_23],
[zeros_32, B]
]) # Result shape (5, 5)
Build from scalars and expressions::
x = ox.State("x", shape=(1,))
y = ox.State("y", shape=(1,))
# Scalars are automatically promoted to 1D arrays
M = Block([
[x, 0],
[0, y]
]) # Result shape (2, 2)
Note
- All blocks in the same row must have the same height (first dimension)
- All blocks in the same column must have the same width (second dimension)
- For N-D tensors (3D+), all trailing dimensions must match across all blocks
- Scalar values and raw Python lists are automatically wrapped via to_expr()
- 1D arrays are treated as row vectors when determining block dimensions
- N-D tensors are supported for JAX lowering; CVXPy only supports 2D blocks
Source code in openscvx/symbolic/expr/array.py
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 | |
__init__(blocks: List[Union[Expr, float, int, np.ndarray, List]])
¶
Initialize a block matrix construction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
blocks
|
List[Union[Expr, float, int, ndarray, List]]
|
A nested list of expressions. Can be either: - 2D: [[row1_blocks], [row2_blocks], ...] for multiple rows - 1D: [block1, block2, ...] for a single row (auto-promoted to [[...]]) Raw values (numbers, lists, numpy arrays) are automatically converted to Constant expressions. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If blocks is empty |
Source code in openscvx/symbolic/expr/array.py
canonicalize() -> Expr
¶
Canonicalize by recursively canonicalizing all blocks.
If the block contains only a single element ([[a]]), returns the canonicalized element directly to simplify the expression tree.
Source code in openscvx/symbolic/expr/array.py
check_shape() -> Tuple[int, ...]
¶
Validate block dimensions and compute output shape.
For 2D blocks, returns (total_rows, total_cols). For N-D blocks, returns the shape after assembling blocks along the first two axes, with trailing dimensions preserved.
Returns:
| Type | Description |
|---|---|
Tuple[int, ...]
|
Tuple representing the assembled block array shape |
Raises:
| Type | Description |
|---|---|
ValueError
|
If block dimensions are incompatible |
Source code in openscvx/symbolic/expr/array.py
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 | |
BoundaryType
¶
Bases: str, Enum
Enumeration of boundary condition types for state variables.
This enum allows users to specify boundary conditions using plain strings while maintaining type safety internally. Boundary conditions control how the optimizer handles initial and final state values.
Attributes:
| Name | Type | Description |
|---|---|---|
FIXED |
str
|
State value is fixed to a specific value |
FREE |
str
|
State value is free to be optimized within bounds |
MINIMIZE |
str
|
Objective term to minimize the state value |
MAXIMIZE |
str
|
Objective term to maximize the state value |
Example
Can use either enum or string:
BoundaryType.FIXED
"fixed" # Equivalent
Source code in openscvx/symbolic/expr/state.py
CTCS
¶
Bases: Expr
Continuous-Time Constraint Satisfaction using augmented state dynamics.
CTCS enables strict continuous-time constraint enforcement in discretized trajectory optimization by augmenting the state vector with additional states whose dynamics are the constraint violation penalties. By constraining these augmented states to remain at zero throughout the trajectory, the original constraints are guaranteed to be satisfied continuously, not just at discrete nodes.
How it works:
- Each constraint (in canonical form: lhs <= 0) is wrapped in a penalty function
- Augmented states s_aug_i are added with dynamics: ds_aug_i/dt = sum(penalty_j(lhs_j)) for all CTCS constraints j in group i
- Each augmented state is constrained: s_aug_i(t) = 0 for all t (strictly enforced)
- Since s_aug_i integrates the penalties, s_aug_i = 0 implies all penalties in the group are zero, which means all constraints in the group are satisfied continuously
Grouping and augmented states:
- CTCS constraints with the same node interval are grouped into a single augmented state by default (their penalties are summed)
- CTCS constraints with different node intervals create separate augmented states
- Using the
idxparameter explicitly assigns constraints to specific augmented states, allowing manual control over grouping - Each unique group creates one augmented state named
_ctcs_aug_0,_ctcs_aug_1, etc.
This is particularly useful for:
- Path constraints that must hold throughout the entire trajectory (not just at nodes)
- Obstacle avoidance where constraint violation between nodes could be catastrophic
- State limits that should be respected continuously (e.g., altitude > 0 for aircraft)
- Ensuring smooth, feasible trajectories between discretization points
Penalty functions (applied to constraint violations):
- squared_relu: Square(PositivePart(lhs)) - smooth, differentiable (default)
- huber: Huber(PositivePart(lhs)) - less sensitive to outliers than squared
- smooth_relu: SmoothReLU(lhs) - smooth approximation of ReLU
Attributes:
| Name | Type | Description |
|---|---|---|
constraint |
The wrapped Constraint (typically Inequality) to enforce continuously |
|
penalty |
Penalty function type ('squared_relu', 'huber', or 'smooth_relu') |
|
nodes |
Optional (start, end) tuple specifying the interval for enforcement, or None to enforce over the entire trajectory |
|
idx |
Optional grouping index for managing multiple augmented states. CTCS constraints with the same idx and nodes are grouped together, sharing an augmented state. If None, auto-assigned based on node intervals. |
|
check_nodally |
Whether to also enforce the constraint at discrete nodes for additional numerical robustness (creates both continuous and nodal constraints) |
Example
Single augmented state (default behavior - same node interval):
altitude = State("alt", shape=(1,))
constraints = [
(altitude >= 10).over((0, 10)), # Both constraints share
(altitude <= 1000).over((0, 10)) # one augmented state
]
Multiple augmented states (different node intervals):
constraints = [
(altitude >= 10).over((0, 5)), # Creates _ctcs_aug_0
(altitude >= 20).over((5, 10)) # Creates _ctcs_aug_1
]
Manual grouping with idx parameter:
constraints = [
(altitude >= 10).over((0, 10), idx=0), # Group 0
(velocity <= 100).over((0, 10), idx=1), # Group 1 (separate state)
(altitude <= 1000).over((0, 10), idx=0) # Also group 0
]
Source code in openscvx/symbolic/expr/constraint.py
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 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 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 | |
__init__(constraint: Constraint, penalty: str = 'squared_relu', nodes: Optional[Tuple[int, int]] = None, idx: Optional[int] = None, check_nodally: bool = False)
¶
Initialize a CTCS constraint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
constraint
|
Constraint
|
The Constraint to enforce continuously (typically an Inequality) |
required |
penalty
|
str
|
Penalty function type. Options: - 'squared_relu': Square(PositivePart(lhs)) - default, smooth, differentiable - 'huber': Huber(PositivePart(lhs)) - robust to outliers - 'smooth_relu': SmoothReLU(lhs) - smooth ReLU approximation |
'squared_relu'
|
nodes
|
Optional[Tuple[int, int]]
|
Optional (start, end) tuple of node indices defining the enforcement interval. None means enforce over the entire trajectory. Must satisfy start < end. CTCS constraints with the same nodes are automatically grouped together. |
None
|
idx
|
Optional[int]
|
Optional grouping index for multiple augmented states. Allows organizing multiple CTCS constraints with separate augmented state variables. If None, constraints are auto-grouped by their node intervals. Explicitly setting idx allows manual control over which constraints share an augmented state. |
None
|
check_nodally
|
bool
|
If True, also enforce the constraint at discrete nodes for numerical stability (creates both continuous and nodal constraints). Defaults to False. |
False
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If constraint is not a Constraint instance |
ValueError
|
If nodes is not None or a 2-tuple of integers |
ValueError
|
If nodes[0] >= nodes[1] (invalid interval) |
Source code in openscvx/symbolic/expr/constraint.py
canonicalize() -> Expr
¶
Canonicalize the inner constraint while preserving CTCS parameters.
Returns:
| Name | Type | Description |
|---|---|---|
CTCS |
Expr
|
A new CTCS with canonicalized inner constraint and same parameters |
Source code in openscvx/symbolic/expr/constraint.py
check_shape() -> Tuple[int, ...]
¶
Validate the constraint and penalty expression shapes.
CTCS transforms the wrapped constraint into a penalty expression that is summed (integrated) over the trajectory, always producing a scalar result.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Empty tuple () representing scalar shape |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the wrapped constraint has invalid shape |
ValueError
|
If the generated penalty expression is not scalar |
Source code in openscvx/symbolic/expr/constraint.py
children() -> List[Expr]
¶
Return the wrapped constraint as the only child.
Returns:
| Name | Type | Description |
|---|---|---|
list |
List[Expr]
|
Single-element list containing the wrapped constraint |
over(interval: tuple[int, int]) -> CTCS
¶
Set or update the continuous interval for this CTCS constraint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
interval
|
tuple[int, int]
|
Tuple of (start, end) node indices defining the enforcement interval |
required |
Returns:
| Name | Type | Description |
|---|---|---|
CTCS |
CTCS
|
New CTCS constraint with the specified interval |
Example
Define constraint over range:
constraint = (altitude >= 10).over((0, 50))
Update interval to cover different range:
constraint_updated = constraint.over((50, 100))
Source code in openscvx/symbolic/expr/constraint.py
penalty_expr() -> Expr
¶
Build the penalty expression for this CTCS constraint.
Transforms the constraint's left-hand side (in canonical form: lhs <= 0) into a penalty expression using the specified penalty function. The penalty is zero when the constraint is satisfied and positive when violated.
This penalty expression becomes part of the dynamics of an augmented state. Multiple CTCS constraints in the same group (same idx) have their penalties summed: ds_aug_i/dt = sum(penalty_j) for all j in group i. By constraining s_aug_i(t) = 0 for all t, we ensure all penalties in the group are zero, which strictly enforces all constraints in the group continuously.
Returns:
| Name | Type | Description |
|---|---|---|
Expr |
Expr
|
Sum of the penalty function applied to the constraint violation |
Raises:
| Type | Description |
|---|---|
ValueError
|
If an unknown penalty type is specified |
Note
This method is used internally during problem compilation to create augmented state dynamics. Multiple penalty expressions with the same idx are summed together before being added to the dynamics vector via Concat.
Source code in openscvx/symbolic/expr/constraint.py
Concat
¶
Bases: Expr
Concatenation operation for symbolic expressions.
Concatenates a sequence of expressions along their first dimension. All inputs must have the same rank and matching dimensions except for the first dimension.
Attributes:
| Name | Type | Description |
|---|---|---|
exprs |
Tuple of expressions to concatenate |
Example
Define a Concat expression:
x = ox.State("x", shape=(3,))
y = ox.State("y", shape=(4,))
z = Concat(x, y) # Creates Concat(x, y), result shape (7,)
Source code in openscvx/symbolic/expr/array.py
__init__(*exprs: Expr)
¶
Initialize a concatenation operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*exprs
|
Expr
|
Expressions to concatenate along the first dimension |
()
|
Source code in openscvx/symbolic/expr/array.py
canonicalize() -> Expr
¶
Canonicalize concatenation by canonicalizing all operands.
Returns:
| Name | Type | Description |
|---|---|---|
Expr |
Expr
|
Canonical form of the concatenation expression |
Source code in openscvx/symbolic/expr/array.py
check_shape() -> Tuple[int, ...]
¶
Check concatenation shape compatibility and return result shape.
Source code in openscvx/symbolic/expr/array.py
Cond
¶
Bases: Expr
Conditional expression for JAX-traceable branching.
Implements a conditional expression that selects between two branches based
on a predicate. This wraps jax.lax.cond to enable conditional logic in
symbolic expressions for dynamics and constraints.
The predicate can be:
- A single Inequality constraint (created with <= or >=)
- A list of Inequality constraints (AND semantics, shorthand for All([...]))
- An All expression for explicit AND semantics
- An Any expression for OR semantics
- None for purely node-based switching (requires node_ranges)
After canonicalization, each constraint is in the form lhs <= 0, so the
predicate evaluates to True when the constraint is satisfied (lhs <= 0) and
False when violated (lhs > 0).
The true and false branches must have broadcastable shapes (following JAX/NumPy broadcasting rules).
Optionally, the conditional can be restricted to specific node ranges using
the node_ranges parameter. Outside these ranges, the false branch is
always evaluated.
Attributes:
| Name | Type | Description |
|---|---|---|
predicate |
The predicate expression (All, Any, or single Inequality). |
|
true_branch |
Expression to evaluate when predicate is True |
|
false_branch |
Expression to evaluate when predicate is False |
|
node_ranges |
Optional list of (start, end) tuples specifying node ranges where the conditional is active. None means active at all nodes. |
Example
Conditional velocity limit based on distance::
distance = ox.Norm(position - obstacle)
expr = ox.Cond(
distance <= safety_threshold, # predicate: True when close
5.0, # true branch: slow speed
10.0 # false branch: fast speed
)
Multiple predicates with AND semantics (explicit)::
expr = ox.Cond(
ox.All([x >= 0.0, x <= 10.0]), # True when x in [0, 10]
1.0, # in range
0.0 # out of range
)
Multiple predicates with OR semantics::
expr = ox.Cond(
ox.Any([in_region_a, in_region_b]), # True if in either region
region_value,
default_value
)
Reduce vector predicate::
expr = ox.Cond(
ox.All(position >= lower_bound), # True if all elements satisfy
safe_value,
unsafe_value
)
Conditional active only during specific trajectory phases::
expr = ox.Cond(
distance <= safety_threshold,
5.0,
10.0,
node_ranges=[(0, 2), (5, 7)] # active at nodes 0-1 and 5-6
)
Purely node-based switching (no predicate)::
expr = ox.Cond(
None, # no predicate
boost_thrust, # true branch at specified nodes
coast_thrust, # false branch elsewhere
node_ranges=[(0, 10), (20, 30)] # boost at nodes 0-9 and 20-29
)
Note
This operation is only supported for JAX lowering. CVXPy lowering will raise NotImplementedError since conditional logic is not DCP-compliant.
Source code in openscvx/symbolic/expr/logic.py
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 | |
__init__(pred: Union[Inequality, List[Inequality], All, Any, None], true_branch: Union[Expr, float, int, np.ndarray], false_branch: Union[Expr, float, int, np.ndarray], node_ranges: Optional[List[Tuple[int, int]]] = None)
¶
Initialize a conditional expression.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pred
|
Union[Inequality, List[Inequality], All, Any, None]
|
Predicate for the conditional. Can be: - Single Inequality (e.g., x <= 5) - List of Inequalities (AND semantics, shorthand for All([...])) - All expression for explicit AND - Any expression for OR semantics - None for purely node-based switching (requires node_ranges) |
required |
true_branch
|
Union[Expr, float, int, ndarray]
|
Expression to evaluate when predicate is True |
required |
false_branch
|
Union[Expr, float, int, ndarray]
|
Expression to evaluate when predicate is False |
required |
node_ranges
|
Optional[List[Tuple[int, int]]]
|
Optional list of (start, end) tuples specifying node ranges where the conditional is active. Each tuple defines a half-open interval [start, end) of node indices. Outside these ranges, the false branch is always evaluated. None means active at all nodes. Required when pred is None. |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If pred is not a valid predicate type |
ValueError
|
If node_ranges contains invalid ranges or pred=None without node_ranges |
Source code in openscvx/symbolic/expr/logic.py
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 | |
canonicalize() -> Expr
¶
Canonicalize by canonicalizing all children, preserving node_ranges.
Source code in openscvx/symbolic/expr/logic.py
check_shape() -> Tuple[int, ...]
¶
Check and return the output shape of the conditional.
The predicate must be scalar (or reduce to scalar via All/Any), and the true and false branches must have broadcastable shapes. The output shape is the broadcasted shape of the two branches.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
The broadcasted shape of true_branch and false_branch |
Raises:
| Type | Description |
|---|---|
ValueError
|
If predicate is not scalar or branches have incompatible shapes |
Source code in openscvx/symbolic/expr/logic.py
children()
¶
Return the child expressions: predicate (if any), true branch, and false branch.
Source code in openscvx/symbolic/expr/logic.py
Constant
¶
Bases: Expr
Constant value expression.
Represents a constant numeric value in the expression tree. Constants are automatically normalized (squeezed) upon construction to ensure consistency.
Attributes:
| Name | Type | Description |
|---|---|---|
value |
The numpy array representing the constant value (squeezed) |
Example
Define constants:
c1 = Constant(5.0) # Scalar constant
c2 = Constant([1, 2, 3]) # Vector constant
c3 = to_expr(10) # Also creates a Constant
Source code in openscvx/symbolic/expr/expr.py
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 | |
__init__(value: np.ndarray)
¶
Initialize a constant expression.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
ndarray
|
Numeric value or numpy array to wrap as a constant. Will be converted to numpy array and squeezed. |
required |
Source code in openscvx/symbolic/expr/expr.py
canonicalize() -> Expr
¶
Constants are already in canonical form.
Returns:
| Name | Type | Description |
|---|---|---|
Expr |
Expr
|
Returns self since constants are already canonical |
check_shape() -> Tuple[int, ...]
¶
Return the shape of this constant's value.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
The shape of the constant's numpy array value |
Source code in openscvx/symbolic/expr/expr.py
Constraint
¶
Bases: Expr
Abstract base class for optimization constraints.
Constraints represent relationships between expressions that must be satisfied in the optimization problem. This base class provides common functionality for both equality and inequality constraints.
Attributes:
| Name | Type | Description |
|---|---|---|
lhs |
Left-hand side expression |
|
rhs |
Right-hand side expression |
|
is_convex |
Flag indicating if the constraint is known to be convex |
Note
Constraints are canonicalized to standard form: (lhs - rhs) {op} 0
Source code in openscvx/symbolic/expr/constraint.py
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 | |
__init__(lhs: Expr, rhs: Expr)
¶
at(nodes: Union[list, tuple]) -> NodalConstraint
¶
Apply this constraint only at specific discrete nodes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nodes
|
Union[list, tuple]
|
List of node indices where the constraint should be enforced |
required |
Returns:
| Type | Description |
|---|---|
NodalConstraint
|
NodalConstraint wrapping this constraint with node specification |
Source code in openscvx/symbolic/expr/constraint.py
canonicalize() -> Expr
¶
Canonicalize constraint to standard form: (lhs - rhs) {op} 0.
This works for both Equality and Inequality by using type(self) to construct the appropriate subclass type.
Source code in openscvx/symbolic/expr/constraint.py
check_shape() -> Tuple[int, ...]
¶
Check that constraint operands are broadcastable and return the shape.
Returns the broadcasted shape of lhs and rhs, which represents the shape of the constraint residual (lhs - rhs). Vector constraints are interpreted element-wise.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
The broadcasted shape of lhs and rhs |
Source code in openscvx/symbolic/expr/constraint.py
convex() -> Constraint
¶
Mark this constraint as convex for CVXPy lowering.
Returns:
| Type | Description |
|---|---|
Constraint
|
Self with convex flag set to True (enables method chaining) |
over(interval: tuple[int, int], penalty: str = 'squared_relu', idx: Optional[int] = None, check_nodally: bool = False) -> CTCS
¶
Apply this constraint over a continuous interval using CTCS.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
interval
|
tuple[int, int]
|
Tuple of (start, end) node indices for the continuous interval |
required |
penalty
|
str
|
Penalty function type ("squared_relu", "huber", "smooth_relu") |
'squared_relu'
|
idx
|
Optional[int]
|
Optional grouping index for multiple augmented states |
None
|
check_nodally
|
bool
|
Whether to also enforce this constraint nodally |
False
|
Returns:
| Type | Description |
|---|---|
CTCS
|
CTCS constraint wrapping this constraint with interval specification |
Source code in openscvx/symbolic/expr/constraint.py
Control
¶
Bases: Variable
Control input variable for trajectory optimization problems.
Control represents control input variables (actuator commands) in a trajectory optimization problem. Unlike State variables which evolve according to dynamics, Controls are direct decision variables that the optimizer can freely adjust (within specified bounds) at each time step to influence the system dynamics.
Controls are conceptually similar to State variables but simpler - they don't have boundary conditions (initial/final specifications) since controls are typically not constrained at the endpoints. Like States, Controls support:
- Min/max bounds to enforce actuator limits
- Initial trajectory guesses to help the optimizer converge
Common examples of control inputs include:
- Thrust magnitude and direction for spacecraft/rockets
- Throttle settings for engines
- Steering angles for vehicles
- Torques for robotic manipulators
- Force/acceleration commands
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Unique name identifier for this control variable |
_shape |
tuple[int, ...]
|
Shape of the control vector (typically 1D like (3,) for 3D thrust) |
_slice |
slice | None
|
Internal slice information for variable indexing |
_min |
ndarray | None
|
Minimum bounds for each element of the control |
_max |
ndarray | None
|
Maximum bounds for each element of the control |
_guess |
ndarray | None
|
Initial guess for the control trajectory (n_points, n_controls) |
Example
Scalar throttle control bounded [0, 1]:
throttle = Control("throttle", shape=(1,))
throttle.min = [0.0]
throttle.max = [1.0]
throttle.guess = np.full((50, 1), 0.5) # Start at 50% throttle
3D thrust vector for spacecraft:
thrust = Control("thrust", shape=(3,))
thrust.min = [-10, -10, 0] # No downward thrust
thrust.max = [10, 10, 50] # Limited thrust
thrust.guess = np.zeros((50, 3)) # Initialize with zero thrust
2D steering control (left/right, forward/backward):
steer = Control("steer", shape=(2,))
steer.min = [-1, -1]
steer.max = [1, 1]
steer.guess = np.linspace([0, 0], [0, 1], 50) # Gradual acceleration
Source code in openscvx/symbolic/expr/control.py
8 9 10 11 12 13 14 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 | |
scaling_max: Optional[np.ndarray]
property
writable
¶
Get the scaling maximum bounds for the control variables.
Returns:
| Type | Description |
|---|---|
Optional[ndarray]
|
Array of scaling maximum values for each control variable element, or None if not set. |
scaling_min: Optional[np.ndarray]
property
writable
¶
Get the scaling minimum bounds for the control variables.
Returns:
| Type | Description |
|---|---|
Optional[ndarray]
|
Array of scaling minimum values for each control variable element, or None if not set. |
__init__(name: str, shape: Tuple[int, ...])
¶
Initialize a Control object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Name identifier for the control variable |
required |
shape
|
Tuple[int, ...]
|
Shape of the control vector (typically 1D tuple like (3,)) |
required |
Source code in openscvx/symbolic/expr/control.py
Cos
¶
Bases: Expr
Element-wise cosine function for symbolic expressions.
Computes the cosine of each element in the operand. Preserves the shape of the input expression.
Attributes:
| Name | Type | Description |
|---|---|---|
operand |
Expression to apply cosine function to |
Example
Define a Cos expression:
theta = Variable("theta", shape=(3,))
cos_theta = Cos(theta)
Source code in openscvx/symbolic/expr/math.py
CrossNodeConstraint
¶
Bases: Expr
A constraint that couples specific trajectory nodes via .at(k) references.
Unlike NodalConstraint which applies a constraint pattern at multiple nodes (via vmapping), CrossNodeConstraint is a single constraint with fixed node indices embedded in the expression via NodeReference nodes.
CrossNodeConstraint is created automatically when a bare Constraint contains NodeReference nodes (from .at(k) calls). Users should NOT manually wrap cross-node constraints - they are auto-detected during constraint separation.
Key differences from NodalConstraint:
- NodalConstraint: Same constraint evaluated at multiple nodes via vmapping. Signature: (x, u, node, params) → scalar, vmapped to (N, n_x) inputs.
- CrossNodeConstraint: Single constraint coupling specific fixed nodes. Signature: (X, U, params) → scalar, operates on full trajectory arrays.
Lowering:
- Non-convex: Lowered to JAX with automatic differentiation for SCP linearization
- Convex: Lowered to CVXPy and solved directly by the convex solver
Attributes:
| Name | Type | Description |
|---|---|---|
constraint |
The wrapped Constraint containing NodeReference nodes |
Example
Rate limit constraint (auto-detected as CrossNodeConstraint):
position = State("pos", shape=(3,))
# This creates a CrossNodeConstraint automatically:
rate_limit = position.at(5) - position.at(4) <= 0.1
# Mark as convex if the constraint is convex:
rate_limit_convex = (position.at(5) - position.at(4) <= 0.1).convex()
Creating multiple cross-node constraints with a loop:
constraints = []
for k in range(1, N):
# Each iteration creates one CrossNodeConstraint
rate_limit = position.at(k) - position.at(k-1) <= max_step
constraints.append(rate_limit)
Note
Do NOT use .at([...]) on cross-node constraints. The nodes are already specified via .at(k) inside the expression. Using .at([...]) will raise an error during constraint separation.
Source code in openscvx/symbolic/expr/constraint.py
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 | |
is_convex: bool
property
¶
Whether the underlying constraint is marked as convex.
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
True if the constraint is convex, False otherwise |
__init__(constraint: Constraint)
¶
Initialize a CrossNodeConstraint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
constraint
|
Constraint
|
The Constraint containing NodeReference nodes. Must contain at least one NodeReference (from .at(k) calls). |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
If constraint is not a Constraint instance |
Source code in openscvx/symbolic/expr/constraint.py
canonicalize() -> Expr
¶
Canonicalize the wrapped constraint.
Returns:
| Name | Type | Description |
|---|---|---|
CrossNodeConstraint |
Expr
|
A new CrossNodeConstraint with canonicalized inner constraint |
Source code in openscvx/symbolic/expr/constraint.py
check_shape() -> Tuple[int, ...]
¶
Validate the wrapped constraint's shape.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Empty tuple () representing scalar shape |
children() -> List[Expr]
¶
Return the wrapped constraint as the only child.
Returns:
| Type | Description |
|---|---|
List[Expr]
|
Single-element list containing the wrapped constraint |
convex() -> CrossNodeConstraint
¶
Mark the underlying constraint as convex for CVXPy lowering.
Returns:
| Type | Description |
|---|---|
CrossNodeConstraint
|
Self with underlying constraint's convex flag set to True |
Diag
¶
Bases: Expr
Diagonal matrix construction from a vector.
Creates a square diagonal matrix from a 1D vector. The vector elements become the diagonal entries, with all off-diagonal entries set to zero. This is analogous to numpy.diag() or jax.numpy.diag().
Note
Currently only supports creating diagonal matrices from vectors. Extracting diagonals from matrices is not yet implemented.
Attributes:
| Name | Type | Description |
|---|---|---|
operand |
1D vector expression to place on the diagonal |
Example
Define a Diag:
v = Variable("v", shape=(3,))
D = Diag(v) # Creates a (3, 3) diagonal matrix
Source code in openscvx/symbolic/expr/linalg.py
__init__(operand: Union[Expr, float, int, np.ndarray])
¶
Initialize a diagonal matrix operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operand
|
Union[Expr, float, int, ndarray]
|
1D vector expression to place on the diagonal |
required |
check_shape() -> Tuple[int, ...]
¶
Diag converts a vector (n,) to a diagonal matrix (n,n).
Source code in openscvx/symbolic/expr/linalg.py
Div
¶
Bases: Expr
Element-wise division operation for symbolic expressions.
Represents element-wise division (left / right). Supports broadcasting following NumPy rules. Can be created using the / operator on Expr objects.
Attributes:
| Name | Type | Description |
|---|---|---|
left |
Numerator expression |
|
right |
Denominator expression |
Example
Define a Div expression
x = ox.State("x", shape=(3,))
y = ox.State("y", shape=(3,))
z = x / y # Creates Div(x, y)
Source code in openscvx/symbolic/expr/arithmetic.py
__init__(left: Union[Expr, float, int, np.ndarray], right: Union[Expr, float, int, np.ndarray])
¶
canonicalize() -> Expr
¶
Canonicalize division: fold constants if both sides are constants.
Returns:
| Name | Type | Description |
|---|---|---|
Expr |
Expr
|
Canonical form of the division expression |
Source code in openscvx/symbolic/expr/arithmetic.py
check_shape() -> Tuple[int, ...]
¶
Check shape compatibility and compute broadcasted result shape like NumPy.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
The broadcasted shape of both operands |
Raises:
| Type | Description |
|---|---|
ValueError
|
If operand shapes are not broadcastable |
Source code in openscvx/symbolic/expr/arithmetic.py
Equality
¶
Bases: Constraint
Equality constraint for optimization problems.
Represents an equality constraint: lhs == rhs. Can be created using the == operator on Expr objects.
Example
Define an Equality constraint:
x = ox.State("x", shape=(3,))
constraint = x == 0 # Creates Equality(x, Constant(0))
Source code in openscvx/symbolic/expr/constraint.py
Exp
¶
Bases: Expr
Element-wise exponential function for symbolic expressions.
Computes e^x for each element in the operand, where e is Euler's number. Preserves the shape of the input expression.
Attributes:
| Name | Type | Description |
|---|---|---|
operand |
Expression to apply exponential function to |
Example
Define an Exp expression:
x = Variable("x", shape=(3,))
exp_x = Exp(x)
Source code in openscvx/symbolic/expr/math.py
Expr
¶
Base class for symbolic expressions in optimization problems.
Expr is the foundation of the symbolic expression system in openscvx. It represents nodes in an abstract syntax tree (AST) for mathematical expressions. Expressions support:
- Arithmetic operations: +, -, *, /, @, **
- Comparison operations: ==, <=, >=
- Indexing and slicing: []
- Transposition: .T property
- Shape checking and validation
- Canonicalization (algebraic simplification)
All Expr subclasses implement a tree structure where each node can have child expressions accessed via the children() method.
Attributes:
| Name | Type | Description |
|---|---|---|
__array_priority__ |
Priority for operations with numpy arrays (set to 1000) |
Note
When used in operations with numpy arrays, Expr objects take precedence, allowing symbolic expressions to wrap numeric values automatically.
Source code in openscvx/symbolic/expr/expr.py
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 | |
T: Transpose
property
¶
Transpose property for matrix expressions.
Returns:
| Name | Type | Description |
|---|---|---|
Transpose |
Transpose
|
A Transpose expression wrapping this expression |
Example
Create a transpose:
A = ox.State("A", shape=(3, 4))
A_T = A.T # Creates Transpose(A), result shape (4, 3)
at(k: int) -> NodeReference
¶
Reference this expression at a specific trajectory node.
This method enables inter-node constraints where you can reference the value of an expression at different time steps. Common patterns include rate limits and multi-step dependencies.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
k
|
int
|
Absolute node index (integer) in the trajectory. Can be positive (0, 1, 2, ...) or negative (-1 for last node). |
required |
Returns:
| Name | Type | Description |
|---|---|---|
NodeReference |
NodeReference
|
An expression representing this expression at node k |
Example
Rate limit constraint (applied across trajectory using a loop):
position = State("pos", shape=(3,))
# Create rate limit for each node
constraints = [
(ox.linalg.Norm(position.at(k) - position.at(k-1)) <= 0.1).at([k])
for k in range(1, N)
]
Multi-step dependency:
state = State("x", shape=(1,))
# Fibonacci-like recurrence
constraints = [
(state.at(k) == state.at(k-1) + state.at(k-2)).at([k])
for k in range(2, N)
]
Performance Note
Cross-node constraints use dense Jacobian storage which can be memory-intensive for large N (>100 nodes). See LoweredCrossNodeConstraint documentation for details on memory usage and future sparse Jacobian support.
Source code in openscvx/symbolic/expr/expr.py
canonicalize() -> Expr
¶
Return a canonical (simplified) form of this expression.
Canonicalization performs algebraic simplifications such as: - Constant folding (e.g., 2 + 3 → 5) - Identity elimination (e.g., x + 0 → x, x * 1 → x) - Flattening nested operations (e.g., Add(Add(a, b), c) → Add(a, b, c)) - Algebraic rewrites (e.g., constraints to standard form)
Returns:
| Name | Type | Description |
|---|---|---|
Expr |
Expr
|
A canonical version of this expression |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If canonicalization is not implemented for this node type |
Source code in openscvx/symbolic/expr/expr.py
check_shape() -> Tuple[int, ...]
¶
Compute and validate the shape of this expression.
This method: 1. Recursively checks shapes of all child expressions 2. Validates that operations are shape-compatible (e.g., broadcasting rules) 3. Returns the output shape of this expression
For example: - A Parameter with shape (3, 4) returns (3, 4) - MatMul of (3, 4) @ (4, 5) returns (3, 5) - Sum of any shape returns () (scalar) - Add broadcasts shapes like NumPy
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
The shape of this expression as a tuple of integers. Empty tuple () represents a scalar. |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If shape checking is not implemented for this node type |
ValueError
|
If the expression has invalid shapes (e.g., incompatible dimensions) |
Source code in openscvx/symbolic/expr/expr.py
children() -> List[Expr]
¶
Return the child expressions of this node.
Returns:
| Name | Type | Description |
|---|---|---|
list |
List[Expr]
|
List of child Expr objects. Empty list for leaf nodes. |
pretty(indent: int = 0) -> str
¶
Generate a pretty-printed string representation of the expression tree.
Creates an indented, hierarchical view of the expression tree structure, useful for debugging and visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
indent
|
int
|
Current indentation level (default: 0) |
0
|
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
Multi-line string representation of the expression tree |
Example
Pretty print an expression:
expr = (x + y) * z
print(expr.pretty())
# Mul
# Add
# State
# State
# State
Source code in openscvx/symbolic/expr/expr.py
structural_hash() -> bytes
¶
Compute a structural hash of this expression.
Returns a hash that depends only on the mathematical structure of the expression, not on variable names. Two expressions that are structurally equivalent (same operations, same variable positions) will have the same hash.
Returns:
| Name | Type | Description |
|---|---|---|
bytes |
bytes
|
SHA-256 digest of the expression structure |
Source code in openscvx/symbolic/expr/expr.py
Hstack
¶
Bases: Expr
Horizontal stacking operation for symbolic expressions.
Concatenates expressions horizontally (along columns for 2D arrays). This is analogous to numpy.hstack() or jax.numpy.hstack().
Behavior depends on input dimensionality: - 1D arrays: Concatenates along axis 0 (making a longer vector) - 2D arrays: Concatenates along axis 1 (columns), rows must match - Higher-D: Concatenates along axis 1, all other dimensions must match
Attributes:
| Name | Type | Description |
|---|---|---|
arrays |
List of expressions to stack horizontally |
Example
1D case: concatenate vectors:
x = Variable("x", shape=(3,))
y = Variable("y", shape=(2,))
h = Hstack([x, y]) # Result shape (5,)
2D case: concatenate matrices horizontally:
A = Variable("A", shape=(3, 4))
B = Variable("B", shape=(3, 2))
C = Hstack([A, B]) # Result shape (3, 6)
Source code in openscvx/symbolic/expr/array.py
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 | |
__init__(arrays: List[Union[Expr, float, int, np.ndarray]])
¶
Initialize a horizontal stack operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
arrays
|
List[Union[Expr, float, int, ndarray]]
|
List of expressions to concatenate horizontally |
required |
Source code in openscvx/symbolic/expr/array.py
check_shape() -> Tuple[int, ...]
¶
Horizontal stack concatenates arrays along the second axis (columns).
Source code in openscvx/symbolic/expr/array.py
Huber
¶
Bases: Expr
Huber penalty function for symbolic expressions.
The Huber penalty is a smooth approximation to the absolute value function that is quadratic for small values (|x| < delta) and linear for large values (|x| >= delta). This makes it more robust to outliers than squared penalties while maintaining smoothness.
The Huber function is defined as: - (x^2) / (2*delta) for |x| <= delta - |x| - delta/2 for |x| > delta
Attributes:
| Name | Type | Description |
|---|---|---|
x |
Expression to apply Huber penalty to |
|
delta |
Threshold parameter controlling the transition point (default: 0.25) |
Example
Define a Huber penalty expression:
residual = y_measured - y_predicted
penalty = Huber(residual, delta=0.5)
Source code in openscvx/symbolic/expr/math.py
__init__(x: Union[Expr, float, int, np.ndarray], delta: float = 0.25)
¶
Initialize a Huber penalty operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Union[Expr, float, int, ndarray]
|
Expression to apply Huber penalty to |
required |
delta
|
float
|
Threshold parameter for quadratic-to-linear transition (default: 0.25) |
0.25
|
Source code in openscvx/symbolic/expr/math.py
canonicalize() -> Expr
¶
Index
¶
Bases: Expr
Indexing and slicing operation for symbolic expressions.
Represents indexing or slicing of an expression using NumPy-style indexing. Can be created using square bracket notation on Expr objects.
Attributes:
| Name | Type | Description |
|---|---|---|
base |
Expression to index into |
|
index |
Index specification (int, slice, or tuple of indices/slices) |
Example
Define an Index expression:
x = ox.State("x", shape=(10,))
y = x[0:5] # Creates Index(x, slice(0, 5))
z = x[3] # Creates Index(x, 3)
Source code in openscvx/symbolic/expr/array.py
__init__(base: Expr, index: Union[int, slice, tuple])
¶
Initialize an indexing operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base
|
Expr
|
Expression to index into |
required |
index
|
Union[int, slice, tuple]
|
NumPy-style index (int, slice, or tuple of indices/slices) |
required |
Source code in openscvx/symbolic/expr/array.py
canonicalize() -> Expr
¶
Canonicalize index by canonicalizing the base expression.
Returns:
| Name | Type | Description |
|---|---|---|
Expr |
Expr
|
Canonical form of the indexing expression |
check_shape() -> Tuple[int, ...]
¶
Compute the shape after indexing.
Source code in openscvx/symbolic/expr/array.py
Inequality
¶
Bases: Constraint
Inequality constraint for optimization problems.
Represents an inequality constraint: lhs <= rhs. Can be created using the <= operator on Expr objects.
Example
Define an Inequality constraint:
x = ox.State("x", shape=(3,))
constraint = x <= 10 # Creates Inequality(x, Constant(10))
Source code in openscvx/symbolic/expr/constraint.py
Inv
¶
Bases: Expr
Matrix inverse operation for symbolic expressions.
Computes the inverse of a square matrix. For batched inputs with shape (..., M, M), inverts the last two dimensions following jax.numpy.linalg.inv conventions.
The canonicalization includes an optimization that eliminates double inverses: Inv(Inv(A)) simplifies to A.
Attributes:
| Name | Type | Description |
|---|---|---|
operand |
Square matrix expression to invert |
Example
Define matrix inverse expressions::
M = Variable("M", shape=(3, 3))
M_inv = Inv(M) # Result shape (3, 3)
# Batched case
M_batch = Variable("M_batch", shape=(5, 3, 3))
M_batch_inv = Inv(M_batch) # Result shape (5, 3, 3)
Note
Matrix inverse is non-convex and only supported in JAX lowering. CVXPy lowering will raise NotImplementedError since inv(X) is neither convex nor concave for variable matrices.
Warning
Solving a matrix inverse inside an optimization loop can be somewhat of an oxymoron and performance may be severly impacted. Consider whether your problem can be reformulated to avoid the inverse.
Source code in openscvx/symbolic/expr/linalg.py
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 | |
__init__(operand: Union[Expr, float, int, np.ndarray])
¶
Initialize a matrix inverse operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operand
|
Union[Expr, float, int, ndarray]
|
Square matrix expression to invert. Must have shape (..., M, M) where the last two dimensions are equal. |
required |
Source code in openscvx/symbolic/expr/linalg.py
canonicalize() -> Expr
¶
Canonicalize the operand with double inverse optimization and constant folding.
Source code in openscvx/symbolic/expr/linalg.py
check_shape() -> Tuple[int, ...]
¶
Matrix inverse preserves shape; validates square matrix.
Source code in openscvx/symbolic/expr/linalg.py
Leaf
¶
Bases: Expr
Base class for leaf nodes (terminal expressions) in the symbolic expression tree.
Leaf nodes represent named symbolic variables that don't have child expressions. This includes Parameters, Variables, States, and Controls.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Name identifier for the leaf node |
_shape |
tuple
|
Shape of the leaf node |
Source code in openscvx/symbolic/expr/expr.py
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 | |
shape: Tuple[int, ...]
property
¶
Get the shape of the leaf node.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Shape of the leaf node |
__init__(name: str, shape: tuple = ())
¶
Initialize a Leaf node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Name identifier for the leaf node |
required |
shape
|
tuple
|
Shape of the leaf node |
()
|
Source code in openscvx/symbolic/expr/expr.py
canonicalize() -> Expr
¶
Leaf nodes are already in canonical form.
Returns:
| Name | Type | Description |
|---|---|---|
Expr |
Expr
|
Returns self since leaf nodes are already canonical |
check_shape() -> Tuple[int, ...]
¶
Return the shape of this leaf node.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
The shape of the leaf node |
Linterp
¶
Bases: Expr
1D linear interpolation for symbolic expressions.
Computes the linear interpolant of data points (xp, fp) evaluated at x, equivalent to jax.numpy.interp(x, xp, fp). For values outside the data range, the boundary values are returned (no extrapolation).
This is useful for incorporating tabulated data (e.g., atmospheric properties, engine thrust curves, aerodynamic coefficients) into trajectory optimization dynamics and constraints.
Attributes:
| Name | Type | Description |
|---|---|---|
x |
Query point(s) at which to evaluate the interpolant (symbolic expression) |
|
xp |
1D array of x-coordinates of data points (must be increasing) |
|
fp |
1D array of y-coordinates of data points (same length as xp) |
Example
Interpolate atmospheric density from altitude table::
import openscvx as ox
import numpy as np
# US 1976 Standard Atmosphere data
alt_data = np.array([0, 5000, 10000, 15000, 20000]) # meters
rho_data = np.array([1.225, 0.736, 0.414, 0.195, 0.089]) # kg/m^3
altitude = ox.State("altitude", shape=(1,))
rho = ox.Linterp(altitude[0], alt_data, rho_data)
# rho can now be used in dynamics expressions
drag = 0.5 * rho * v**2 * Cd * S
Note
- xp must be strictly increasing
- For query points outside [xp[0], xp[-1]], boundary values are returned
Source code in openscvx/symbolic/expr/math.py
702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 | |
__init__(x: Union[Expr, float, int, np.ndarray], xp: Union[Expr, float, int, np.ndarray], fp: Union[Expr, float, int, np.ndarray])
¶
Initialize a 1D linear interpolation node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Union[Expr, float, int, ndarray]
|
Query point(s) at which to evaluate the interpolant. Can be a scalar or array symbolic expression. |
required |
xp
|
Union[Expr, float, int, ndarray]
|
1D array of x-coordinates of data points. Must be increasing. Can be a numpy array or Constant expression. |
required |
fp
|
Union[Expr, float, int, ndarray]
|
1D array of y-coordinates of data points. Must have same length as xp. Can be a numpy array or Constant expression. |
required |
Source code in openscvx/symbolic/expr/math.py
canonicalize() -> Expr
¶
Canonicalize by canonicalizing all operands.
check_shape() -> Tuple[int, ...]
¶
Output shape matches the query point shape.
The interpolation is element-wise over x, so the output has the same shape as the query points.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Shape of the query point x |
Raises:
| Type | Description |
|---|---|
ValueError
|
If xp and fp have different lengths or are not 1D |
Source code in openscvx/symbolic/expr/math.py
Log
¶
Bases: Expr
Element-wise natural logarithm function for symbolic expressions.
Computes the natural logarithm (base e) of each element in the operand. Preserves the shape of the input expression.
Attributes:
| Name | Type | Description |
|---|---|---|
operand |
Expression to apply logarithm to |
Example
Define a Log expression:
x = Variable("x", shape=(3,))
log_x = Log(x)
Source code in openscvx/symbolic/expr/math.py
LogSumExp
¶
Bases: Expr
Log-sum-exp function for symbolic expressions.
Computes the log-sum-exp (LSE) of multiple operands, which is a smooth, differentiable approximation to the maximum function. The log-sum-exp is defined as:
logsumexp(x₁, x₂, ..., xₙ) = log(exp(x₁) + exp(x₂) + ... + exp(xₙ))
This function is numerically stable and is commonly used in optimization as a smooth alternative to the non-differentiable maximum function. It satisfies the inequality:
max(x₁, x₂, ..., xₙ) ≤ logsumexp(x₁, x₂, ..., xₙ) ≤ max(x₁, x₂, ..., xₙ) + log(n)
The log-sum-exp is convex and is particularly useful for: - Smooth approximations of maximum constraints - Soft maximum operations in neural networks - Relaxing logical OR operations in STL specifications
Attributes:
| Name | Type | Description |
|---|---|---|
operands |
List of expressions to compute log-sum-exp over |
Example
Define a LogSumExp expression:
x = Variable("x", shape=(3,))
y = Variable("y", shape=(3,))
z = Variable("z", shape=(3,))
lse = LogSumExp(x, y, z) # Smooth approximation to max(x, y, z)
Use in STL relaxation:
import openscvx as ox
# Relax: Or(φ₁, φ₂) using log-sum-exp
phi1 = ox.Norm(x - goal1) - 0.5
phi2 = ox.Norm(x - goal2) - 0.5
relaxed_or = LogSumExp(phi1, phi2) >= 0
Source code in openscvx/symbolic/expr/math.py
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 690 691 692 693 694 695 696 697 698 699 | |
__init__(*args: Union[Expr, float, int, np.ndarray])
¶
Initialize a log-sum-exp operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*args
|
Union[Expr, float, int, ndarray]
|
Two or more expressions to compute log-sum-exp over |
()
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If fewer than two operands are provided |
Source code in openscvx/symbolic/expr/math.py
canonicalize() -> Expr
¶
Canonicalize log-sum-exp: flatten nested LogSumExp, fold constants.
Source code in openscvx/symbolic/expr/math.py
check_shape() -> Tuple[int, ...]
¶
LogSumExp broadcasts shapes like NumPy, preserving element-wise shape.
Source code in openscvx/symbolic/expr/math.py
MatMul
¶
Bases: Expr
Matrix multiplication operation for symbolic expressions.
Represents matrix multiplication following standard linear algebra rules. Can be created using the @ operator on Expr objects. Handles: - Matrix @ Matrix: (m,n) @ (n,k) -> (m,k) - Matrix @ Vector: (m,n) @ (n,) -> (m,) - Vector @ Matrix: (m,) @ (m,n) -> (n,) - Vector @ Vector: (m,) @ (m,) -> scalar
Attributes:
| Name | Type | Description |
|---|---|---|
left |
Left-hand side expression |
|
right |
Right-hand side expression |
Example
Define a MatMul expression:
A = ox.State("A", shape=(3, 4))
x = ox.State("x", shape=(4,))
y = A @ x # Creates MatMul(A, x), result shape (3,)
Source code in openscvx/symbolic/expr/arithmetic.py
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 | |
__init__(left: Union[Expr, float, int, np.ndarray], right: Union[Expr, float, int, np.ndarray])
¶
Initialize a matrix multiplication operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
left
|
Union[Expr, float, int, ndarray]
|
Left-hand side expression for matrix multiplication |
required |
right
|
Union[Expr, float, int, ndarray]
|
Right-hand side expression for matrix multiplication |
required |
Source code in openscvx/symbolic/expr/arithmetic.py
check_shape() -> Tuple[int, ...]
¶
Check matrix multiplication shape compatibility and return result shape.
Source code in openscvx/symbolic/expr/arithmetic.py
Max
¶
Bases: Expr
Element-wise maximum function for symbolic expressions.
Computes the element-wise maximum across two or more operands. Supports broadcasting following NumPy rules. During canonicalization, nested Max operations are flattened and constants are folded.
Attributes:
| Name | Type | Description |
|---|---|---|
operands |
List of expressions to compute maximum over |
Example
Define a Max expression:
x = Variable("x", shape=(3,))
y = Variable("y", shape=(3,))
max_xy = Max(x, y, 0) # Element-wise max(x, y, 0)
Source code in openscvx/symbolic/expr/math.py
__init__(*args: Union[Expr, float, int, np.ndarray])
¶
Initialize a maximum operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*args
|
Union[Expr, float, int, ndarray]
|
Two or more expressions to compute maximum over |
()
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If fewer than two operands are provided |
Source code in openscvx/symbolic/expr/math.py
canonicalize() -> Expr
¶
Canonicalize max: flatten nested Max, fold constants.
Source code in openscvx/symbolic/expr/math.py
check_shape() -> Tuple[int, ...]
¶
Max broadcasts shapes like NumPy.
Source code in openscvx/symbolic/expr/math.py
Mul
¶
Bases: Expr
Element-wise multiplication operation for symbolic expressions.
Represents element-wise (Hadamard) multiplication of two or more expressions. Supports broadcasting following NumPy rules. Can be created using the * operator on Expr objects. For matrix multiplication, use MatMul or the @ operator.
Attributes:
| Name | Type | Description |
|---|---|---|
factors |
List of expression operands to multiply together |
Example
Define a Mul expression:
x = ox.State("x", shape=(3,))
y = ox.State("y", shape=(3,))
z = x * y * 2 # Creates Mul(x, y, Constant(2))
Source code in openscvx/symbolic/expr/arithmetic.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 | |
__init__(*args: Union[Expr, float, int, np.ndarray])
¶
Initialize an element-wise multiplication operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*args
|
Union[Expr, float, int, ndarray]
|
Two or more expressions to multiply together |
()
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If fewer than two operands are provided |
Source code in openscvx/symbolic/expr/arithmetic.py
canonicalize() -> Expr
¶
Canonicalize multiplication: flatten, fold constants, and eliminating ones.
Returns:
| Name | Type | Description |
|---|---|---|
Expr |
Expr
|
Canonical form of the multiplication expression |
Source code in openscvx/symbolic/expr/arithmetic.py
check_shape() -> Tuple[int, ...]
¶
Check shape compatibility and compute broadcasted result shape like NumPy.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
The broadcasted shape of all operands |
Raises:
| Type | Description |
|---|---|
ValueError
|
If operand shapes are not broadcastable |
Source code in openscvx/symbolic/expr/arithmetic.py
Neg
¶
Bases: Expr
Negation operation for symbolic expressions.
Represents element-wise negation (unary minus). Can be created using the unary - operator on Expr objects.
Attributes:
| Name | Type | Description |
|---|---|---|
operand |
Expression to negate |
Example
Define a Neg expression:
x = ox.State("x", shape=(3,))
y = -x # Creates Neg(x)
Source code in openscvx/symbolic/expr/arithmetic.py
__init__(operand: Union[Expr, float, int, np.ndarray])
¶
Initialize a negation operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operand
|
Union[Expr, float, int, ndarray]
|
Expression to negate |
required |
NodalConstraint
¶
Bases: Expr
Wrapper for constraints enforced only at specific discrete trajectory nodes.
NodalConstraint allows selective enforcement of constraints at specific time points (nodes) in a discretized trajectory, rather than enforcing them at every node. This is useful for:
- Specifying waypoint constraints (e.g., pass through point X at node 10)
- Boundary conditions at non-standard locations
- Reducing computational cost by checking constraints less frequently
- Enforcing periodic constraints (e.g., every 5th node)
The wrapper maintains clean separation between the constraint's mathematical definition and the specification of where it should be applied during optimization.
Note
Bare Constraint objects (without .at() or .over()) are automatically converted to NodalConstraints applied at all nodes during preprocessing.
Attributes:
| Name | Type | Description |
|---|---|---|
constraint |
The wrapped Constraint (Equality or Inequality) to enforce |
|
nodes |
List of integer node indices where the constraint is enforced |
Example
Enforce position constraint only at nodes 0, 10, and 20:
x = State("x", shape=(3,))
target = [10, 5, 0]
constraint = (x == target).at([0, 10, 20])
Equivalent using NodalConstraint directly:
constraint = NodalConstraint(x == target, nodes=[0, 10, 20])
Periodic constraint enforcement (every 10th node):
velocity_limit = (vel <= 100).at(list(range(0, 100, 10)))
Bare constraints are automatically applied at all nodes. These are equivalent:
constraint1 = vel <= 100 # Auto-converted to all nodes
constraint2 = (vel <= 100).at(list(range(n_nodes)))
Source code in openscvx/symbolic/expr/constraint.py
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 | |
__init__(constraint: Constraint, nodes: list[int])
¶
Initialize a NodalConstraint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
constraint
|
Constraint
|
The Constraint (Equality or Inequality) to enforce at specified nodes |
required |
nodes
|
list[int]
|
List of integer node indices where the constraint should be enforced. Automatically converts numpy integers to Python integers. |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
If constraint is not a Constraint instance |
TypeError
|
If nodes is not a list |
TypeError
|
If any node index is not an integer |
Note
Bounds checking for cross-node constraints (those containing NodeReference) is performed later in the pipeline when N is known, via validate_cross_node_constraint_bounds() in preprocessing.py.
Source code in openscvx/symbolic/expr/constraint.py
canonicalize() -> Expr
¶
Canonicalize the wrapped constraint while preserving node specification.
Returns:
| Name | Type | Description |
|---|---|---|
NodalConstraint |
Expr
|
A new NodalConstraint with canonicalized inner constraint |
Source code in openscvx/symbolic/expr/constraint.py
check_shape() -> Tuple[int, ...]
¶
Validate the wrapped constraint's shape.
NodalConstraint wraps a constraint without changing its computational meaning, only specifying where it should be applied. Like all constraints, it produces a scalar result.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Empty tuple () representing scalar shape |
Source code in openscvx/symbolic/expr/constraint.py
children() -> List[Expr]
¶
Return the wrapped constraint as the only child.
Returns:
| Name | Type | Description |
|---|---|---|
list |
List[Expr]
|
Single-element list containing the wrapped constraint |
convex() -> NodalConstraint
¶
Mark the underlying constraint as convex for CVXPy lowering.
Returns:
| Type | Description |
|---|---|
NodalConstraint
|
Self with underlying constraint's convex flag set to True (enables method chaining) |
Example
Mark a constraint as convex: constraint = (x <= 10).at([0, 5, 10]).convex()
Source code in openscvx/symbolic/expr/constraint.py
NodeReference
¶
Bases: Expr
Reference to a variable at a specific trajectory node.
NodeReference enables inter-node constraints by allowing you to reference the value of a state or control variable at a specific discrete time point (node) in the trajectory. This is essential for expressing temporal relationships such as:
- Rate limits and smoothness constraints
- Multi-step dependencies and recurrence relations
- Constraints coupling specific nodes
Attributes:
| Name | Type | Description |
|---|---|---|
base |
The expression (typically a Leaf like State or Control) being referenced |
|
node_idx |
Trajectory node index (integer, can be negative for end-indexing) |
Example
Rate limit across trajectory:
position = State("pos", shape=(3,))
# Create rate limit constraints for all nodes
constraints = [
(ox.linalg.Norm(position.at(k) - position.at(k-1)) <= 0.1).at([k])
for k in range(1, N)
]
Multi-step dependency:
state = State("x", shape=(1,))
# Fibonacci-like recurrence at each node
constraints = [
(state.at(k) == state.at(k-1) + state.at(k-2)).at([k])
for k in range(2, N)
]
Coupling specific nodes:
# Constrain distance between nodes 5 and 10
coupling = (position.at(10) - position.at(5) <= threshold).at([10])
Performance Note
Cross-node constraints use dense Jacobian storage. For details on memory usage and performance implications, see LoweredCrossNodeConstraint documentation.
Note
NodeReference is typically created via the .at(k) method on expressions
rather than constructed directly.
Source code in openscvx/symbolic/expr/expr.py
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 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 | |
__init__(base: Expr, node_idx: int)
¶
Initialize a NodeReference.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base
|
Expr
|
Expression to reference at a specific node (typically a Leaf) |
required |
node_idx
|
int
|
Absolute trajectory node index (integer) Supports negative indexing (e.g., -1 for last node) |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
If node_idx is not an integer |
Source code in openscvx/symbolic/expr/expr.py
canonicalize() -> Expr
¶
Canonicalize by canonicalizing the base expression.
Returns:
| Name | Type | Description |
|---|---|---|
NodeReference |
Expr
|
A new NodeReference with canonicalized base |
Source code in openscvx/symbolic/expr/expr.py
check_shape() -> Tuple[int, ...]
¶
Return the shape of the base expression.
NodeReference doesn't change the shape of the underlying expression, it just references it at a specific time point.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
The shape of the base expression |
Source code in openscvx/symbolic/expr/expr.py
Norm
¶
Bases: Expr
Norm operation for symbolic expressions (reduction to scalar).
Computes the norm of an expression according to the specified order parameter. This is a reduction operation that always produces a scalar result regardless of the input shape. Supports various norm types following NumPy/SciPy conventions.
Attributes:
| Name | Type | Description |
|---|---|---|
operand |
Expression to compute norm of |
|
ord |
Norm order specification (default: "fro" for Frobenius norm) - "fro": Frobenius norm (default) - "inf": Infinity norm - 1: L1 norm (sum of absolute values) - 2: L2 norm (Euclidean norm) - Other values as supported by the backend |
Example
Define Norms:
x = Variable("x", shape=(3,))
euclidean_norm = Norm(x, ord=2) # L2 norm, result is scalar
A = Variable("A", shape=(3, 4))
frobenius_norm = Norm(A) # Frobenius norm, result is scalar
Source code in openscvx/symbolic/expr/linalg.py
__init__(operand: Union[Expr, float, int, np.ndarray], ord: Union[str, int, float] = 'fro')
¶
Initialize a norm operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operand
|
Union[Expr, float, int, ndarray]
|
Expression to compute norm of |
required |
ord
|
Union[str, int, float]
|
Norm order specification (default: "fro") |
'fro'
|
Source code in openscvx/symbolic/expr/linalg.py
canonicalize() -> Expr
¶
Canonicalize the operand but preserve the ord parameter.
check_shape() -> Tuple[int, ...]
¶
Norm reduces any shape to a scalar.
Or
¶
Bases: STLExpr
Logical OR operation for STL predicates.
Combines constraint predicates with disjunction. The Or is satisfied if at least one of its operands is satisfied.
During lowering, this is handled by STLJax which computes the smooth maximum (LogSumExp) of the robustness values automatically.
The Or operation allows expressing constraints like: - "Reach either goal A OR goal B" - "Avoid obstacle 1 OR obstacle 2" (at least one must be satisfied) - "Use path 1 OR path 2 OR path 3"
Attributes:
| Name | Type | Description |
|---|---|---|
predicates |
List of predicates (Constraint or STLExpr objects) |
Example
Visit either waypoint 1 OR waypoint 2:
import openscvx as ox
position = ox.State("pos", shape=(2,))
goal_a = ox.Parameter("goal_a", shape=(2,), value=[1.0, 1.0])
goal_b = ox.Parameter("goal_b", shape=(2,), value=[-1.0, -1.0])
# Define predicates as constraints
reach_a = ox.Norm(position - goal_a) <= 0.5
reach_b = ox.Norm(position - goal_b) <= 0.5
# Combine with OR operator
reach_either = ox.stl.Or(reach_a, reach_b)
# Enforce continuously over time interval
constraints = [reach_either.over((3, 5))]
Nested STL operators are also supported:
# Or of And expressions
expr = ox.stl.Or(
ox.stl.And(c1, c2),
ox.stl.And(c3, c4),
)
Note
Or evaluates to a scalar robustness value (positive when satisfied).
Use .over() or .at() to convert to a constraint, or manually create
a constraint with: -Or(...) <= 0
See Also
stljax.formula.Or: Underlying STLJax implementation used during lowering
Source code in openscvx/symbolic/expr/stl.py
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 | |
__init__(*predicates: Union[Constraint, STLExpr])
¶
Initialize a logical OR operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*predicates
|
Union[Constraint, STLExpr]
|
Two or more Constraint or STLExpr objects to combine with logical OR. Each represents a predicate to be satisfied. |
()
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If fewer than two predicates are provided |
TypeError
|
If predicates are not Constraint or STLExpr instances |
Source code in openscvx/symbolic/expr/stl.py
canonicalize() -> Expr
¶
Canonicalize by flattening nested Or and canonicalizing predicates.
Flattens nested Or operations into a single flat Or with all predicates at the same level. For example: Or(a, Or(b, c)) → Or(a, b, c).
Returns:
| Name | Type | Description |
|---|---|---|
Expr |
Expr
|
Canonical form. If only one predicate remains, returns it directly. |
Source code in openscvx/symbolic/expr/stl.py
check_shape() -> Tuple[int, ...]
¶
Validate predicate shapes and return scalar shape.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Empty tuple () indicating a scalar result (STL robustness) |
Raises:
| Type | Description |
|---|---|
ValueError
|
If fewer than two predicates exist |
Source code in openscvx/symbolic/expr/stl.py
Parameter
¶
Bases: Leaf
Parameter that can be changed at runtime without recompilation.
Parameters are symbolic variables with initial values that can be updated through the problem's parameter dictionary. They allow for efficient parameter sweeps without needing to recompile the optimization problem.
Example
obs_center = ox.Parameter("obs_center", shape=(3,), value=np.array([1.0, 0.0, 0.0]))
Later: problem.parameters["obs_center"] = new_value¶
Source code in openscvx/symbolic/expr/expr.py
__init__(name: str, shape: tuple = (), value: Union[float, int, np.ndarray, None] = None)
¶
Initialize a Parameter node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Name identifier for the parameter |
required |
shape
|
tuple
|
Shape of the parameter (default: scalar) |
()
|
value
|
Union[float, int, ndarray, None]
|
Initial value for the parameter (required) |
None
|
Source code in openscvx/symbolic/expr/expr.py
PositivePart
¶
Bases: Expr
Positive part function for symbolic expressions.
Computes max(x, 0) element-wise, effectively zeroing out negative values while preserving positive values. This is also known as the ReLU (Rectified Linear Unit) function and is commonly used as a penalty function building block in optimization.
Attributes:
| Name | Type | Description |
|---|---|---|
x |
Expression to apply positive part function to |
Example
Define a PositivePart expression:
constraint_violation = x - 10
penalty = PositivePart(constraint_violation) # Penalizes x > 10
Source code in openscvx/symbolic/expr/math.py
Power
¶
Bases: Expr
Element-wise power operation for symbolic expressions.
Represents element-wise exponentiation (base ** exponent). Supports broadcasting following NumPy rules. Can be created using the ** operator on Expr objects.
Attributes:
| Name | Type | Description |
|---|---|---|
base |
Base expression |
|
exponent |
Exponent expression |
Example
Define a Power expression:
x = ox.State("x", shape=(3,))
y = x ** 2 # Creates Power(x, Constant(2))
Source code in openscvx/symbolic/expr/arithmetic.py
__init__(base: Union[Expr, float, int, np.ndarray], exponent: Union[Expr, float, int, np.ndarray])
¶
QDCM
¶
Bases: Expr
Quaternion to Direction Cosine Matrix (DCM) conversion.
Converts a unit quaternion representation to a 3x3 direction cosine matrix (also known as a rotation matrix). This operation is commonly used in 6-DOF spacecraft dynamics, aircraft simulation, and robotics applications.
The quaternion is expected in scalar-last format: [qx, qy, qz, qw] where qw is the scalar component. The resulting DCM can be used to transform vectors from one reference frame to another.
Attributes:
| Name | Type | Description |
|---|---|---|
q |
Quaternion expression with shape (4,) |
Example
Use the QDCM to rotate a vector:
import openscvx as ox
q = ox.State("q", shape=(4,))
dcm = ox.QDCM(q) # Creates rotation matrix, shape (3, 3)
v_body = ox.Variable("v_body", shape=(3,))
v_inertial = dcm @ v_body
Note
The input quaternion does not need to be normalized; the implementation automatically handles normalization during evaluation.
Source code in openscvx/symbolic/expr/spatial.py
__init__(q: Union[Expr, float, int, np.ndarray])
¶
Initialize a quaternion to DCM conversion.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
q
|
Union[Expr, float, int, ndarray]
|
Quaternion expression with shape (4,) in [qx, qy, qz, qw] format |
required |
check_shape() -> Tuple[int, ...]
¶
Check that input is a quaternion and return DCM shape.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Shape (3, 3) for the resulting direction cosine matrix |
Raises:
| Type | Description |
|---|---|
ValueError
|
If quaternion does not have shape (4,) |
Source code in openscvx/symbolic/expr/spatial.py
SE3Adjoint
¶
Bases: Expr
SE(3) Adjoint representation Ad_T for transforming twists between frames.
Computes the 6×6 adjoint matrix Ad_T that transforms twists from one coordinate frame to another. Given a transformation T_ab from frame A to frame B, the adjoint transforms a twist expressed in frame A to frame B:
ξ_b = Ad_{T_ab} @ ξ_a
For SE(3), given T with rotation R and translation p:
Ad_T = [ R 0 ]
[ [p]×R R ]
where [p]× is the 3×3 skew-symmetric matrix of p.
This is essential for:
- Velocity propagation through kinematic chains
- Computing geometric Jacobians for manipulators
- Recursive Newton-Euler dynamics algorithms
Attributes:
| Name | Type | Description |
|---|---|---|
transform |
4×4 homogeneous transformation matrix |
Example
Transform a body twist to the world frame::
import openscvx as ox
T_world_body = forward_kinematics(q) # 4×4 transform
twist_body = ox.State("twist_body", shape=(6,))
# Transform twist to world frame
Ad_T = ox.lie.SE3Adjoint(T_world_body) # 6×6 matrix
twist_world = Ad_T @ twist_body
Compute geometric Jacobian columns::
# Each column of the geometric Jacobian is Ad_{T_0i} @ ξ_i
J_col_i = ox.lie.SE3Adjoint(T_0_to_i) @ screw_axis_i
Note
The adjoint satisfies: Ad_{T1 @ T2} = Ad_{T1} @ Ad_{T2}
See Also
- SE3AdjointDual: For transforming wrenches between frames
- Adjoint: The small adjoint (Lie bracket) for twist-on-twist action
Source code in openscvx/symbolic/expr/lie/adjoint.py
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 | |
__init__(transform: Union[Expr, float, int, np.ndarray])
¶
Initialize SE3 Adjoint operator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
transform
|
Union[Expr, float, int, ndarray]
|
4×4 homogeneous transformation matrix with shape (4, 4) |
required |
check_shape() -> Tuple[int, ...]
¶
Check that input is a 4×4 matrix and return output shape.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Shape (6, 6) for the adjoint matrix |
Raises:
| Type | Description |
|---|---|
ValueError
|
If transform does not have shape (4, 4) |
Source code in openscvx/symbolic/expr/lie/adjoint.py
SE3AdjointDual
¶
Bases: Expr
SE(3) coadjoint representation Ad*_T for transforming wrenches between frames.
Computes the 6×6 coadjoint matrix Ad*_T that transforms wrenches from one coordinate frame to another. Given a transformation T_ab from frame A to frame B, the coadjoint transforms a wrench expressed in frame B to frame A:
F_a = Ad*_{T_ab} @ F_b
For SE(3), given T with rotation R and translation p:
Ad*_T = [ R [p]×R ]
[ 0 R ]
This is the transpose-inverse of Ad_T: Ad*_T = (Ad_T)^{-T}
This is essential for:
- Force/torque propagation in dynamics
- Transforming wrenches between end-effector and base frames
- Recursive Newton-Euler dynamics algorithms
Attributes:
| Name | Type | Description |
|---|---|---|
transform |
4×4 homogeneous transformation matrix |
Example
Transform a wrench from end-effector to base frame::
import openscvx as ox
T_base_ee = forward_kinematics(q) # 4×4 transform
wrench_ee = ox.Control("wrench_ee", shape=(6,))
# Transform wrench to base frame
Ad_star_T = ox.lie.SE3AdjointDual(T_base_ee) # 6×6 matrix
wrench_base = Ad_star_T @ wrench_ee
Note
The coadjoint is related to the adjoint by: Ad*_T = (Ad_T)^{-T}
See Also
- SE3Adjoint: For transforming twists between frames
- AdjointDual: The small coadjoint for Coriolis/centrifugal forces
Source code in openscvx/symbolic/expr/lie/adjoint.py
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 | |
__init__(transform: Union[Expr, float, int, np.ndarray])
¶
Initialize SE3 coadjoint operator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
transform
|
Union[Expr, float, int, ndarray]
|
4×4 homogeneous transformation matrix with shape (4, 4) |
required |
check_shape() -> Tuple[int, ...]
¶
Check that input is a 4×4 matrix and return output shape.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Shape (6, 6) for the coadjoint matrix |
Raises:
| Type | Description |
|---|---|
ValueError
|
If transform does not have shape (4, 4) |
Source code in openscvx/symbolic/expr/lie/adjoint.py
SE3Exp
¶
Bases: Expr
Exponential map from se(3) twist to SE(3) transformation matrix.
Maps a 6D twist vector to a 4×4 homogeneous transformation matrix. Uses jaxlie for numerically robust implementation with proper handling of small angles and translations.
The twist ξ = [v; ω] follows the convention:
- v: 3D linear velocity component
- ω: 3D angular velocity component
This is the key operation for Product of Exponentials (PoE) forward kinematics in robotic manipulators.
Attributes:
| Name | Type | Description |
|---|---|---|
twist |
6D twist vector [v; ω] with shape (6,) |
Example
Product of Exponentials forward kinematics::
import openscvx as ox
import numpy as np
# Screw axis for revolute joint about z-axis at origin
screw_axis = np.array([0, 0, 0, 0, 0, 1]) # [v; ω]
theta = ox.State("theta", shape=(1,))
# Joint transformation
T = ox.lie.SE3Exp(ox.Constant(screw_axis) * theta) # 4×4 matrix
# Chain multiple joints
T_01 = ox.lie.SE3Exp(screw1 * q1)
T_12 = ox.lie.SE3Exp(screw2 * q2)
T_02 = T_01 @ T_12
Extract position from transformation::
T_ee = forward_kinematics(joint_angles)
p_ee = T_ee[:3, 3] # End-effector position
Note
The twist convention [v; ω] matches jaxlie's SE3 tangent parameterization, so no reordering is performed.
See Also
- SE3Log: Inverse operation (transformation matrix to twist)
- SO3Exp: Rotation-only exponential map
- AdjointDual: For dynamics computations with twists
Source code in openscvx/symbolic/expr/lie/se3.py
__init__(twist: Union[Expr, float, int, np.ndarray])
¶
Initialize SE3 exponential map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
twist
|
Union[Expr, float, int, ndarray]
|
6D twist vector [v; ω] with shape (6,) |
required |
check_shape() -> Tuple[int, ...]
¶
Check that input is a 6D vector and return output shape.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Shape (4, 4) for the homogeneous transformation matrix |
Raises:
| Type | Description |
|---|---|
ValueError
|
If twist does not have shape (6,) |
Source code in openscvx/symbolic/expr/lie/se3.py
SE3Log
¶
Bases: Expr
Logarithm map from SE(3) transformation matrix to se(3) twist.
Maps a 4×4 homogeneous transformation matrix to a 6D twist vector. Uses jaxlie for numerically robust implementation.
The output twist ξ = [v; ω] follows the convention:
- v: 3D linear component
- ω: 3D angular component (rotation vector)
This is useful for computing error metrics between poses in optimization.
Attributes:
| Name | Type | Description |
|---|---|---|
transform |
4×4 homogeneous transformation matrix with shape (4, 4) |
Example
Compute pose error for trajectory optimization::
import openscvx as ox
T_current = forward_kinematics(q)
T_target = ox.Parameter("T_target", shape=(4, 4), value=goal_pose)
# Relative transformation
T_error = ox.linalg.inv(T_target) @ T_current
# Convert to twist for error metric
twist_error = ox.lie.SE3Log(T_error)
pose_cost = ox.linalg.Norm(twist_error) ** 2
See Also
- SE3Exp: Inverse operation (twist to transformation matrix)
- SO3Log: Rotation-only logarithm map
Source code in openscvx/symbolic/expr/lie/se3.py
__init__(transform: Union[Expr, float, int, np.ndarray])
¶
Initialize SE3 logarithm map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
transform
|
Union[Expr, float, int, ndarray]
|
4×4 homogeneous transformation matrix with shape (4, 4) |
required |
check_shape() -> Tuple[int, ...]
¶
Check that input is a 4×4 matrix and return output shape.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Shape (6,) for the twist vector |
Raises:
| Type | Description |
|---|---|
ValueError
|
If transform does not have shape (4, 4) |
Source code in openscvx/symbolic/expr/lie/se3.py
SO3Exp
¶
Bases: Expr
Exponential map from so(3) to SO(3) rotation matrix.
Maps a 3D rotation vector (axis-angle representation) to a 3×3 rotation matrix using the Rodrigues formula. Uses jaxlie for numerically robust implementation with proper handling of small angles.
The rotation vector ω has direction equal to the rotation axis and magnitude equal to the rotation angle in radians.
Attributes:
| Name | Type | Description |
|---|---|---|
omega |
3D rotation vector with shape (3,) |
Example
Create a rotation about the z-axis::
import openscvx as ox
import numpy as np
# 90 degree rotation about z
omega = ox.Constant(np.array([0, 0, np.pi/2]))
R = ox.lie.SO3Exp(omega) # 3×3 rotation matrix
Parameterized rotation for optimization::
theta = ox.State("theta", shape=(1,))
axis = ox.Constant(np.array([0, 0, 1])) # z-axis
R = ox.lie.SO3Exp(axis * theta)
See Also
- SO3Log: Inverse operation (rotation matrix to rotation vector)
- SE3Exp: Full rigid body transformation including translation
Source code in openscvx/symbolic/expr/lie/so3.py
__init__(omega: Union[Expr, float, int, np.ndarray])
¶
Initialize SO3 exponential map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
omega
|
Union[Expr, float, int, ndarray]
|
3D rotation vector (axis × angle) with shape (3,) |
required |
check_shape() -> Tuple[int, ...]
¶
Check that input is a 3D vector and return output shape.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Shape (3, 3) for the rotation matrix |
Raises:
| Type | Description |
|---|---|
ValueError
|
If omega does not have shape (3,) |
Source code in openscvx/symbolic/expr/lie/so3.py
SO3Log
¶
Bases: Expr
Logarithm map from SO(3) rotation matrix to so(3) rotation vector.
Maps a 3×3 rotation matrix to a 3D rotation vector (axis-angle representation). Uses jaxlie for numerically robust implementation.
The output rotation vector ω has direction equal to the rotation axis and magnitude equal to the rotation angle in radians.
Attributes:
| Name | Type | Description |
|---|---|---|
rotation |
3×3 rotation matrix with shape (3, 3) |
Example
Extract rotation vector from a rotation matrix::
import openscvx as ox
R = ox.State("R", shape=(3, 3)) # Rotation matrix state
omega = ox.lie.SO3Log(R) # 3D rotation vector
See Also
- SO3Exp: Inverse operation (rotation vector to rotation matrix)
- SE3Log: Full rigid body transformation logarithm
Source code in openscvx/symbolic/expr/lie/so3.py
__init__(rotation: Union[Expr, float, int, np.ndarray])
¶
Initialize SO3 logarithm map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rotation
|
Union[Expr, float, int, ndarray]
|
3×3 rotation matrix with shape (3, 3) |
required |
check_shape() -> Tuple[int, ...]
¶
Check that input is a 3×3 matrix and return output shape.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Shape (3,) for the rotation vector |
Raises:
| Type | Description |
|---|---|
ValueError
|
If rotation does not have shape (3, 3) |
Source code in openscvx/symbolic/expr/lie/so3.py
SSM
¶
Bases: Expr
Angular rate vector to 3x3 skew-symmetric matrix (cross product matrix).
Constructs the 3x3 skew-symmetric matrix [ω]x that represents the cross product operation. For any 3D vector v, the cross product ω x v can be computed as the matrix-vector product [ω]x @ v.
The resulting matrix has the form
⎡ 0 -ωz ωy ⎤ ⎢ ωz 0 -ωx ⎥ ⎣-ωy ωx 0 ⎦
This operation is widely used in: - Rigid body dynamics (angular momentum calculations) - DCM time derivatives: Ṙ = [ω]x @ R - Velocity kinematics in robotics - Coriolis and centrifugal acceleration terms
Attributes:
| Name | Type | Description |
|---|---|---|
w |
Angular velocity or 3D vector expression with shape (3,) |
Example
Use the SSM to compute the rotation matrix derivative:
import openscvx as ox
omega = ox.Control("omega", shape=(3,))
R = ox.State("R", shape=(3, 3)) # Direction cosine matrix
# DCM time derivative
R_dot = ox.SSM(omega) @ R
Note
The skew-symmetric property ensures that [ω]xᵀ = -[ω]x, which is important for preserving orthogonality in DCM propagation.
See Also
SSMP: 4x4 skew-symmetric matrix for quaternion dynamics
Source code in openscvx/symbolic/expr/spatial.py
__init__(w: Union[Expr, float, int, np.ndarray])
¶
Initialize a vector to skew-symmetric matrix conversion.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
w
|
Union[Expr, float, int, ndarray]
|
3D vector expression with shape (3,) in [ωx, ωy, ωz] format |
required |
check_shape() -> Tuple[int, ...]
¶
Check that input is a 3D vector and return matrix shape.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Shape (3, 3) for the resulting skew-symmetric matrix |
Raises:
| Type | Description |
|---|---|
ValueError
|
If input vector does not have shape (3,) |
Source code in openscvx/symbolic/expr/spatial.py
SSMP
¶
Bases: Expr
Angular rate to 4x4 skew-symmetric matrix for quaternion dynamics.
Constructs the 4x4 skew-symmetric matrix Ω(ω) used in quaternion kinematic differential equations. This matrix relates angular velocity to the time derivative of the quaternion:
q̇ = (1/2) * Ω(ω) @ q
The resulting matrix has the form
⎡ 0 ωz -ωy ωx ⎤ ⎢-ωz 0 ωx ωy ⎥ ⎢ ωy -ωx 0 ωz ⎥ ⎣-ωx -ωy -ωz 0 ⎦
This is particularly useful for formulating quaternion-based attitude dynamics in spacecraft and aircraft trajectory optimization problems.
Attributes:
| Name | Type | Description |
|---|---|---|
w |
Angular velocity vector expression with shape (3,) |
Example
Use the SSMP to compute the quaternion derivative:
import openscvx as ox
omega = ox.Control("omega", shape=(3,))
q = ox.State("q", shape=(4,))
# Quaternion kinematic equation
q_dot = 0.5 * ox.SSMP(omega) @ q
See Also
SSM: 3x3 skew-symmetric matrix for cross product operations
Source code in openscvx/symbolic/expr/spatial.py
__init__(w: Union[Expr, float, int, np.ndarray])
¶
Initialize an angular velocity to skew-symmetric matrix conversion.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
w
|
Union[Expr, float, int, ndarray]
|
Angular velocity vector expression with shape (3,) in [ωx, ωy, ωz] format |
required |
Source code in openscvx/symbolic/expr/spatial.py
check_shape() -> Tuple[int, ...]
¶
Check that input is a 3D angular velocity and return matrix shape.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Shape (4, 4) for the resulting skew-symmetric matrix |
Raises:
| Type | Description |
|---|---|
ValueError
|
If angular velocity does not have shape (3,) |
Source code in openscvx/symbolic/expr/spatial.py
Sin
¶
Bases: Expr
Element-wise sine function for symbolic expressions.
Computes the sine of each element in the operand. Preserves the shape of the input expression.
Attributes:
| Name | Type | Description |
|---|---|---|
operand |
Expression to apply sine function to |
Example
Define a Sin expression:
theta = Variable("theta", shape=(3,))
sin_theta = Sin(theta)
Source code in openscvx/symbolic/expr/math.py
SmoothReLU
¶
Bases: Expr
Smooth approximation to the ReLU (positive part) function.
Computes a smooth, differentiable approximation to max(x, 0) using the formula: sqrt(max(x, 0)^2 + c^2) - c
The parameter c controls the smoothness: smaller values give a sharper transition, while larger values produce a smoother approximation. As c approaches 0, this converges to the standard ReLU function.
This is particularly useful in optimization contexts where smooth gradients are required, such as in penalty methods for constraint handling (CTCS).
Attributes:
| Name | Type | Description |
|---|---|---|
x |
Expression to apply smooth ReLU to |
|
c |
Smoothing parameter (default: 1e-8) |
Example
Define a smooth ReLU expression:
constraint_violation = x - 10
penalty = SmoothReLU(constraint_violation, c=1e-6)
Source code in openscvx/symbolic/expr/math.py
__init__(x: Union[Expr, float, int, np.ndarray], c: float = 1e-08)
¶
Initialize a smooth ReLU operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Union[Expr, float, int, ndarray]
|
Expression to apply smooth ReLU to |
required |
c
|
float
|
Smoothing parameter controlling transition sharpness (default: 1e-8) |
1e-08
|
Source code in openscvx/symbolic/expr/math.py
canonicalize() -> Expr
¶
Sqrt
¶
Bases: Expr
Element-wise square root function for symbolic expressions.
Computes the square root of each element in the operand. Preserves the shape of the input expression.
Attributes:
| Name | Type | Description |
|---|---|---|
operand |
Expression to apply square root to |
Example
Define a Sqrt expression:
x = Variable("x", shape=(3,))
sqrt_x = Sqrt(x)
Source code in openscvx/symbolic/expr/math.py
Square
¶
Bases: Expr
Element-wise square function for symbolic expressions.
Computes the square (x^2) of each element in the operand. Preserves the shape of the input expression. This is more efficient than using Power(x, 2) for some optimization backends.
Attributes:
| Name | Type | Description |
|---|---|---|
x |
Expression to square |
Example
Define a Square expression:
v = Variable("v", shape=(3,))
v_squared = Square(v) # Equivalent to v ** 2
Source code in openscvx/symbolic/expr/math.py
Stack
¶
Bases: Expr
Stack expressions vertically to create a higher-dimensional array.
Stacks a list of expressions along a new first dimension. All input expressions must have the same shape. The result has shape (num_rows, *row_shape).
This is similar to numpy.array([row1, row2, ...]) or jax.numpy.stack(rows, axis=0).
Attributes:
| Name | Type | Description |
|---|---|---|
rows |
List of expressions to stack, each representing a "row" |
Example
Leverage stack to combine expressions:
x = Variable("x", shape=(3,))
y = Variable("y", shape=(3,))
z = Variable("z", shape=(3,))
stacked = Stack([x, y, z]) # Creates shape (3, 3)
# Equivalent to: [[x[0], x[1], x[2]],
# [y[0], y[1], y[2]],
# [z[0], z[1], z[2]]]
Source code in openscvx/symbolic/expr/array.py
__init__(rows: List[Union[Expr, float, int, np.ndarray]])
¶
Initialize a stack operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
List[Union[Expr, float, int, ndarray]]
|
List of expressions to stack along a new first dimension. All expressions must have the same shape. |
required |
Source code in openscvx/symbolic/expr/array.py
check_shape() -> Tuple[int, ...]
¶
Stack creates a 2D matrix from 1D rows.
Source code in openscvx/symbolic/expr/array.py
State
¶
Bases: Variable
State variable with boundary conditions for trajectory optimization.
State represents a dynamic state variable in a trajectory optimization problem. Unlike control inputs, states evolve according to dynamics constraints and can have boundary conditions specified at the initial and final time points. Like all Variables, States also support min/max bounds and initial trajectory guesses to help guide the optimization solver toward good solutions.
States support four types of boundary conditions:
- fixed: State value is constrained to a specific value
- free: State value is optimized within the specified bounds
- minimize: Adds a term to the objective function to minimize the state value
- maximize: Adds a term to the objective function to maximize the state value
Each element of a multi-dimensional state can have different boundary condition types, allowing for fine-grained control over the optimization.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Unique name identifier for this state variable |
_shape |
tuple[int, ...]
|
Shape of the state vector (typically 1D like (3,) for 3D position) |
_slice |
slice | None
|
Internal slice information for variable indexing |
_min |
ndarray | None
|
Minimum bounds for state variables |
_max |
ndarray | None
|
Maximum bounds for state variables |
_guess |
ndarray | None
|
Initial trajectory guess |
_initial |
ndarray | None
|
Initial state values with boundary condition types |
initial_type |
ndarray | None
|
Array of boundary condition types for initial state |
_final |
ndarray | None
|
Final state values with boundary condition types |
final_type |
ndarray | None
|
Array of boundary condition types for final state |
Example
Scalar time state with fixed initial time, minimize final time:
time = State("time", (1,))
time.min = [0.0]
time.max = [10.0]
time.initial = [("fixed", 0.0)]
time.final = [("minimize", 5.0)]
3D position state with mixed boundary conditions:
pos = State("pos", (3,))
pos.min = [0, 0, 10]
pos.max = [10, 10, 200]
pos.initial = [0, ("free", 1), 50] # x fixed, y free, z fixed
pos.final = [10, ("free", 5), ("maximize", 150)] # Maximize final altitude
Source code in openscvx/symbolic/expr/state.py
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 | |
final: Optional[np.ndarray]
property
writable
¶
Get the final state boundary condition values.
Returns:
| Type | Description |
|---|---|
Optional[ndarray]
|
Array of final state values (regardless of boundary condition type), |
Optional[ndarray]
|
or None if not set. |
Note
Use final_type to see the boundary condition types for each element.
Example
Get final state boundary conditions:
x = State("x", (2,))
x.final = [10, ("minimize", 0)]
print(x.final) # [10. 0.]
print(x.final_type) # ['Fix' 'Minimize']
initial: Optional[np.ndarray]
property
writable
¶
Get the initial state boundary condition values.
Returns:
| Type | Description |
|---|---|
Optional[ndarray]
|
Array of initial state values (regardless of boundary condition type), |
Optional[ndarray]
|
or None if not set. |
Note
Use initial_type to see the boundary condition types for each element.
Example
Get initial state boundary conditions:
x = State("x", (2,))
x.initial = [0, ("free", 1)]
print(x.initial) # [0. 1.]
print(x.initial_type) # ['Fix' 'Free']
max: Optional[np.ndarray]
property
writable
¶
Get the maximum bounds for the state variables.
Returns:
| Type | Description |
|---|---|
Optional[ndarray]
|
Array of maximum values for each state variable element. |
Example
Get upper bounds:
vel = State("vel", (3,))
vel.max = [10, 10, 5]
print(vel.max) # [10. 10. 5.]
min: Optional[np.ndarray]
property
writable
¶
Get the minimum bounds for the state variables.
Returns:
| Type | Description |
|---|---|
Optional[ndarray]
|
Array of minimum values for each state variable element. |
Example
Get lower bounds:
pos = State("pos", (3,))
pos.min = [0, 0, 10]
print(pos.min) # [0. 0. 10.]
scaling_max: Optional[np.ndarray]
property
writable
¶
Get the scaling maximum bounds for the state variables.
Returns:
| Type | Description |
|---|---|
Optional[ndarray]
|
Array of scaling maximum values for each state variable element, or None if not set. |
scaling_min: Optional[np.ndarray]
property
writable
¶
Get the scaling minimum bounds for the state variables.
Returns:
| Type | Description |
|---|---|
Optional[ndarray]
|
Array of scaling minimum values for each state variable element, or None if not set. |
__init__(name: str, shape: Tuple[int, ...])
¶
Initialize a State object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Name identifier for the state variable |
required |
shape
|
Tuple[int, ...]
|
Shape of the state vector (typically 1D tuple) |
required |
Source code in openscvx/symbolic/expr/state.py
Sub
¶
Bases: Expr
Subtraction operation for symbolic expressions.
Represents element-wise subtraction (left - right). Supports broadcasting following NumPy rules. Can be created using the - operator on Expr objects.
Attributes:
| Name | Type | Description |
|---|---|---|
left |
Left-hand side expression (minuend) |
|
right |
Right-hand side expression (subtrahend) |
Example
Define a Sub expression:
x = ox.State("x", shape=(3,))
y = ox.State("y", shape=(3,))
z = x - y # Creates Sub(x, y)
Source code in openscvx/symbolic/expr/arithmetic.py
__init__(left: Union[Expr, float, int, np.ndarray], right: Union[Expr, float, int, np.ndarray])
¶
canonicalize() -> Expr
¶
Canonicalize subtraction: fold constants if both sides are constants.
Returns:
| Name | Type | Description |
|---|---|---|
Expr |
Expr
|
Canonical form of the subtraction expression |
Source code in openscvx/symbolic/expr/arithmetic.py
check_shape() -> Tuple[int, ...]
¶
Check shape compatibility and compute broadcasted result shape like NumPy.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
The broadcasted shape of all operands |
Raises:
| Type | Description |
|---|---|
ValueError
|
If operand shapes are not broadcastable |
Source code in openscvx/symbolic/expr/arithmetic.py
Sum
¶
Bases: Expr
Sum reduction operation for symbolic expressions.
Sums all elements of an expression, reducing it to a scalar. This is a reduction operation that collapses all dimensions.
Attributes:
| Name | Type | Description |
|---|---|---|
operand |
Expression whose elements will be summed |
Example
Define a Sum expression::
x = ox.State("x", shape=(3, 4))
total = Sum(x) # Creates Sum(x), result shape ()
Source code in openscvx/symbolic/expr/linalg.py
__init__(operand: Union[Expr, float, int, np.ndarray])
¶
Initialize a sum reduction operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operand
|
Union[Expr, float, int, ndarray]
|
Expression to sum over all elements |
required |
canonicalize() -> Expr
¶
Canonicalize sum: canonicalize the operand.
Returns:
| Name | Type | Description |
|---|---|---|
Expr |
Expr
|
Canonical form of the sum expression |
check_shape() -> Tuple[int, ...]
¶
Sum reduces any shape to a scalar.
Tan
¶
Bases: Expr
Element-wise tangent function for symbolic expressions.
Computes the tangent of each element in the operand. Preserves the shape of the input expression.
Attributes:
| Name | Type | Description |
|---|---|---|
operand |
Expression to apply tangent function to |
Example
Define a Tan expression:
theta = Variable("theta", shape=(3,))
tan_theta = Tan(theta)
Note
Tan is only supported for JAX lowering. CVXPy lowering will raise NotImplementedError since tangent is not DCP-compliant.
Source code in openscvx/symbolic/expr/math.py
Transpose
¶
Bases: Expr
Matrix transpose operation for symbolic expressions.
Transposes the last two dimensions of an expression. For matrices, this swaps rows and columns. For higher-dimensional arrays, it swaps the last two axes. Scalars and vectors are unchanged by transposition.
The canonicalization includes an optimization that eliminates double transposes: (A.T).T simplifies to A.
Attributes:
| Name | Type | Description |
|---|---|---|
operand |
Expression to transpose |
Example
Define Tranpose expressions:
A = Variable("A", shape=(3, 4))
A_T = Transpose(A) # or A.T, result shape (4, 3)
v = Variable("v", shape=(5,))
v_T = Transpose(v) # result shape (5,) - vectors unchanged
Source code in openscvx/symbolic/expr/linalg.py
__init__(operand: Union[Expr, float, int, np.ndarray])
¶
Initialize a transpose operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operand
|
Union[Expr, float, int, ndarray]
|
Expression to transpose |
required |
canonicalize() -> Expr
¶
Canonicalize the operand with double transpose optimization.
Source code in openscvx/symbolic/expr/linalg.py
check_shape() -> Tuple[int, ...]
¶
Matrix transpose operation swaps the last two dimensions.
Source code in openscvx/symbolic/expr/linalg.py
Variable
¶
Bases: Leaf
Base class for decision variables in optimization problems.
Variable represents decision variables (free parameters) in an optimization problem. These are values that the optimizer can adjust to minimize the objective function while satisfying constraints. Variables can have bounds (min/max) and initial guesses to guide the optimization process.
Unlike Parameters (which are fixed values that can be changed between solves), Variables are optimized by the solver. In trajectory optimization, Variables typically represent discretized state or control trajectories.
Note
Variable is typically not instantiated directly. Instead, use the specialized subclasses State (for state variables with boundary conditions) or Control (for control inputs). These provide additional functionality specific to trajectory optimization.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Name identifier for the variable |
_shape |
tuple[int, ...]
|
Shape of the variable as a tuple (typically 1D) |
_slice |
slice | None
|
Internal slice information for variable indexing |
_min |
ndarray | None
|
Minimum bounds for each element of the variable |
_max |
ndarray | None
|
Maximum bounds for each element of the variable |
_guess |
ndarray | None
|
Initial guess for the variable trajectory (n_points, n_vars) |
Example
Typically, use State or Control instead of Variable directly:¶
pos = openscvx.State("pos", shape=(3,)) u = openscvx.Control("u", shape=(2,))
Source code in openscvx/symbolic/expr/variable.py
9 10 11 12 13 14 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 | |
guess: Optional[np.ndarray]
property
writable
¶
Get the initial guess for the variable trajectory.
The guess provides a starting point for the optimizer. A good initial guess can significantly improve convergence speed and help avoid local minima.
Returns:
| Type | Description |
|---|---|
Optional[ndarray]
|
2D array of shape (n_points, n_vars) representing the variable trajectory |
Optional[ndarray]
|
over time, or None if no guess is provided. |
Example
x = Variable("x", shape=(2,))
Linear interpolation from [0,0] to [10,10] over 50 points¶
x.guess = np.linspace([0, 0], [10, 10], 50) print(x.guess.shape) # (50, 2)
max: Optional[np.ndarray]
property
writable
¶
Get the maximum bounds (upper bounds) for the variable.
Returns:
| Type | Description |
|---|---|
Optional[ndarray]
|
Array of maximum values for each element of the variable, or None if unbounded. |
Example
vel = Variable("vel", shape=(3,)) vel.max = [10, 10, 5] print(vel.max) # [10., 10., 5.]
min: Optional[np.ndarray]
property
writable
¶
Get the minimum bounds (lower bounds) for the variable.
Returns:
| Type | Description |
|---|---|
Optional[ndarray]
|
Array of minimum values for each element of the variable, or None if unbounded. |
Example
pos = Variable("pos", shape=(3,)) pos.min = [-10, -10, 0] print(pos.min) # [-10., -10., 0.]
slice: Optional[slice]
property
¶
Get the slice indexing this variable in the unified state/control vector.
After preprocessing, each variable is assigned a canonical position in the unified optimization vector. This property returns the slice object that extracts this variable's values from the unified vector.
This is particularly useful for expert users working with byof (bring-your-own functions) who need to manually index into the unified x and u vectors.
Returns:
| Name | Type | Description |
|---|---|---|
slice |
Optional[slice]
|
Slice object for indexing into unified vector, or None if the variable hasn't been preprocessed yet. |
Example
velocity = ox.State("velocity", shape=(3,))
... after Problem construction ...¶
print(velocity.slice) # slice(2, 5) (for example)
Use in byof functions¶
def my_constraint(x, u, node, params): vel = x[velocity.slice] # Extract velocity from unified state return jnp.sum(vel**2) - 100 # |v|^2 <= 100
__init__(name: str, shape: Tuple[int, ...])
¶
Initialize a Variable object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Name identifier for the variable |
required |
shape
|
Tuple[int, ...]
|
Shape of the variable as a tuple (typically 1D like (3,) for 3D vector) |
required |
Source code in openscvx/symbolic/expr/variable.py
append(other: Optional[Variable] = None, *, min: float = -np.inf, max: float = np.inf, guess: float = 0.0) -> None
¶
Append a new dimension to this variable or merge with another variable.
This method extends the variable's dimension by either: 1. Appending another Variable object (concatenating their dimensions) 2. Adding a single new scalar dimension with specified bounds and guess
The bounds and guesses of both variables are concatenated appropriately.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
Optional[Variable]
|
Another Variable object to append. If None, adds a single scalar dimension with the specified min/max/guess values. |
None
|
min
|
float
|
Minimum bound for the new dimension (only used if other is None). Defaults to -np.inf (unbounded below). |
-inf
|
max
|
float
|
Maximum bound for the new dimension (only used if other is None). Defaults to np.inf (unbounded above). |
inf
|
guess
|
float
|
Initial guess value for the new dimension (only used if other is None). Defaults to 0.0. |
0.0
|
Example
Create a 2D variable and extend it to 3D:
pos_xy = Variable("pos", shape=(2,))
pos_xy.min = [-10, -10]
pos_xy.max = [10, 10]
pos_xy.append(min=0, max=100) # Add z dimension
print(pos_xy.shape) # (3,)
print(pos_xy.min) # [-10., -10., 0.]
print(pos_xy.max) # [10., 10., 100.]
Merge two variables:
pos = Variable("pos", shape=(3,))
vel = Variable("vel", shape=(3,))
pos.append(vel) # Now pos has shape (6,)
Source code in openscvx/symbolic/expr/variable.py
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 | |
Vmap
¶
Bases: Expr
Vectorized map over batched data in symbolic expressions.
Vmap enables data-parallel operations by applying a symbolic expression to each element of a batched array (or multiple arrays). This is the symbolic equivalent of JAX's jax.vmap, allowing efficient vectorized computation without explicit loops.
The expression is defined via a lambda that receives one or more Placeholder arguments, each representing a single element from the corresponding batch. During lowering, this becomes a jax.vmap call.
The behavior depends on the type of each batch element:
- numpy array or Constant: Data is baked into the compiled function at trace time, equivalent to closure-captured values in BYOF.
- Parameter: Data is looked up from the params dict at runtime, allowing the same compiled code to be reused with different values.
- State: Data is extracted from the unified state vector at runtime, enabling vectorized operations over state elements (e.g., multi-agent).
- Control: Data is extracted from the unified control vector at runtime, enabling vectorized operations over control elements.
Attributes:
| Name | Type | Description |
|---|---|---|
_batches |
tuple
|
Tuple of data sources (Constant, Parameter, State, or Control) |
_axis |
int
|
The axis to vmap over (default: 0) |
_placeholders |
tuple
|
Tuple of placeholders used in the expression |
_child |
Expr
|
The expression tree built from the user's lambda |
_is_parameter |
tuple
|
Tuple of bools indicating which batches are Parameters |
_is_state |
tuple
|
Tuple of bools indicating which batches are States |
_is_control |
tuple
|
Tuple of bools indicating which batches are Controls |
Example
Compute distances to multiple reference points (baked-in)::
position = ox.State("position", shape=(3,))
init_poses = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
distances = ox.Vmap(
lambda pose: ox.linalg.Norm(position - pose),
batch=init_poses
)
# distances has shape (3,)
With runtime-updateable Parameter::
refs = ox.Parameter("refs", shape=(10, 3), value=init_poses)
dist_state = ox.State("dist_state", shape=(10,))
dynamics["dist_state"] = ox.Vmap(
lambda pose: ox.linalg.Norm(position - pose),
batch=refs
)
# Later, change the parameter value without recompiling:
problem.parameters["refs"] = new_poses
With multiple batch arguments::
obs_centers = ox.Parameter("obs_centers", shape=(100, 3))
obs_radii = ox.Parameter("obs_radii", shape=(100,))
constraints = ox.Vmap(
lambda center, radius: radius <= ox.linalg.Norm(position - center),
batch=[obs_centers, obs_radii]
)
# constraints has shape (100,)
With State batching (multi-agent)::
# State representing n_agents positions, each 3D
agent_positions = ox.State("agent_positions", shape=(n_agents, 3))
# Apply constraint to each agent's position
constraints = ox.Vmap(
lambda pos: ox.linalg.Norm(pos) <= max_distance,
batch=agent_positions
)
# constraints has shape (n_agents,)
With Control batching::
# Control representing n_thrusters, each scalar
thrusters = ox.Control("thrusters", shape=(n_thrusters,))
# Apply constraint to each thruster
constraints = ox.Vmap(
lambda t: t <= max_thrust,
batch=thrusters
)
# constraints has shape (n_thrusters,)
Batching over a non-default axis::
# Data shaped (features, n_samples) - batch over axis 1
samples = ox.Parameter("samples", shape=(3, n_samples), value=data)
results = ox.Vmap(
lambda sample: ox.linalg.Norm(sample),
batch=samples,
axis=1 # batch over samples, not features
)
# results has shape (n_samples,)
Note
- For static data that won't change, pass a numpy array or Constant to get closure-equivalent behavior (numerically identical to BYOF).
- For data that needs to be updated between iterations, use Parameter.
- For vectorized operations over state/control elements, pass State/Control.
- When using multiple batches, all must have the same size along the vmap axis.
Prefer Constants over Parameters
Use a raw numpy array or Constant unless you specifically need to update the vmap data between solves without recompiling.
Using a Parameter (runtime lookup) may produce different numerical results compared to using a Constant (baked-in), even when the underlying data is identical. This can manifest as:
- Different SCP iteration counts
- Different convergence behavior
- In unlucky cases, convergence to a different local solution
This is likely due to JAX/XLA trace and compilation differences between the two code paths. When data is baked in, JAX sees concrete values at trace time. When data is looked up from a params dict at runtime, JAX traces through the dictionary access, potentially producing different XLA compilation or floating-point operation ordering.
Source code in openscvx/symbolic/expr/vmap.py
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 | |
axis: int
property
¶
The axis being vmapped over.
batch: Union[Constant, Parameter, State, Control]
property
¶
The first batched data source (for single-batch backward compatibility).
batches: Tuple[Union[Constant, Parameter, State, Control], ...]
property
¶
Tuple of batched data sources being vmapped over.
is_control: Tuple[bool, ...]
property
¶
Tuple of bools indicating which batches are Controls (control vector lookup).
is_parameter: Tuple[bool, ...]
property
¶
Tuple of bools indicating which batches are Parameters (runtime lookup).
is_state: Tuple[bool, ...]
property
¶
Tuple of bools indicating which batches are States (state vector lookup).
num_batches: int
property
¶
Number of batch arguments.
placeholder: _Placeholder
property
¶
The first placeholder (for single-batch backward compatibility).
placeholders: Tuple[_Placeholder, ...]
property
¶
Tuple of placeholders used in the inner expression.
__init__(fn: Callable[..., Expr], batch: Union[BatchSource, Sequence[BatchSource]], axis: int = 0)
¶
Initialize a Vmap expression.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[..., Expr]
|
A callable (typically a lambda) that takes one or more Placeholder arguments and returns a symbolic expression. Each Placeholder represents a single element from the corresponding batched data. |
required |
batch
|
Union[BatchSource, Sequence[BatchSource]]
|
The batched data to vmap over. Can be: - A single batch source (numpy array, Constant, Parameter, State, or Control) - A list/tuple of batch sources for multi-argument vmapping Each batch source can be: - numpy array: baked into compiled function (closure-equivalent) - Constant: baked into compiled function (closure-equivalent) - Parameter: looked up from params dict at runtime - State: extracted from unified state vector at runtime - Control: extracted from unified control vector at runtime |
required |
axis
|
int
|
The axis to vmap over. Default is 0 (first axis). Applied to all batch sources. |
0
|
Example
Single batch (baked-in data)::
ox.Vmap(lambda x: ox.linalg.Norm(x), batch=points)
Single batch with Parameter::
refs = ox.Parameter("refs", shape=(10, 3), value=points)
ox.Vmap(lambda ref: ox.linalg.Norm(position - ref), batch=refs)
Multiple batches::
centers = ox.Parameter("centers", shape=(100, 3))
radii = ox.Parameter("radii", shape=(100,))
ox.Vmap(
lambda c, r: r <= ox.linalg.Norm(position - c),
batch=[centers, radii]
)
State batching (multi-agent)::
agent_positions = ox.State("positions", shape=(n_agents, 3))
ox.Vmap(lambda pos: g(pos), batch=agent_positions)
Non-default axis::
# Batch over axis 1 instead of axis 0
data = ox.Parameter("data", shape=(3, n_samples))
ox.Vmap(lambda x: f(x), batch=data, axis=1)
Source code in openscvx/symbolic/expr/vmap.py
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 | |
canonicalize() -> Expr
¶
Canonicalize by canonicalizing the child expression.
Returns:
| Name | Type | Description |
|---|---|---|
Vmap |
Expr
|
A new Vmap with canonicalized child expression |
Source code in openscvx/symbolic/expr/vmap.py
check_shape() -> Tuple[int, ...]
¶
Compute the output shape of the vmapped expression.
The output shape is (batch_size,) + inner_shape, where batch_size is the size of the vmap axis and inner_shape is the shape of the child expression.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[int, ...]
|
Output shape after vmapping |
Example
If data has shape (10, 3) and the inner expression produces a scalar (shape ()), the output shape is (10,).
Source code in openscvx/symbolic/expr/vmap.py
children() -> List[Expr]
¶
Return child expressions.
Returns:
| Name | Type | Description |
|---|---|---|
list |
List[Expr]
|
The vmapped expression and any Parameter/State/Control data sources. These are included so traverse() finds them for parameter/variable collection in preprocessing. |
Source code in openscvx/symbolic/expr/vmap.py
Vstack
¶
Bases: Expr
Vertical stacking operation for symbolic expressions.
Concatenates expressions vertically (along rows for 2D arrays). This is analogous to numpy.vstack() or jax.numpy.vstack().
All input expressions must have the same number of dimensions, and all dimensions except the first must match. The result concatenates along axis 0 (rows).
Attributes:
| Name | Type | Description |
|---|---|---|
arrays |
List of expressions to stack vertically |
Example
Stack vectors to create a matrix:
x = Variable("x", shape=(3,))
y = Variable("y", shape=(3,))
v = Vstack([x, y]) # Result shape (2, 3)
Stack matrices vertically:
A = Variable("A", shape=(3, 4))
B = Variable("B", shape=(2, 4))
C = Vstack([A, B]) # Result shape (5, 4)
Source code in openscvx/symbolic/expr/array.py
__init__(arrays: List[Union[Expr, float, int, np.ndarray]])
¶
Initialize a vertical stack operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
arrays
|
List[Union[Expr, float, int, ndarray]]
|
List of expressions to concatenate vertically. All must have matching dimensions except the first. |
required |
Source code in openscvx/symbolic/expr/array.py
check_shape() -> Tuple[int, ...]
¶
Vertical stack concatenates arrays along the first axis (rows).
Source code in openscvx/symbolic/expr/array.py
Fixed(value: float) -> Tuple[str, float]
¶
Create a fixed boundary condition tuple.
This is a convenience function that returns a tuple ("fixed", value) which can be used to explicitly specify fixed boundary conditions for State or Time objects. Note that plain numbers default to fixed, so this is mainly for clarity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
float
|
Fixed value for the boundary condition. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[str, float]
|
("fixed", value) tuple suitable for use in State.initial, State.final, or Time.initial, Time.final. |
Example
Source code in openscvx/symbolic/expr/state.py
Free(guess: float) -> Tuple[str, float]
¶
Create a free boundary condition tuple.
This is a convenience function that returns a tuple ("free", guess) which can be used to specify free boundary conditions for State or Time objects.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
guess
|
float
|
Initial guess value for the free variable. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[str, float]
|
("free", guess) tuple suitable for use in State.initial, State.final, or Time.initial, Time.final. |
Example
Source code in openscvx/symbolic/expr/state.py
Maximize(guess: float) -> Tuple[str, float]
¶
Create a maximize boundary condition tuple.
This is a convenience function that returns a tuple ("maximize", guess) which can be used to specify that a boundary value should be maximized in the objective function for State or Time objects.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
guess
|
float
|
Initial guess value for the variable to be maximized. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[str, float]
|
("maximize", guess) tuple suitable for use in State.initial, State.final, or Time.initial, Time.final. |
Example
Source code in openscvx/symbolic/expr/state.py
Minimize(guess: float) -> Tuple[str, float]
¶
Create a minimize boundary condition tuple.
This is a convenience function that returns a tuple ("minimize", guess) which can be used to specify that a boundary value should be minimized in the objective function for State or Time objects.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
guess
|
float
|
Initial guess value for the variable to be minimized. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
Tuple[str, float]
|
("minimize", guess) tuple suitable for use in State.initial, State.final, or Time.initial, Time.final. |
Example
Source code in openscvx/symbolic/expr/state.py
ctcs(constraint: Constraint, penalty: str = 'squared_relu', nodes: Optional[Tuple[int, int]] = None, idx: Optional[int] = None, check_nodally: bool = False) -> CTCS
¶
Helper function to create CTCS (Continuous-Time Constraint Satisfaction) constraints.
This is a convenience function that creates a CTCS constraint with the same parameters as the CTCS constructor. Useful for functional-style constraint building.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
constraint
|
Constraint
|
The Constraint to enforce continuously |
required |
penalty
|
str
|
Penalty function type ('squared_relu', 'huber', or 'smooth_relu'). Defaults to 'squared_relu'. |
'squared_relu'
|
nodes
|
Optional[Tuple[int, int]]
|
Optional (start, end) tuple of node indices for enforcement interval. None enforces over entire trajectory. |
None
|
idx
|
Optional[int]
|
Optional grouping index for multiple augmented states |
None
|
check_nodally
|
bool
|
Whether to also enforce constraint at discrete nodes. Defaults to False. |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
CTCS |
CTCS
|
A CTCS constraint wrapping the input constraint |
Example
Using the helper function:
from openscvx.symbolic.expr.constraint import ctcs
altitude_constraint = ctcs(
altitude >= 10,
penalty="huber",
nodes=(0, 100),
check_nodally=True
)
Equivalent to using CTCS constructor:
altitude_constraint = CTCS(altitude >= 10, penalty="huber", nodes=(0, 100))
Also equivalent to using .over() method on constraint:
altitude_constraint = (altitude >= 10).over((0, 100), penalty="huber")
Source code in openscvx/symbolic/expr/constraint.py
to_expr(x: Union[Expr, float, int, np.ndarray]) -> Expr
¶
Convert a value to an Expr if it is not already one.
This is a convenience function that wraps numeric values and arrays as Constant expressions, while leaving Expr instances unchanged. Used internally by operators to ensure operands are proper Expr objects.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Union[Expr, float, int, ndarray]
|
Value to convert - can be an Expr, numeric scalar, or numpy array |
required |
Returns:
| Type | Description |
|---|---|
Expr
|
The input if it's already an Expr, otherwise a Constant wrapping the value |
Source code in openscvx/symbolic/expr/expr.py
traverse(expr: Expr, visit: Callable[[Expr], None])
¶
Depth-first traversal of an expression tree.
Visits each node in the expression tree by applying the visit function to the current node, then recursively visiting all children.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
expr
|
Expr
|
Root expression node to start traversal from |
required |
visit
|
Callable[[Expr], None]
|
Callback function applied to each node during traversal |
required |