Ahead-of-time compiler that translates trained neural control barrier functions into bare-metal C++ headers evaluating exact Lie derivatives via dual algebra, with zero dynamic memory allocation on embedded targets.
Project description
dual_cbf_compiler
Ahead-of-time compiler that translates trained neural Control Barrier Functions into bare-metal C++ headers evaluating exact Lie derivatives via dual algebra. The generated code performs zero dynamic memory allocation and is suitable for deployment on resource-constrained microcontrollers running kilohertz-rate safety filters.
Install
pip install -e ".[test]"
Recommended workflow: ONNX-first
The cleanest path is to export your trained PyTorch model to ONNX. ONNX captures the network topology along with the weights, so no manual architecture declaration is required:
import torch
torch.onnx.export(
model,
sample_input,
"model.onnx",
input_names=["x"],
output_names=["h"],
opset_version=14,
)
then either
dual-cbf-compile model.onnx -o dual_cbf.h --relative-degree 1
or programmatically
from dual_cbf_compiler import parse_onnx, emit_cpp_header
network = parse_onnx("model.onnx")
header = emit_cpp_header(network, relative_degree=1)
open("dual_cbf.h", "w").write(header)
Direct PyTorch ingestion (programmatic, recommended)
If you have an in-memory torch.nn.Sequential whose modules describe the
topology, parse it directly — no string declaration required:
import torch.nn as nn
from dual_cbf_compiler import parse_pytorch, emit_cpp_header
model = nn.Sequential(
nn.Linear(4, 32), nn.ReLU(),
nn.Linear(32, 32), nn.ReLU(),
nn.Linear(32, 1),
)
# (load trained weights into model here)
network = parse_pytorch(model) # relative_degree=1 by default
header = emit_cpp_header(network, relative_degree=1)
open("dual_cbf.h", "w").write(header)
PyTorch state_dict from the command line (fallback only)
PyTorch state_dicts (.pt/.pth) only save tensor weights, not the
computational graph, so the CLI must be told the topology via --torch-arch.
This is error-prone — a typo silently maps weights to the wrong dimensions.
Prefer the ONNX export path above.
# Only if ONNX export is unavailable
dual-cbf-compile model.pt -o dual_cbf.h \
--torch-arch "4,relu,32,relu,32,linear,1"
Use the generated C++ header
#include "dual_cbf.h"
float x[4] = {/* state */};
float f[4] = {/* drift */};
// IMPORTANT: G(x) is ROW-MAJOR with shape (n x m).
// Element G(i, j) lives at G[i * m + j].
// If you keep G as a 2D C array float G2D[4][2], populate the flat
// layout as:
// for (int i = 0; i < 4; ++i)
// for (int j = 0; j < 2; ++j)
// G[i * 2 + j] = G2D[i][j];
float G[4 * 2] = {
/* row 0 */ 0.0f, 0.0f,
/* row 1 */ 0.0f, 0.0f,
/* row 2 */ 0.0f, 0.4f,
/* row 3 */ 1.0f, 0.0f,
};
float h, Lf, Lg[2];
dual_cbf::evaluate_cbf(x, f, G, /*m=*/2, &h, &Lf, Lg);
// CBF QP constraint: L_f h + L_G h * u >= -alpha(h)
The generated header repeats this row-major layout reminder in its file-level comment so the convention is impossible to miss at the call site.
Relative-degree-2 (hyper-dual)
For mechanical systems with acceleration inputs:
network = parse_onnx("softplus_model.onnx", relative_degree=2)
header = emit_cpp_header(network, relative_degree=2)
float L2f, LgLf[m];
dual_cbf::evaluate_cbf_2nd_order(x, f, G, Df_f, Df_G, m, &L2f, LgLf);
Df_f is the user-supplied Jacobian-vector product f'(x) f(x) (length n),
and Df_G is the row-major Jacobian-vector product f'(x) G(x) (shape n×m).
ReLU is rejected for relative_degree=2. ReLU's second derivative vanishes
almost everywhere, so the Hessian contribution to L_f^2 h_theta and
L_{col_j(G)} L_f h_theta would be silently lost. Both parse_pytorch and
parse_onnx raise a ValueError at parse time if a ReLU layer is detected
and relative_degree=2 was requested. Retrain with Softplus, Tanh,
or Sigmoid instead.
Verify
# Numpy-only verification (no torch needed)
python3 numpy_verify.py
python3 numpy_verify_2nd.py
# Full suite (requires torch)
pytest -v dual_cbf_compiler/verify.py
The pytest suite builds random PyTorch networks, evaluates Lie derivatives via
torch.autograd, compiles the generated C++ via g++, runs the binary, and
asserts agreement to single-precision machine epsilon.
Design
- Zero dynamic allocation. All weights and biases are embedded as
static const floatarrays. A single shared static scratch buffer of2 * max(n_i)floats (relative-degree 1) or4 * max(n_i)floats (relative-degree 2) is destructively overwritten as the forward pass advances layer by layer. - Forward-only. The chain rule is collapsed into a memoryless forward recurrence by encoding the system state as the real part and a vector field as the dual part of an input dual vector. No reverse-mode AD, no computational graph.
- Algebraic mode selection.
relative_degree=1emits standard dual algebra (eps^2 = 0).relative_degree=2emits hyper-dual arithmetic with cross term eps_12, allowing exact extraction of the second-order Lie derivatives required for acceleration-controlled mechanical systems. - Hard guards on common footguns. ReLU + relative_degree=2 is a
ValueError, not a passive warning. The CLI declines to silently map weights to the wrong topology — when--torch-archand the state_dict disagree, it refuses with a hint to use ONNX.
Layout
dual_cbf_compiler/
ir.py — Internal representation (LinearLayer, ActivationLayer)
parser.py — parse_pytorch, parse_onnx (both accept relative_degree)
emitter.py — emit_cpp_header
verify.py — Pytest suite (PyTorch autograd vs g++-compiled binary)
cli.py — dual-cbf-compile entry point
setup.py
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dual_cbf_compiler-0.1.0.tar.gz.
File metadata
- Download URL: dual_cbf_compiler-0.1.0.tar.gz
- Upload date:
- Size: 5.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b73d9b38d25883a8bbfc701d4c17d2dd4a7314399f0cbb90f8c87d5d6444e77b
|
|
| MD5 |
f52e098ae10560ac4fe04ab6c15a996d
|
|
| BLAKE2b-256 |
81c3e93ac0e50736f53b2f9aabbfb835e8e9bb5dd6e342d895f2bba6cdc191ca
|
File details
Details for the file dual_cbf_compiler-0.1.0-py3-none-any.whl.
File metadata
- Download URL: dual_cbf_compiler-0.1.0-py3-none-any.whl
- Upload date:
- Size: 4.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2ebd14f438e7a30c68dbd3fbf6543de419dae8caf1cd514d4b243a4b3ac17156
|
|
| MD5 |
73e6674e2a13c3c3a11c1e094ade6d6d
|
|
| BLAKE2b-256 |
f6cba87acc4707a679f502a124284cfdaf53ebee40046a2d70d99afd674a2a87
|