Skip to content

Latest commit

 

History

395 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CBFKit: A Control Barrier Function Toolbox for Robotics Applications

Python 3.10–3.12 CI License: BSD-3-Clause arXiv Open In Colab

JAX-based Control Barrier Function (CBF) safety filters for robotics. CBFKit wraps a CBF-QP around any nominal controller or learned policy, so the commands that reach the robot keep the state inside the safe set of the barrier you supply (see Scope and assumptions for what that guarantee rests on). The constraints come from automatic differentiation, the filter JIT-compiles for the control loop, and a modular simulator lets you test the closed loop.

ANYmal-C warehouse delivery in Isaac Lab: the unfiltered walking policy contacts a crossing cart; with CBFKit's batched safety filter the robot adjusts and reaches the goal

Isaac Lab, frozen ANYmal-C walking policy, moving cart. Left: unfiltered. Right: velocity commands filtered by a CBF-QP.

Quick start · Examples · Tutorials · Paper · Project page

  • Safety filters — CBF-CLF-QP controllers for control-affine systems in vanilla, robust (bounded disturbance) and stochastic (SDE) variants, an estimate-feedback risk-aware variant with a Gaussian chance-constraint margin, and an adaptive CVaR-CBF; high-order barriers via relative-degree rectification; a fast interior-point QP solver.
  • Planning — Model Predictive Path Integral (MPPI) control with reach-avoid and STL-style costs, and a receding-horizon MPC.
  • Simulation and estimation — a functional planner → controller → plant → sensor → estimator pipeline, EKF/UKF estimators, Monte Carlo rollouts with jax.vmap, and matplotlib/Plotly/Manim rendering.
  • Integrations — a standalone SafetyFilter, a Gymnasium wrapper, a batched PyTorch bridge for Isaac Lab, a MuJoCo/MJX plant with Unitree G1 examples, and ROS2 node generation.

Installation

Requires Python 3.10–3.12. There is no PyPI release yet; install from GitHub:

pip install "cbfkit @ git+https://github.com/bardhh/cbfkit.git"

The core install covers the safety filters, planners, simulator, estimators, code generation (Jinja2), and matplotlib plotting. Optional features live behind extras, e.g.

pip install "cbfkit[gymnasium] @ git+https://github.com/bardhh/cbfkit.git"
Optional extras
Extra Adds Used by
gymnasium Gymnasium ≥ 1.0 cbfkit.wrappers.gymnasium.SafetyFilterWrapper
neural Flax, Optax learned barrier functions (examples/neural_cbf/)
casadi CasADi get_solver("casadi")
cvxopt CVXOPT (kvxopt on Apple Silicon) get_solver("cvxopt")
solvers casadi + cvxopt
plotly Plotly interactive plots
vis alias of plotly
optuna Optuna parameter sweeps (examples/parameter_sweep/)
codegen Black formatting generated model code (generation itself needs no extra)
manim Manim (also needs ffmpeg and LaTeX on the system) CBFAnimator(backend="manim"), 3D renders
torch PyTorch ≥ 2.8, JAX ≥ 0.6.2 the Isaac Lab / PyTorch bridge
mujoco MuJoCo + MJX (pinned to one version) MujocoPlant, Unitree G1 examples
all gymnasium, neural, solvers, vis, optuna, codegen (not manim, torch, mujoco)
dev all plus pytest, ruff, black, mypy, jupyter development

manim, torch and mujoco are excluded from all and dev because they pull in large or platform-specific runtimes; select them explicitly, e.g. pip install "cbfkit[mujoco] @ git+https://github.com/bardhh/cbfkit.git".

For development, clone and install editable:

git clone https://github.com/bardhh/cbfkit.git && cd cbfkit
pip install -e ".[dev]"
pytest -m "not slow" tests
Docker

VS Code Dev Container. Open the project in VS Code and reopen in container, choosing the CBFKit CPU Dev Container at .devcontainer/cbfkit-container.

Docker Compose

docker compose -f .devcontainer/docker-compose.yml build cbfkit
docker compose -f .devcontainer/docker-compose.yml run --rm cbfkit bash
docker compose -f .devcontainer/docker-compose.yml down

