A Reachability Algorithm Using Zonotopes

Introduction

In this section we present an algorithm implemented using LazySets that computes the reach sets of an affine ordinary differential equation (ODE). This algorithm is from A. Girard's "Reachability of uncertain linear systems using zonotopes, HSCC. Vol. 5. 2005. We have chosen this algorithm for the purpose of illustration of a complete application of LazySets.

Let us introduce some notation. Consider the continuous initial set-valued problem (IVP)

\[ x'(t) = A x(t) + u(t)\]

in the time interval $t ∈ [0, T]$, where:

  • $A$ is a real matrix of order $n$,
  • $u(t)$ is a non-deterministic input such that $\Vert u(t) \Vert_∞ ≤ μ$ for all $t$,
  • $x(0) ∈ \mathcal{X}_0$, where $\mathcal{X}_0$ is a convex set.

Given a step size $δ$, Algorithm1 returns a sequence of sets that overapproximates the states reachable by any trajectory of this IVP.

We present the algorithm parametric in the option to compute the sets in a lazy or in a concrete way. If the parameter lazy is true, the implementation constructs a LinearMap wrapper (represented as a multiplication * of a matrix and a set) and a MinkowskiSum wrapper (represented as a sum of two sets). If the parameter lazy is false, the implementation calls the concrete counterparts linear_map and minkowski_sum.

For further applications of LazySets in reachability analysis, we refer to the library JuliaReach/ReachabilityAnalysis.jl.

Algorithm

using Plots, LazySets, LinearAlgebra, SparseArrays

function Algorithm1(A, X0, δ, μ, T; lazy::Bool=false)
    # bloating factors
    Anorm = norm(A, Inf)
    α = (exp(δ * Anorm) - 1 - δ * Anorm) / norm(X0, Inf)
    β = (exp(δ * Anorm) - 1) * μ / Anorm

    # discretized system
    n = size(A, 1)
    ϕ = exp(δ * A)
    N = floor(Int, T / δ)

    # preallocate arrays
    Q = Vector{LazySet{Float64}}(undef, N)
    R = Vector{LazySet{Float64}}(undef, N)

    # initial reach set in the time interval [0, δ]
    ϕp = (I+ϕ) / 2
    ϕm = (I-ϕ) / 2
    c = X0.center
    Q1_generators = hcat(ϕp * X0.generators, ϕm * c, ϕm * X0.generators)
    Q[1] = lazy ?
        Zonotope(ϕp * c, Q1_generators) ⊕ BallInf(zeros(n), α + β) :
        minkowski_sum(Zonotope(ϕp * c, Q1_generators), BallInf(zeros(n), α + β))
    R[1] = Q[1]

    # set recurrence for [δ, 2δ], ..., [(N-1)δ, Nδ]
    ballβ = BallInf(zeros(n), β)
    for i in 2:N
        Q[i] = lazy ?
            ϕ * Q[i-1] ⊕ ballβ :
            minkowski_sum(linear_map(ϕ, Q[i-1]), ballβ)
        R[i] = Q[i]
    end
    return R
end

Projection

function project(R, vars, n)
    # projection matrix
    M = sparse(1:2, vars, [1., 1.], 2, n)
    return [M * Ri for Ri in R]
end

Example 1

A = [-1 -4; 4 -1]
X0 = Zonotope([1.0, 0.0], Matrix(0.1*I, 2, 2))
μ = 0.05
δ = 0.02
T = 2.

R = Algorithm1(A, X0, δ, μ, 2 * δ); # warm-up

R = Algorithm1(A, X0, δ, μ, T)

plot(R, 1e-2, 0; same_recipe=true, fillalpha=0.1)

Example 2

A = Matrix{Float64}([-1 -4 0 0 0;
                      4 -1 0 0 0;
                      0 0 -3 1 0;
                      0 0 -1 -3 0;
                      0 0 0 0 -2])
X0 = Zonotope([1.0, 0.0, 0.0, 0.0, 0.0], Matrix(0.1*I, 5, 5))
μ = 0.01
δ = 0.005
T = 1.

R = Algorithm1(A, X0, δ, μ, 2 * δ); # warm-up

R = Algorithm1(A, X0, δ, μ, T)
Rproj = project(R, [1, 3], 5)

plot(Rproj, 1e-2, 0; same_recipe=true, fillalpha=0.1, xlabel="x1", ylabel="x3")