Skip to content

symbolic

Symbolic expression system for trajectory optimization.

See openscvx.symbolic.expr for detailed documentation and examples.

ConstraintSet dataclass

Container for categorized symbolic constraints.

This dataclass holds all symbolic constraint types in a structured way, providing type safety and a clear API for accessing constraint categories. This is a pre-lowering container - after lowering, constraints live in LoweredJaxConstraints and LoweredCvxpyConstraints.

The constraint set supports two lifecycle stages:

  1. Before preprocessing: Raw constraints live in unsorted
  2. After preprocessing: unsorted is empty, constraints are categorized

Use is_categorized to check which stage the constraint set is in.

Attributes:

Name Type Description
unsorted List[Union[Constraint, CTCS]]

Raw constraints before categorization. Empty after preprocessing.

ctcs List[CTCS]

CTCS (continuous-time) constraints.

nodal List[NodalConstraint]

Non-convex nodal constraints (will be lowered to JAX).

nodal_convex List[NodalConstraint]

Convex nodal constraints (will be lowered to CVXPy).

cross_node List[CrossNodeConstraint]

Non-convex cross-node constraints (will be lowered to JAX).

cross_node_convex List[CrossNodeConstraint]

Convex cross-node constraints (will be lowered to CVXPy).

Example

Before preprocessing (raw constraints)::

constraints = ConstraintSet(unsorted=[c1, c2, c3])
assert not constraints.is_categorized

After preprocessing (categorized)::

# preprocess_symbolic_problem drains unsorted -> fills categories
assert constraints.is_categorized
for c in constraints.nodal:
    # Process non-convex nodal constraints
    pass
Source code in openscvx/symbolic/constraint_set.py
@dataclass
class ConstraintSet:
    """Container for categorized symbolic constraints.

    This dataclass holds all symbolic constraint types in a structured way,
    providing type safety and a clear API for accessing constraint categories.
    This is a pre-lowering container - after lowering, constraints live in
    LoweredJaxConstraints and LoweredCvxpyConstraints.

    The constraint set supports two lifecycle stages:

    1. **Before preprocessing**: Raw constraints live in `unsorted`
    2. **After preprocessing**: `unsorted` is empty, constraints are categorized

    Use `is_categorized` to check which stage the constraint set is in.

    Attributes:
        unsorted: Raw constraints before categorization. Empty after preprocessing.
        ctcs: CTCS (continuous-time) constraints.
        nodal: Non-convex nodal constraints (will be lowered to JAX).
        nodal_convex: Convex nodal constraints (will be lowered to CVXPy).
        cross_node: Non-convex cross-node constraints (will be lowered to JAX).
        cross_node_convex: Convex cross-node constraints (will be lowered to CVXPy).

    Example:
        Before preprocessing (raw constraints)::

            constraints = ConstraintSet(unsorted=[c1, c2, c3])
            assert not constraints.is_categorized

        After preprocessing (categorized)::

            # preprocess_symbolic_problem drains unsorted -> fills categories
            assert constraints.is_categorized
            for c in constraints.nodal:
                # Process non-convex nodal constraints
                pass
    """

    # Raw constraints before categorization (empty after preprocessing)
    unsorted: List[Union["Constraint", "CTCS"]] = field(default_factory=list)

    # Categorized symbolic constraints (populated by preprocessing)
    ctcs: List["CTCS"] = field(default_factory=list)
    nodal: List["NodalConstraint"] = field(default_factory=list)
    nodal_convex: List["NodalConstraint"] = field(default_factory=list)
    cross_node: List["CrossNodeConstraint"] = field(default_factory=list)
    cross_node_convex: List["CrossNodeConstraint"] = field(default_factory=list)

    @property
    def is_categorized(self) -> bool:
        """True if all constraints have been sorted into categories.

        After preprocessing, `unsorted` should be empty and all constraints
        should be in their appropriate category lists.
        """
        return len(self.unsorted) == 0

    def __bool__(self) -> bool:
        """Return True if any constraint list is non-empty."""
        return bool(
            self.unsorted
            or self.ctcs
            or self.nodal
            or self.nodal_convex
            or self.cross_node
            or self.cross_node_convex
        )

    def __len__(self) -> int:
        """Return total number of constraints across all lists."""
        return (
            len(self.unsorted)
            + len(self.ctcs)
            + len(self.nodal)
            + len(self.nodal_convex)
            + len(self.cross_node)
            + len(self.cross_node_convex)
        )
is_categorized: bool property

True if all constraints have been sorted into categories.

After preprocessing, unsorted should be empty and all constraints should be in their appropriate category lists.

SymbolicProblem dataclass

Container for symbolic problem specification.

This dataclass holds a trajectory optimization problem in symbolic form, either as raw user input or after preprocessing/augmentation. It provides a typed interface for the preprocessing and lowering pipeline.

Lifecycle Stages
  1. Before preprocessing: User creates with raw dynamics, states, controls, and unsorted constraints. Propagation fields are None.
  2. After preprocessing: Dynamics and states are augmented (CTCS, time dilation), constraints are categorized, propagation fields are populated.

