This C++ library provides classes and tailored optimization routines for zonotopes, constrained zonotopes, and hybrid zonotopes.


Zonotopes, Constrained Zonotopes, and Hybrid Zonotopes
Zonotopes and their generalizations are set representations widely used for reachability analysis. A major advantage of these set representations when compared to alternatives such as halfspace representation (H-rep) or vertex representation (V-rep) polytopes is that they have efficient identities for many important set operations. This makes them well-suited to contexts where efficient online set computations are required, such as online safety verification and set-valued state estimation.
This library focuses specifically on zonotopes, constrained zonotopes, and hybrid zonotopes. A set $\mathcal{Z}$ is a zonotope if there exist a generator matrix $G$ and center vector $c$ such that
$$ \mathcal{Z} = \left\lbrace G \xi + c \; : \; \xi \in [-1, 1]^{nG} \right\rbrace \;. $$
Zonotopes represent centrally symmetric, convex polytopes.
A set $\mathcal{Z}_C$ is a constrained zonotope if there additionally exist a constraint matrix $A$ and constraint vector $b$ such that
$$ \mathcal{Z}_C = \left\lbrace G \xi + c \; : \; \xi \in [-1, 1]^{nG}, A \xi = b \right\rbrace \;. $$
Constrained zonotopes represent convex polytopes.
Hybrid zonotopes extend constrained zonotopes by allowing for a subset of the factors $\xi$ to be binary-valued, i.e., $\xi = [\xi_c \quad \xi_b]^T$ where $\xi_c \in [-1, 1]^{nGc}$ and $\xi_b \in \lbrace -1, 1 \rbrace^{nGb}$. A set $\mathcal{Z}_H$ is then a hybrid zonotope if there exist generator matrices $G_c$ and $G_b$, center $c$, constraint matrices $A_c$ and $A_b$, and constraint vector $b$ such that
$$ \mathcal{Z}_H = \left\lbrace \begin{bmatrix} G_c & G_b \end{bmatrix} \begin{bmatrix} \xi_c \\ \xi_b \end{bmatrix} + c \; : \; \begin{matrix} \xi_c \in [-1, 1]^{nGc},\; \xi_b \in \lbrace -1, 1 \rbrace^{nGb}, \\ \begin{bmatrix} A_c & A_b \end{bmatrix} \begin{bmatrix} \xi_c \\ \xi_b \end{bmatrix} = b \end{matrix} \right\rbrace \;. $$
Hybrid zonotopes represent unions of convex polytopes.
ZonoOpt Features
The ZonoOpt library provides classes and set operations for zonotopes, constrained zonotopes, and hybrid zonotopes. For cases where numerical optimization is required, e.g., checking if a set is empty or solving an MPC problem where the constraints are represented as a zonotopic set, custom optimization routines are utilized. Some key features of the ZonoOpt library are as follows:
- All classes and methods are implemented using sparse linear algebra via the Eigen library.
- ZonoOpt has minimal external dependencies, making it easy to integrate into robotics projects using C++ or Python.
- Dependencies are Eigen, Boost, and nlohmann/json
- Polymorphism is used to provide a common interface for zonotopes, constrained zonotopes, and hybrid zonotopes while allowing for specialized implementations.
- E.g.,
support is more efficient for zonotopes than for constrained zonotopes.
- Factors are flexibly defined as either $[\xi_c \quad \xi_b]^T \in [0,1]^{nGc} \times \lbrace 0,1 \rbrace^{nGb}$ or the more standard form $[\xi_c \quad \xi_b]^T \in [-1,1]^{nGc} \times \lbrace -1,1 \rbrace^{nGb}$ to facilitate certain set operations.
- Interval arithmetic is provided via the
Interval, Box, and IntervalMatrix classes.
- Boost's interval library is used within
Interval for most of the fundamental operations.
- Operator overloading is implemented for commonly-used set operations.
Building and Installing
Python bindings can be installed from PyPI with pip install zonoopt. To build the bindings from source, use pip install .. Note that a C++ compiler is required to build from source.
This library can be used in CMake projects either via add_subdirectory or by installing the library. When building the library for installation, you must set the option ZONOOPT_INSTALL to ON, i.e., cmake -DZONOOPT_INSTALL=ON -S . -B build.
Example CMake usage is as follows:
cmake_minimum_required(VERSION 3.15...3.27)
project(your_project)
add_executable(your_project
your_project.cpp
)
# Using add_subdirectory
add_subdirectory(ZonoOpt)
# If installed, find the package instead
# find_package(ZonoOpt REQUIRED)
target_link_libraries(your_project PRIVATE ZonoOpt)
Examples
Consider the case that we wish to compute the robust forward reachable set of a discrete time double integrator system and verify that it does not intersect an unsafe set. We may do this in Python using operator overloading as follows:
import zonoopt as zono
import numpy as np
import matplotlib.pyplot as plt
dt = 0.1
A = np.array([[1., dt],
[0., 1.]])
B = np.array([[0.5*dt**2],
[dt]])
X0 = zono.interval_2_zono(zono.Box([-1., -0.1], [1., 0.1]))
U = zono.interval_2_zono(zono.Box([-0.2], [0.2]))
W = np.diag([0.01, 0.05]) @ zono.make_regular_zono_2D(radius=1., n_sides=8)
X = X0
for k in range(10):
X = A @ X + B @ U + W
O = zono.vrep_2_conzono(np.array([[1.3, 0.],
[1.6, 0.8],
[2.0, -0.4],
[2.3, 0.6]]))
print(f'10-step reachable set intersects unsafe set: {not (X & O).is_empty()}')
fig, ax = plt.subplots(figsize=(4, 3), layout='tight')
h = []
h.append(zono.plot(X, ax=ax, color='b', alpha=0.2)[0])
h.append(zono.plot(O, ax=ax, color='r', alpha=0.2)[0])
ax.legend(h, ['X', 'O'])
plt.show()
Equivalently, these calculations can be performed in C++ as follows:
#include "Eigen/Dense"
#include "Eigen/Sparse"
#include <iostream>
int main()
{
double dt = 0.1;
Eigen::Matrix<double, 2, 2> A;
A << 1, dt,
0, 1;
Eigen::Matrix<double, 2, 1> B;
B << 0.5*dt*dt,
dt;
Eigen::Vector2d x0_min, x0_max;
x0_min << -1.0, -0.1;
x0_max << 1.0, 0.1;
Eigen::Vector<double, 1> u_min, u_max;
u_min << -0.2;
u_max << 0.2;
Eigen::SparseMatrix<double> W_map(2, 2);
W_map.insert(0, 0) = 0.01;
W_map.insert(1, 1) = 0.05;
for (int k=0; k<10; ++k)
{
}
Eigen::Matrix<double, 4, 2> verts;
verts << 1.3, 0.0,
1.6, 0.8,
2.0, -0.4,
2.3, 0.6;
std::cout << "10-step reachable set intersects unsafe set: "
return 0;
}
ZonoOpt library main header file.
Box (i.e., interval vector) class.
Definition Box.hpp:25
std::unique_ptr< HybZono > minkowski_sum(const HybZono &Z1, HybZono &Z2)
Computes Minkowski sum of two sets Z1 and Z2.
Definition SetOperations.cpp:155
std::unique_ptr< HybZono > affine_map(const HybZono &Z, const Eigen::SparseMatrix< zono_float > &R, const Eigen::Vector< zono_float, -1 > &s=Eigen::Vector< zono_float, -1 >())
Returns affine map R*Z + s of set Z.
Definition SetOperations.cpp:12
std::unique_ptr< HybZono > intersection(const HybZono &Z1, HybZono &Z2, const Eigen::SparseMatrix< zono_float > &R=Eigen::SparseMatrix< zono_float >())
Computes the generalized intersection of sets Z1 and Z2 over the matrix R.
Definition SetOperations.cpp:215
std::unique_ptr< Zono > interval_2_zono(const Box &box)
Builds a zonotope from a Box object.
Definition Zono.cpp:346
std::unique_ptr< Zono > make_regular_zono_2D(zono_float radius, int n_sides, bool outer_approx=false, const Eigen::Vector< zono_float, 2 > &c=Eigen::Vector< zono_float, 2 >::Zero())
Builds a 2D regular zonotope with a given radius and number of sides.
Definition Zono.cpp:310
std::unique_ptr< ConZono > vrep_2_conzono(const Eigen::Matrix< zono_float, -1, -1 > &Vpoly)
Builds a constrained zonotope from a vertex representation polytope.
Definition ConZono.cpp:675
std::unique_ptr< HybZono > ZonoPtr
Type alias for a unique pointer to a (polymorphic) HybZono object. <>
Definition ZonoOpt.hpp:93
Further Python examples are located in /examples.
External Solvers
ZonoOpt supports Gurobi and SCIP as external solvers via dynamic loading. Please note that a supported version of Gurobi or SCIP must be installed on your system for dynamic loading to succeed. To invoke an external solver, pass a corresponding settings structure to the function requiring optimization. For example, a point projection using Gurobi in Python would look like:
settings = zono.GurobiSettings()
p_proj = Z.project_point(p, settings=settings)
Equivalently, in C++:
auto p_proj = Z.project_point(p, settings);
Settings for the dynamically-loaded Gurobi solver backend.
Definition GurobiSettings.hpp:43
To change the default solver, globally modify the default solver settings, e.g.,
zono.set_default_solver_settings(zono.SCIPSettings())
in Python, or
void set_default_solver_settings(const SolverSettings &settings)
Replaces the program-wide default solver settings with a polymorphic copy of settings.
Definition SolverDataStructures.hpp:403
Settings for the dynamically-loaded SCIP solver backend.
Definition SCIPSettings.hpp:45
in C++.
Documentation
Auto-generated API documentation is available below.
C++ API
Python API
References
More information about ZonoOpt can be found in the following publications. Please cite one or both of these as appropriate if you use ZonoOpt in your published work:
J. A. Robbins, J. A. Siefert and H. C. Pangborn, "Sparsity-Promoting Reachability Analysis and Optimization of Constrained Zonotopes," in IEEE Transactions on Control Systems Technology, doi: 10.1109/TCST.2026.3700277.
Robbins, J.A., Thompson, A.F., Glunt, J.J., and Pangborn, H.C., "Hybrid System Planning using a Mixed-Integer ADMM Heuristic and Hybrid Zonotopes", 2026. https://arxiv.org/abs/2602.17574
Additional Notes
- For internal reasons, ZonoOpt's versioning begins at 2.0.0. Please note that the library is currently in Beta. In this phase, there may be API changes in minor version updates (e.g., 2.1.0 to 2.2.0). There will be no API changes between patch numbers (e.g., 2.2.0 to 2.2.1).
- For Python users, ZonoOpt's use of multithreading for some hybrid zonotope operations can cause issues with cProfile. Profiling with py-spy is recommended.
See Also
Projects Using ZonoOpt
- pyspect is a Python toolbox for compiling temporal logic specifications into reachability programs.
Related Projects
- zonoLAB is a MATLAB toolbox that provides classes and set operations for zonotopes, constrained zonotopes, and hybrid zonotopes.
- pycvxset is a Python toolbox for convex set manipulation.