Barrier States (BaS) turn safety information into dynamical state variables that can be regulated together with the physical plant. The central design idea is to move safety from an external pointwise condition into an augmented dynamical model that can be used by standard control and optimization machinery.
A state constraint is represented by one or more safety functions
The library uses the convention
For multiple constraints,
This sign convention is used consistently by constraint constructors, barrier functions, safety checks, plotting, and trajectory optimization.
A barrier operator maps a positive safety margin into a quantity that grows near the boundary:
The two built-in operators are the reciprocal barrier
and the logarithmic barrier
Both become large as
from barrier_states.barriers import logarithmic, reciprocal
barrier = reciprocal(constraint, alpha=1.0)
barrier = logarithmic(constraint, alpha=1.0)A constraint object may contain several scalar constraints. Barrier construction supports three modes:
aggregate="none"
aggregate="harmonic"
aggregate="barrier_sum""none" keeps one active barrier coordinate per scalar constraint. This is useful when individual safety quantities should remain visible to the controller.
"harmonic" produces one active barrier through a weighted harmonic aggregation of the safety functions.
"barrier_sum" produces one active barrier by summing the component barrier values.
Aggregation changes the number of active Barrier States, not the underlying definition of physical safety: safety is still checked against all original constraints.
The barrier value
This library deliberately separates the Barrier State subsystem from the physical plant.
bas = DynamicContinuous(model, barrier, gamma=1.0)DynamicContinuous represents only the continuous-time
bas.value(x)
bas.error(x, z)
bas.f(x, z)
bas.g(x, z)
bas.flow(x, z, u)
bas.flow_x(x, z, u)
bas.flow_z(x, z, u)
bas.flow_u(x, z, u)Similarly, DynamicDiscrete represents only the discrete-time Barrier State subsystem:
dbas.value(x)
dbas.error(x, z)
dbas.step(x, z, u)
dbas.step_x(x, z, u)
dbas.step_z(x, z, u)
dbas.step_u(x, z, u)This separation keeps the mathematical roles explicit:
physical model: x dynamics
Barrier State model: z dynamics
embedding: [x, z] dynamics
When a safe reference state
on the consistent manifold. Therefore the Barrier State reference is zero:
bas = DynamicContinuous(model, barrier, gamma=1.0, reference_state=x_ref)The same convention is available in discrete time.
Shifted coordinates are especially convenient for regulation and quadratic costs because the physical target and the Barrier State target can share one augmented reference.
For a control-affine physical system
the continuous Barrier State dynamics are constructed so that, on the consistent manifold, gamma drives off-manifold Barrier State error back toward consistency.
The parameter
gamma > 0controls the rate of that correction. It is a realization parameter, not a replacement for controller tuning.
For
the discrete Barrier State realization propagates the next barrier value and contracts any off-manifold error with rho.
dbas = DynamicDiscrete(model, barrier, rho=0.05)rho may be a scalar, a diagonal vector, or a contraction matrix. Scalar and diagonal entries satisfy
while a full matrix must have spectral radius less than one.
The embedding layer combines the physical state and Barrier State:
Continuous time:
embedded = EmbeddedContinuous(model, bas)
xbar = embedded.augment_state(x)Discrete time:
embedded = EmbeddedDiscrete(model, dbas)
xbar = embedded.augment_state(x)The embedded models expose the ordinary model interface expected by controllers and solvers. For example:
embedded.flow(xbar, u)
embedded.flow_x(xbar, u)
embedded.flow_u(xbar, u)or
embedded.step(xbar, u)
embedded.step_x(xbar, u)
embedded.step_u(xbar, u)The embedding is therefore the bridge between the BaS construction and standard control algorithms.
Once embedded, safety information appears in the augmented state itself. A controller or optimizer can penalize, stabilize, or otherwise regulate the Barrier State coordinates together with the physical states.
This is what allows the same construction to be paired with different methods:
BaS + state feedback / LQR
BaS + PID-style feedback
BaS + SDRE
BaS + DDP / iLQR
BaS + receding-horizon MPC
BaS + custom feedback policies
The library does not force one controller architecture around the Barrier State.
State-Dependent Riccati Equation control uses a state-dependent coefficient representation. For suitable continuous-time systems, the physical dynamics are written relative to a reference as
DynamicContinuousSDC combines the ordinary continuous BaS realization with plant SDC data and a Barrier State SDC factorization. EmbeddedContinuous then assembles the full embedded state-dependent matrices used by SDREFeedback.
bas = DynamicContinuousSDC(model, barrier, plant_sdc, gamma=1.0, reference_state=x_ref, reference_input=u_ref)
embedded = EmbeddedContinuous(model, bas)
controller = SDREFeedback(embedded, Q, R)The SDC factorization is part of the SDRE representation; it does not change the underlying safe set or barrier definition.
For discrete-time embedded dynamics, BaS can be included directly in a finite-horizon objective. With an augmented reference
cost = quadratic_discrete(Q, R, Qf, x_ref=xbar_ref, u_ref=u_ref, x_final_ref=xbar_ref)The DDP-family solver then optimizes the embedded trajectory:
solution = ddp(embedded, cost, xbar0, initial_controls, method="ilqr")The same solver can use either an iLQR first-order dynamics approximation or full second-order DDP when the model provides the required second-order tensors.
DDPMPC repeatedly solves the finite-horizon problem online:
measure x_k
↓
solve finite-horizon DDP/iLQR
↓
apply only u*_0
↓
plant advances to x_{k+1}
↓
shift the optimized controls as the next warm start
↓
solve again
The full predicted state and control sequences are retained for diagnostics and animation, while only the first optimized control is returned to the plant at each call.
Barrier States provide a dynamics-level way to represent and regulate safety. They should not be interpreted as a universal safety wrapper that makes an arbitrary controller safe automatically.
In practice, safety conclusions depend on the assumptions of the selected Barrier State construction, safe initialization, numerical validity, and successful bounded regulation of the embedded safety dynamics. The resulting guarantees also depend on the chosen controller and its ability to regulate the nonlinear embedded system over the relevant region of operation.
The library therefore keeps safety checks explicit and exposes Barrier State and constraint histories so that a result can be inspected rather than assumed.