Skip to content

Latest commit

 

History

History
363 lines (251 loc) · 9.08 KB

conventions.md

File metadata and controls

363 lines (251 loc) · 9.08 KB

Conventions

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.

Safety sign convention

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.

State and input arrays

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 state ordering

Embedded states always use

$$ \bar x=\begin{bmatrix}x \\ z\end{bmatrix}. $$

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)

Continuous model contract

A continuous model has

model.domain == "continuous"
model.n
model.m

and 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

$$ \dot x=f(x)+g(x)u. $$

Discrete model contract

A discrete model has

model.domain == "discrete"
model.n
model.m

and 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")

Constraint shapes

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.

Barrier shapes

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.

Dynamic BaS versus embedding

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.

Controller call signatures

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.

Feedback-gain sign convention

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)

Reference states

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.

Input bounds

Controller and solver bounds may be scalars or input-sized vectors:

input_lower=-2.0
input_upper=2.0

or

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.

Derivative orientation

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).

Second-order discrete dynamics

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.

Simulation result orientation

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 behavior

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.

Finite values and mutation

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.

Naming

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 _.