GPU (Linux only)

docker compose -f .devcontainer/docker-compose.yml --profile gpu build cbfkit_gpu
docker compose -f .devcontainer/docker-compose.yml --profile gpu run --rm cbfkit_gpu bash

Quick start

A safety filter needs three things: dynamics in control-affine form, a barrier function h(x) whose zero super-level set is the safe set, and the commands you want to keep safe. Here a naive "drive straight at the goal" command is filtered around a keep-out disc:

import jax.numpy as jnp
from cbfkit.certificates import generate_certificate
from cbfkit.certificates.conditions.barrier_conditions.zeroing_barriers import linear_class_k
from cbfkit.wrappers import SafetyFilter

# Single integrator on the plane: x_dot = u, with x = [px, py]
dynamics = lambda x: (jnp.zeros(2), jnp.eye(2))

# Keep-out disc of radius 0.5 at (2, 0); the safe set is {x : h(x) >= 0}
obstacle, radius = jnp.array([2.0, 0.0]), 0.5
barrier = generate_certificate(
    certificate=lambda x: jnp.sum((x - obstacle) ** 2) - radius**2,
    certificate_conditions=linear_class_k(1.0),  # h_dot >= -1.0 * h
)

# Minimally modify each commanded velocity so the disc is never entered
dt = 0.05  # the filter keeps its own clock; you still integrate the plant yourself
shield = SafetyFilter.from_cbf_qp(
    dynamics=dynamics, barriers=barrier, control_limits=jnp.array([2.0, 2.0]), dt=dt
)

x, goal = jnp.array([0.0, 0.1]), jnp.array([4.0, 0.0])
closest = jnp.inf
for _ in range(150):
    u_nom = (goal - x) / jnp.linalg.norm(goal - x)  # naive command, aimed through the obstacle
    u, info = shield.filter(x, u_nom)               # info["fallback_used"] flags a QP failure
    x = x + dt * u
    closest = jnp.minimum(closest, jnp.linalg.norm(x - obstacle))
print(f"final position {x.round(2)}, closest approach {closest:.2f} m (radius {radius})")

which prints

final position [ 3.96 -0.01], closest approach 0.56 m (radius 0.5)

The unfiltered command would drive straight through the disc; the filter bends the path around it and lets the robot continue to the goal. SafetyFilter.filter works with any control loop. It keeps its own clock, PRNG key and solver warm start, and on a QP failure it falls back to the nominal command by default (fallback= accepts other strategies).

The same controllers plug into CBFKit's simulator for closed-loop studies with planners, sensors and estimators. A unicycle version of this example, with the CBF-QP running inside the simulator loop, is examples/unicycle/reach_goal/unicycle_reach_avoid_cbf.py.

Scope and assumptions

CBFKit implements CBF-based controllers whose safety properties hold under the conditions of the underlying theory, not unconditionally. In particular:

  • Certificate validity. The filter enforces h_dot(x, u) >= -alpha(h(x)) at each step. Forward invariance of the safe set follows only if h is a valid CBF for the model you supply and the QP stays feasible. Learned barriers (see Neural CBF) are candidates fitted to samples, not certificates.
  • Model mismatch. The certificate covers the model the filter sees. For the Unitree G1 demos that is a reduced-order command model; tracking error of the walking policy enters as a measured disturbance bound in the robust variant, not as a guarantee about the full robot.
  • Sampled implementation. Constraints are enforced at the controller rate on a continuous-time condition. Inter-sample behaviour, solver tolerances and the fallback action on QP failure all matter in practice; the examples report minimum barrier values and fallback counts for this reason.
  • Monte Carlo results are empirical. Rollout-based risk estimates in the stochastic and risk-aware examples are evaluations, not proofs.

The controller variants target the following model classes:

Variant Model
vanilla $\dot{x} = f(x) + g(x)u$
robust $\dot{x} = f(x) + g(x)u + w$ with a bounded additive disturbance $\lVert w \rVert \le w_{\max}$ (2-norm or sup-norm, set by disturbance_norm)
stochastic $dx = \big(f(x) + g(x)u\big)dt + \sigma(x)dw$ with $w$ a Wiener process

