This page collects the interface and array conventions used throughout Barrier States Python. Following these conventions is the easiest way to make custom models, constraints, controllers, and solvers interoperate with the built-in components.
Every scalar safety function uses
h(x) > 0 safe
h(x) = 0 boundary
h(x) < 0 unsafe
Safe initial states and safe reference states are expected to be strictly inside the safe set.
Single states and inputs are one-dimensional NumPy arrays:
physical state x (n,)
input u (m,)
Barrier State z (nz,)
embedded state xbar (n + nz,)
Trajectories are time-major:
state trajectory X (N + 1, n)
input trajectory U (N, m)
feedback gains K (N, m, n)
feedforward gains k (N, m)
For embedded systems, replace n by the embedded dimension where appropriate.
This row-per-time-step convention is intentional and differs from MATLAB's common column-per-time-step convention.
Embedded states always use
In code:
x = xbar[:embedded.physical_dim]
z = xbar[embedded.physical_dim:]Prefer the explicit helpers when possible:
xbar = embedded.augment_state(x)
x, z = embedded.split_state(xbar)
error = embedded.barrier_error(xbar)A continuous model has
model.domain == "continuous"
model.n
model.mand exposes
model.flow(x, u) # (n,)
model.flow_x(x, u) # (n, n)
model.flow_u(x, u) # (n, m)Control-affine models used by continuous BaS additionally expose
model.f(x) # (n,)
model.g(x) # (n, m)with
A discrete model has
model.domain == "discrete"
model.n
model.mand exposes
model.step(x, u) # (n,)
model.step_x(x, u) # (n, n)
model.step_u(x, u) # (n, m)Built-in continuous models can be converted with
from barrier_states.models import discretize
discrete_model = discretize(model, dt=0.1, method="euler")A StateConstraint with q scalar constraints uses
h(x) (q,)
h_x(x) (q, n)
h_xx(x) (q, n, n) when available
Rows of h_x correspond to scalar constraints. Hessian slice h_xx[i] is the Hessian of h_i.
state_indices arguments use ordinary zero-based Python indexing.
A barrier may expose either one active coordinate or several depending on its aggregation mode.
aggregate="none" one active barrier per scalar constraint
aggregate="harmonic" one active barrier
aggregate="barrier_sum" one active barrier
Use the barrier object's metadata rather than assuming the number of Barrier States from the number of physical constraints.
DynamicContinuous and DynamicDiscrete represent the Barrier-State subsystem only. Their state dimension is the Barrier-State dimension, while physical_dim records the plant dimension.
The embedding classes create the full augmented model:
bas = DynamicContinuous(model, barrier, ...)
embedded = EmbeddedContinuous(model, bas)or
dbas = DynamicDiscrete(model, barrier, ...)
embedded = EmbeddedDiscrete(model, dbas)Controllers and trajectory optimizers normally operate on the embedded model.
Continuous-time controllers are callable as
u = controller(t, x)Discrete-time controllers are callable as
u = controller(k, x)Custom controller functions should follow the same convention so they can be passed directly to the simulation utilities.
The library uses two different gain signs, because the gains come from two different sources. This is the one convention that cannot be inferred from a function signature, so read it before mixing controllers.
Designed gains use the standard negative-feedback form. StateFeedback,
SDREFeedback, and PIDBaSFeedback all subtract the gain term:
StateFeedback u = u_ref - K (x - x_ref)
SDREFeedback u = u_ref - K e, K = R^-1 B' P
PIDBaSFeedback u = u_ref - Kp e_p - Ki e_i - Kd e_d - Kb z
Here K is the positive-definite LQR/CARE gain, so a stabilizing K has
positive entries on a positively coupled channel.
Solver gains are already negated. The DDP/iLQR backward pass returns
K = -Quu^-1 Qux, so TrajectoryPolicy adds the gain term:
TrajectoryPolicy u_k = U_k + K_k (x_k - X_k), K_k = solution.feedback_gains[k]
Warning
Do not pass solution.feedback_gains[k] straight into StateFeedback. The
solver gain is already negated, so StateFeedback would negate it a second
time and apply positive feedback. Nothing raises in that case; the closed
loop simply destabilizes. Negate it explicitly if you need a static
controller from a solver gain:
controller = StateFeedback(embedded, -solution.feedback_gains[0],
reference_state=xbar_ref)When a Barrier-State realization receives reference_state=x_ref, the Barrier-State coordinate is shifted so the consistent Barrier-State reference is zero.
Use
xbar_ref = embedded.augment_state(x_ref)instead of constructing the augmented reference by hand.
The reference state must be strictly safe because the barrier must be finite there.
Controller and solver bounds may be scalars or input-sized vectors:
input_lower=-2.0
input_upper=2.0or
input_lower=np.array([-2.0, -1.0])
input_upper=np.array([ 2.0, 3.0])Unspecified sides are treated as unbounded.
In the DDP-family solver, these limits are applied as forward-pass saturation. They are not a box-constrained or active-set DDP backward pass.
Jacobians use output-by-input orientation:
flow_x (n, n)
flow_u (n, m)
step_x (n, n)
step_u (n, m)
For the discrete quadratic stage cost,
lx (n,)
lu (m,)
lxx (n, n)
luu (m, m)
lxu (n, m)
The DDP solver internally uses the control-by-state orientation where required by the backward pass, but public cost derivatives retain lxu.shape == (n, m).
Full method="ddp" requires output-index-last tensors:
step_xx(x, u) (n, n, n)
step_uu(x, u) (m, m, n)
step_ux(x, u) (m, n, n)
The last axis indexes the output component of the discrete step map. For scalar-output models, a plain matrix with the corresponding first two dimensions may also be accepted by the solver.
Use method="ilqr" when these second-order dynamics are not available.
Continuous simulation results use
time (T,)
states (T, n)
controls (T, m) or simulation-specific sampled equivalent
Discrete simulation results use
states (N + 1, n)
controls (N, m)
Always use the returned result object's arrays rather than reconstructing control timing from assumptions.
Plotting functions create and return Matplotlib objects but do not call plt.show() internally.
A typical example therefore ends with
plot_state_trajectory_2d(...)
plot_control(...)
plot_bas_state(...)
plt.show()Animation helpers return an AnimationResult that carries the animation, its
figure, role-keyed axes and artists, and the arrays the helper derived. Keep
that object alive until plt.show() or file export:
result = animate_cart_pole(...)
result.axes["scene"].set_title("Swing-up")
plt.show()Export uses either the save argument or the method. Both take one path and
choose the writer from its suffix:
animate_cart_pole(..., save="outputs/cart_pole.gif")
result.save("outputs/cart_pole.mp4", fps=30).gif needs only Pillow, which Matplotlib already requires. .mp4 needs the
external ffmpeg binary and raises RuntimeError when it is missing.
Public numerical interfaces expect real, finite arrays unless a parameter explicitly allows infinity, such as an unbounded input limit.
Library functions are designed not to mutate user-supplied state, control, weight, and initial-guess arrays. Treat returned arrays as outputs rather than relying on undocumented shared storage.
The library uses explicit names that mirror the mathematics:
DynamicContinuous / DynamicDiscrete Barrier-State subsystem
EmbeddedContinuous / EmbeddedDiscrete full [x, z] dynamics
StateFeedback fixed state feedback
SDREFeedback pointwise Riccati feedback
TrajectoryPolicy policy from a DDP solution
DDPMPC receding-horizon DDP/iLQR controller
Prefer these public abstractions over importing private modules whose names begin with _.