Quickzonoreach is a python3 library for quick computations of discrete-time reach sets using zonotopes, aiming to be suitable for online computation. The system model is x' = A_i x + B_i u, where A_i and B_i can change at each step (suitable for piecewise trajectory linearized models). The initial states are given as a box and each step can have its own box input constraints and time step.
Important functions:
get_zonotope_reachset()(inquickzonoreach/zono.py): computes and returns the reach set, given lists for the parameters at each step.iterate_zonotope_reachset()(inquickzonoreach/zono.py): runs a passed-in custom function on each element of the reach set, given parameters similar toget_zonotope_reachsetget_center_sim()(inquickzonoreach/sim.py): computes a center simulation using similar parameters asget_zonotope_reachset. Useful for sanity checks.Zonotope.verts()(inquickzonoreach/zono.py): get an approximate 2-d projection of the zonotope.Zonotope.box_bounds()(inquickzonoreach/zono.py): quick box bounds of the zonotope.Zonotope.plot()(inquickzonoreach/zono.py): plots a 2-d projection of the zonotope with matplotlib.Zonotope.maximize() (inquickzonoreach/zono.py): optimize over the zonotope in a given direction, for quick safety checking without LP.
Four examples are also provided:
examples/example_plot.py: Producequickzonoreach.png(the plot at the top of theREADME) which matches Hylaa's output,hylaa.png, that can be produced byhylaa_check.py(you'll need Hylaa installed from https://github.com/stanleybak/hylaa).examples/example_compare.py: Compares accuracy usingquick=True(Euler approximation) method vs computing full matrix exponential. Thequick=Truemode is fairly accurate when the time step is small. This plot is provided below.examples/example_time_varying.py: Producestime_varying.png, which does a few steps for a medium-dimensional time-varying system, and checks that the center sim is inside the computed zonotopes.examples/example_profile.py: Measures runtime for various step sizes and dimensions to produce the tables shown below.
The quick=True parameter to get_zonotope_reachset can be used to approximate the matrix exponential by the first two terms of the series definition (e^{At} = I + At), rather than computing it with a library call to expm. This is slightly faster, and may be accurate enough especially when the time step is small. A plot showing the difference at the final time step (at time pi) for the noisy harmonic oscillator system is below:
A rough gauge of performance can be computed with example_profile.py. Performance depends in the specific system, so it may vary from when you use the library. The script will produce the tables show below.
For these measurements, we used a replicated a noisy harmonic oscillator with two inputs at each time step. The time bound is pi, and we vary the number of dimensions and time steps. The measurements are in seconds, performed on my laptop (i5-5300U CPU at 2.3GHz).
| Exact Save All | 8 steps | 16 steps | 32 steps | 64 steps | 128 steps | 256 steps |
|---|---|---|---|---|---|---|
| 2 dims | 0.08 | 0.08 | 0.2 | 0.3 | 0.6 | 1.2 |
| 4 dims | 0.05 | 0.09 | 0.2 | 0.3 | 0.7 | 1.3 |
| 8 dims | 0.05 | 0.1 | 0.2 | 0.4 | 0.8 | 1.5 |
| 16 dims | 0.07 | 0.1 | 0.2 | 0.5 | 1.0 | 2.0 |
| 32 dims | 0.1 | 0.2 | 0.3 | 0.7 | 1.4 | - |
| 64 dims | 0.2 | 0.3 | 0.6 | 1.3 | - | - |
| 128 dims | 1.0 | 2.0 | - | - | - | - |
| 256 dims | 4.1 | - | - | - | - | - |
| Quick Save All | 8 steps | 16 steps | 32 steps | 64 steps | 128 steps | 256 steps | 512 steps | 1024 steps | 2048 steps |
|---|---|---|---|---|---|---|---|---|---|
| 2 dims | 0.0007 | 0.001 | 0.002 | 0.005 | 0.01 | 0.07 | 0.1 | 0.5 | 1.7 |
| 4 dims | 0.0005 | 0.0006 | 0.0009 | 0.002 | 0.01 | 0.02 | 0.1 | 0.4 | 1.8 |
| 8 dims | 0.0004 | 0.0005 | 0.0009 | 0.002 | 0.006 | 0.02 | 0.09 | 0.5 | 1.9 |
| 16 dims | 0.0005 | 0.0006 | 0.001 | 0.003 | 0.007 | 0.02 | 0.1 | 1.2 | - |
| 32 dims | 0.002 | 0.003 | 0.005 | 0.008 | 0.01 | 0.06 | 0.3 | 1.5 | - |
| 64 dims | 0.005 | 0.004 | 0.003 | 0.006 | 0.01 | 0.09 | 0.4 | 1.8 | - |
| 128 dims | 0.01 | 0.02 | 0.03 | 0.05 | 0.08 | 0.2 | 0.7 | 2.7 | - |
| 256 dims | 0.02 | 0.06 | 0.09 | 0.1 | 0.2 | 0.5 | 1.4 | - | - |
| 512 dims | 0.1 | 0.2 | 0.3 | 0.5 | 0.9 | 1.8 | - | - | - |
| 1024 dims | 0.5 | 0.7 | 1.1 | - | - | - | - | - | - |
| 2048 dims | 2.3 | - | - | - | - | - | - | - | - |
Which zonotopes are saved can be controlled with the save_list parameter to get_zonotope_reachset. If you're doing many steps or working in high dimensions, saving copies at every step can sometimes slow things down.
| Quick Save Last | 32 steps | 64 steps | 128 steps | 256 steps | 512 steps | 1024 steps | 2048 steps | 4096 steps | 8192 steps | 16384 steps | 32768 steps |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 2 dims | 0.002 | 0.003 | 0.006 | 0.02 | 0.02 | 0.04 | 0.04 | 0.1 | 0.3 | 1.0 | 3.3 |
| 4 dims | 0.0006 | 0.0009 | 0.002 | 0.004 | 0.008 | 0.02 | 0.05 | 0.2 | 0.5 | 2.8 | - |
| 8 dims | 0.002 | 0.003 | 0.006 | 0.02 | 0.03 | 0.03 | 0.08 | 0.5 | 1.8 | - | - |
| 16 dims | 0.002 | 0.004 | 0.008 | 0.02 | 0.05 | 0.1 | 0.3 | 0.9 | 3.2 | - | - |
| 32 dims | 0.004 | 0.006 | 0.02 | 0.03 | 0.08 | 0.2 | 0.6 | 2.0 | - | - | - |
| 64 dims | 0.008 | 0.01 | 0.02 | 0.05 | 0.1 | 0.3 | 1.1 | - | - | - | - |
| 128 dims | 0.06 | 0.04 | 0.06 | 0.1 | 0.3 | 0.8 | 3.4 | - | - | - | - |
| 256 dims | 0.1 | 0.1 | 0.2 | 0.4 | 1.0 | 3.0 | - | - | - | - | - |
| 512 dims | 0.3 | 0.4 | 0.7 | 1.5 | - | - | - | - | - | - | - |
| 1024 dims | 1.2 | - | - | - | - | - | - | - | - | - | - |
There exists a Docker container which in theory should make sure the examples can execute after each commit, as indicated by the image at the top of the README. For details, see the commands and comments in the Dockerfile. To run the examples in Docker do:
docker build -t quickzonoreach .
docker run quickzonoreach
If using quick and only saving some of the zonotopes is still too slow, make sure you have a good version of BLAS installed like OpenBlas (matrix dot product in numpy is an important operation for speed). It may be possible to use 32-bit floats instead of 64 bit.
Also, the library is currently single-threaded, so using multiple cores or GPUs for computation could increase speed more, although this would need code modification.