Examples and tutorials

Examples use pre-built systems from cbfkit.systems (unicycle, single and double integrator, kinematic bicycle, quadrotor, fixed-wing UAV, Van der Pol, a nonlinear 2D system, pedestrians) and need no code generation. examples/README.md lists them in a recommended order:

python examples/unicycle/reach_goal/unicycle_reach_avoid_cbf.py
python examples/unicycle/reach_goal/mppi_cbf.py
cbfkit-bench list          # benchmark scenarios with sweep configs and comparisons

Tutorials show how to define a new system and generate its dynamics, controllers, certificates and ROS2 node with cbfkit.codegen. See tutorials/README.md.

Tutorial Description
code_generation_tutorial.ipynb Generate dynamics, controllers, and certificates for a Van der Pol oscillator
multi_robot_coordination.ipynb Multi-robot CBF coordination with code generation
rectify_relative_degree.ipynb High-order CBFs for constraints with relative degree above one
mppi_cbf_reach_avoid.py MPPI + CBF for unicycle reach-avoid
mppi_stl_reach_avoid.py MPPI with Signal Temporal Logic specifications
single_integrator_dynamic_obstacles.py Dynamic obstacle avoidance
multi_robot_3d_reachavoid.py 3D multi-robot reach-avoid rendered with Manim

There is no separate API reference yet. The docstrings, the examples above, and the project page are the documentation.

Integrations

Gymnasium: safe RL without retraining

SafetyFilterWrapper filters every action from an RL policy through a CBF-QP before it reaches the environment. It works with PPO, SAC, or any algorithm that emits continuous actions, and needs no change to the policy. Requirements: a Box action space, a control-affine model of the environment, barrier certificates for it, and the observation-to-state and action-to-control mappings if they are not the identity.

Safe RL: naive vs CBF-filtered policy

pip install "cbfkit[gymnasium] @ git+https://github.com/bardhh/cbfkit.git"
python examples/gymnasium/safe_single_integrator.py

Isaac Lab and PyTorch: batched policy commands

BatchedSafetyFilter runs one CBFKit controller across many parallel environments with independent histories, clocks, random streams and resets. The optional torch extra adds TorchSafetyFilter, which copies PyTorch tensors through DLPack on their current device rather than aliasing them; it is an inference bridge and does not carry gradients back to the policy.

The warehouse example filters planar velocity requests before a frozen ANYmal-C walking policy in Isaac Lab, using simulator obstacle state. Each controller ran the same 24 scenarios with a crossing cart. Two of them fell within 1.2 s of startup, before any obstacle interaction, in both the stop and CBF runs; those two are excluded from every row below.

Controller (22 trials each) Obstacle contacts Falls Clean deliveries
Unfiltered policy 21 17 0
Distance-based stop rule 0 1 21
CBF-QP filter 0 0 22

Median filter latency for the eight-robot batch was 2.8 ms including the bridge. The counts are descriptive: hidden simulator and policy state was not held equal across modes, so they do not establish a causal advantage over stopping. The validation report has the unfiltered 24-trial table and states what the experiment does and does not establish.

Integration guide · Reproduce the warehouse demo · Measurements and limitations

MuJoCo/MJX: humanoid locomotion under reduced-order CBFs

Left: the same walking policy with no safety filter. Right: CBFKit.

Goal past an obstacle. Left: the walking policy alone walks through the keep-out. Right: the robust CoM CBF bends the path around it.

Goal past an obstacle. Left: the walking policy alone walks through the keep-out. Right: the robust CoM CBF bends the path around it.

Plaza with two pillars and three pedestrians. Left: unfiltered, the robot walks through the keep-outs. Right: high-order CBFs on tracked agents keep every one clear.

Plaza with two pillars and three pedestrians. Left: unfiltered, the robot walks through the keep-outs. Right: high-order CBFs on tracked agents keep every one clear.

1.5 m gap between two pedestrians. Left: the goal-directed policy drives straight into them. Right: the rotating-ellipse footprint CBF with a committed sidestep turns the G1 sideways and threads the gap.