Use is_preprocessed to check whether preprocessing has completed.

Attributes:

Name Type Description
dynamics Expr

Symbolic dynamics expression (dx/dt = f(x, u)). After preprocessing, includes CTCS augmented state dynamics.

states List[State]

List of State objects. After preprocessing, includes time state and CTCS augmented states.

controls List[Control]

List of Control objects. After preprocessing, includes time dilation control.

constraints ConstraintSet

ConstraintSet holding all constraints. Before preprocessing, raw constraints live in constraints.unsorted. After preprocessing, constraints are categorized into ctcs, nodal, nodal_convex, etc.

parameters Dict[str, any]

Dictionary mapping parameter names to numpy arrays.

N int

Number of discretization nodes.

node_intervals List[Tuple[int, int]]

List of (start, end) tuples for CTCS constraint intervals. Populated during preprocessing when CTCS constraints are sorted.

dynamics_prop Optional[Expr]

Propagation dynamics (may include extra states). None before preprocessing, populated after.

states_prop Optional[List[State]]

Propagation states (may include extra states). None before preprocessing, populated after.

controls_prop Optional[List[Control]]

Propagation controls (typically same as controls). None before preprocessing, populated after.

Example

Before preprocessing::

problem = SymbolicProblem(
    dynamics=dynamics_expr,
    states=[x, v],
    controls=[u],
    constraints=ConstraintSet(unsorted=[c1, c2, c3]),
    parameters={"mass": 1.0},
    N=50,
)
assert not problem.is_preprocessed

After preprocessing::

processed = preprocess_symbolic_problem(problem, time=time_config)
assert processed.is_preprocessed
assert processed.constraints.is_categorized
# Now ready for lowering
lowered = lower_symbolic_problem(processed)
Source code in openscvx/symbolic/problem.py
@dataclass
class SymbolicProblem:
    """Container for symbolic problem specification.

    This dataclass holds a trajectory optimization problem in symbolic form,
    either as raw user input or after preprocessing/augmentation. It provides
    a typed interface for the preprocessing and lowering pipeline.

    Lifecycle Stages:
        1. **Before preprocessing**: User creates with raw dynamics, states,
           controls, and unsorted constraints. Propagation fields are None.
        2. **After preprocessing**: Dynamics and states are augmented (CTCS,
           time dilation), constraints are categorized, propagation fields
           are populated.

    Use `is_preprocessed` to check whether preprocessing has completed.

    Attributes:
        dynamics: Symbolic dynamics expression (dx/dt = f(x, u)).
            After preprocessing, includes CTCS augmented state dynamics.
        states: List of State objects. After preprocessing, includes
            time state and CTCS augmented states.
        controls: List of Control objects. After preprocessing, includes
            time dilation control.
        constraints: ConstraintSet holding all constraints. Before preprocessing,
            raw constraints live in `constraints.unsorted`. After preprocessing,
            constraints are categorized into ctcs, nodal, nodal_convex, etc.
        parameters: Dictionary mapping parameter names to numpy arrays.
        N: Number of discretization nodes.
        node_intervals: List of (start, end) tuples for CTCS constraint intervals.
            Populated during preprocessing when CTCS constraints are sorted.

        dynamics_prop: Propagation dynamics (may include extra states).
            None before preprocessing, populated after.
        states_prop: Propagation states (may include extra states).
            None before preprocessing, populated after.
        controls_prop: Propagation controls (typically same as controls).
            None before preprocessing, populated after.

    Example:
        Before preprocessing::

            problem = SymbolicProblem(
                dynamics=dynamics_expr,
                states=[x, v],
                controls=[u],
                constraints=ConstraintSet(unsorted=[c1, c2, c3]),
                parameters={"mass": 1.0},
                N=50,
            )
            assert not problem.is_preprocessed

        After preprocessing::

            processed = preprocess_symbolic_problem(problem, time=time_config)
            assert processed.is_preprocessed
            assert processed.constraints.is_categorized
            # Now ready for lowering
            lowered = lower_symbolic_problem(processed)
    """

    # Core problem specification
    dynamics: "Expr"
    states: List["State"]
    controls: List["Control"]
    constraints: ConstraintSet
    parameters: Dict[str, any]
    N: int

    # CTCS node intervals (populated during preprocessing)
    node_intervals: List[Tuple[int, int]] = field(default_factory=list)

    # Propagation (None before preprocessing, populated after)
    dynamics_prop: Optional["Expr"] = None
    states_prop: Optional[List["State"]] = None
    controls_prop: Optional[List["Control"]] = None

    @property
    def is_preprocessed(self) -> bool:
        """True if the problem has been preprocessed and is ready for lowering.

        A problem is considered preprocessed when:
        1. All constraints have been categorized (unsorted is empty)
        2. Propagation dynamics have been set up
        """
        return self.constraints.is_categorized and self.dynamics_prop is not None
is_preprocessed: bool property

True if the problem has been preprocessed and is ready for lowering.

A problem is considered preprocessed when: 1. All constraints have been categorized (unsorted is empty) 2. Propagation dynamics have been set up