1.5 m gap between two pedestrians. Left: the goal-directed policy drives straight into them. Right: the rotating-ellipse footprint CBF with a committed sidestep turns the G1 sideways and threads the gap.

26 pedestrians who never yield to the robot. Left: the goal-directed policy collides in the crush. Right: social MPPI in front of a relaxed CBF-QP crosses with the barrier positive throughout.

26 pedestrians who never yield to the robot. Left: the goal-directed policy collides in the crush. Right: social MPPI in front of a relaxed CBF-QP crosses with the barrier positive throughout.

A Unitree G1 runs in MJX through sim.execute(plant=MujocoPlant(...)) with a pretrained walking policy underneath and a CBF-QP on the centre-of-mass command above it: keep-out barriers on obstacles and tracked pedestrians, a rotating-ellipse footprint for squeezing through gaps, and an optional socially tuned MPPI planner in front of the filter. The certificate covers the command-side reduced model; tracking error enters as a measured disturbance bound. In each clip the left panel runs the same walking policy with the certificate removed (and, in the corridor and scramble, without the MPPI planner), the floor discs are the keep-out sets coloured by their barrier value, and the HUD plots the minimum barrier value of both runs. The clips are rendered from logged runs by examples/mujoco/g1_showcase.py (simulate, then render --side-by-side); the scramble uses 26 pedestrians and the corridor a 1.5 m gap, where the examples' own defaults are 40 and 1.25 m.

pip install "cbfkit[mujoco] @ git+https://github.com/bardhh/cbfkit.git"
python examples/mujoco/g1_navigate.py

Scramble crossing · Certified sidestep · Plaza among pedestrians · Goal past an obstacle · Sampling MPC over MJX rollouts

ROS2

CBFKit is a Python toolbox; it does not depend on ROS. The code generation pipeline (cbfkit.codegen.create_new_system.generate_model) emits a ROS2 controller node script next to each generated model, under <model>/ros2/controller.py, which you adapt to your message types. The code generation tutorial walks through it.

QP solver

CBF-CLF-QPs are tiny (a handful of decision variables, tens of constraints) and are solved at the control rate. CBFKit ships a Mehrotra predictor-corrector primal-dual interior-point solver written for this regime, selected with solver=get_solver("fast") on any CBF-QP controller; the default remains get_solver("jaxopt") (OSQP), and CVXOPT and CasADi backends are available behind extras. The fast solver's advantage is reliability rather than raw throughput: its barrier-regularized Newton system stays well-conditioned on the slack-relaxed, ill-conditioned QPs that stall OSQP (the G1 scramble example hit one such degenerate QP), it runs a fixed budget of 16 Newton iterations (benign problems converge by iteration 8), and it cold-restarts if a warm start stalls.

Wall time per solve with each solver wrapped in jax.jit, which is the path the simulator's JIT mode and any jitted controller take, on one fixed random positive-definite QP per size, 200 repetitions after a warm-up call, CPU only (Apple M5 Max, JAX 0.6.2, float64):

Size (n×m) JAXopt OSQP fast
2×5 30 µs 34 µs
4×10 38 µs 43 µs
8×20 83 µs 50 µs

Called eagerly from Python instead, OSQP takes about 45 ms per solve at every size because each of its iterations is dispatched separately, while the fast solver stays near 50 µs; that eager gap is what earlier versions of this README reported as a 700–880× speed-up. Both solvers agree to about 1e-5 on these problems; the benchmark checks timing, not accuracy. Reproduce either table with:

python benchmarks/qp_solver_comparison.py --jit --no-plot   # jitted, JAX solvers only
python benchmarks/qp_solver_comparison.py                   # eager, includes CVXOPT

Gallery

Neural CBF: learn a candidate barrier from data

When obstacles are hard to describe analytically (point clouds, occupancy maps, scanned environments), a small network can learn h(x) from labelled safe/unsafe states and plug straight into the CBF-QP controller. The result is a fitted candidate barrier; validity in the sense of Scope and assumptions has to be checked separately.

Neural CBF: agent avoiding a learned obstacle

python examples/neural_cbf/neural_cbf_obstacle_avoidance.py

Multi-robot 3D coordination with Manim

Multi-robot reach-avoid in 3D rendered with CBFKit's Manim backend. The same backend renders 2D CBFAnimator scenes: pass backend="manim" (or "manim-<low|medium|high|production>") and save("out.mp4") writes the video (.gif also supported).

Manim 3D render of multi-robot reach-avoid

python tutorials/multi_robot_3d_reachavoid.py
More examples
Ellipsoidal-obstacle CBF
Ellipsoidal-obstacle CBF 🔗
Unicycle reach-goal with linear class-K
Stochastic CBF
Stochastic CBF (SDE) 🔗
Safety under Brownian disturbance
Robust CBF
Robust CBF 🔗
Worst-case bounded disturbance
MPPI rollouts
MPPI rollout sampling 🔗
Sampling-based planning
MPPI reach-avoid
MPPI reach-avoid 🔗
Sampling-based planning with goal + obstacle cost
MPPI navigation among pedestrians
MPPI among pedestrians 🔗
Sampling-based planning with moving agents
Multi-robot 2D coordination
Multi-robot 2D 🔗
Coordination via shared CBFs
Pedestrian head-on
Pedestrian head-on 🔗
Dynamic-agent avoidance
Fixed-wing aerial 3D
Fixed-wing aerial 3D 🔗
UAV reach-drop-point in 3D
EKF state estimation
EKF state estimation 🔗
Unicycle reach-goal under measurement noise
Van der Pol CLF
Van der Pol (CLF) 🔗
Nonlinear regulation to the origin
Model Predictive Control
Model Predictive Control 🔗
Receding-horizon LTI tracking
Quadrotor 6-DOF geometric tracking
Quadrotor 6-DOF 🔗
Geometric SE(3) tracking + altitude CBF
Monte Carlo safety evaluation
Monte Carlo safety evaluation 🔗
200 stochastic CBF rollouts (jax.vmap), live empirical risk

Also in the repository: estimate-feedback risk-aware CBFs, adaptive CVaR-CBF, barrier-activated controllers, parameter sweeps, quadrotor attitude control, and a 2D Manim animator.

Simulation architecture

cbfkit_architecture

cbfkit.simulation.simulator.execute runs the loop planner → nominal controller → safety controller → plant → integrator → sensor → estimator. If the planner returns a control trajectory, the nominal controller is skipped and the safety controller receives it directly. If it returns a state trajectory, the nominal controller converts it to a control input first. Pass use_jit=True for the lax.scan path, plant=MujocoPlant(...) in place of dynamics + integrator for MJX, and use conduct_monte_carlo or the jax.vmap path for batches.

In the simulator each component is a pure function returning (output, updated_data):

Component Signature Returns
Dynamics (x) (f, g)
Nominal controller (t, x, key, reference) (u, ControllerData)
Controller (safety filter) (t, x, u_nom, key, data) (u, ControllerData)
Planner (t, x, u_prev, key, data) (u_traj | None, PlannerData)
Cost function (state, action) cost

Legacy controller signatures such as (t, x) or (t, x, u_nom) are adapted automatically by cbfkit.controllers.setup_controller. The SafetyFilter and Gymnasium wrappers hold this per-step data for you when you run the controller outside the simulator.

Contributing and project status

Bug reports and feature requests go to the issue tracker. CONTRIBUTING.md describes the development setup, the pre-commit hooks, and what CI checks. Version history is on the releases page; the package version is read from src/cbfkit/VERSION.

Citing CBFKit

If you use CBFKit in your research, please cite the paper:

@misc{black2024cbfkit,
  title={CBFKIT: A Control Barrier Function Toolbox for Robotics Applications},
  author={Mitchell Black and Georgios Fainekos and Bardh Hoxha and Hideki Okamoto and Danil Prokhorov},
  year={2024},
  eprint={2404.07158},
  archivePrefix={arXiv},
  primaryClass={cs.RO}
}

License

BSD 3-Clause. See LICENSE.

About

JAX toolbox for safe robotics control: Control Barrier Function (CBF) safety filters, MPPI planning, and a drop-in safe-RL layer.

Topics

Resources

Contributing

Stars

111 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages