Spacecraft Docking

The Spacecraft Docking benchmark is a model of a docking spacecraft in 2D.

using ClosedLoopReachability
import OrdinaryDiffEq, Plots, DisplayAs
using ReachabilityBase.CurrentPath: @current_path
using ReachabilityBase.Timing: print_timed
using Plots: plot, plot!

Model

There are 4 state variables $(s_x, s_y, \dot{s}_x, \dot{s}_y)$, where $(s_x, s_y)$ is the position and $(\dot{s}_x, \dot{s}_y)$ is the velocity of the spacecraft [RCM+22].

vars_idx = Dict(:states => 1:4, :controls => 5:6)

const m = 12.0
const n = 0.001027
const three_n² = 3 * n^2
const two_n = 2 * n

@taylorize function SpacecraftDocking!(dx, x, p, t)
    s_x, s_y, s_x′, s_y′, F_x, F_y = x

    dx[1] = s_x′
    dx[2] = s_y′
    dx[3] = three_n² * s_x + two_n * s_y′ + F_x / m
    dx[4] = -two_n * s_x′ + F_y / m
    dx[5] = zero(F_x)
    dx[6] = zero(F_y)
    return dx
end;

We are given a neural-network controller with 4 hidden layers of 4, 256, 256, and 4 neurons, respectively, identity activations in the first and fourth hidden layer (which represent a pre- and postprocessing via linear maps), and tanh activations everywhere else. The controller has 4 inputs (the state variables) and 2 outputs ($F_x, F_y$).

path = @current_path("SpacecraftDocking", "SpacecraftDocking_controller.polar")
controller = read_POLAR(path);

The control period is 1 time unit.

period = 1.0;

Specification

We consider a smaller uncertain initial condition than originally proposed:

X₀ = Hyperrectangle(low=[70, 70, -0.14, -0.14], high=[106, 106, 0.14, 0.14])
U₀ = ZeroSet(2);

The control problem is:

ivp = @ivp(x' = SpacecraftDocking!(x), dim: 6, x(0) ∈ X₀ × U₀)
prob = ControlledPlant(ivp, controller, vars_idx, period);

The safety specification is given as follows:

\[‖\dot{s}_x^2 + \dot{s}_y^2‖ ≤ 0.2 + 2 n ‖s_x^2 + s_y^2‖\]

A sufficient condition for guaranteed verification is to overapproximate the result via interval arithmetic.

function predicate_point(v::Union{AbstractVector,IntervalBox})
    x, y, x′, y′, F_x, F_y = v
    return sqrt(x′^2 + y′^2) <= 0.2 + two_n * sqrt(x^2 + y^2)
end

function predicate_set(R)
    return predicate_point(convert(IntervalBox, box_approximation(R)))
end

predicate(sol) = all(predicate_set(R) for F in sol for R in F)

T = 40.0
T_warmup = 2 * period;  # shorter time horizon for warm-up run

Analysis

To enclose the continuous dynamics, we use a Taylor-model-based algorithm:

algorithm_plant = TMJets(abstol=5e-1, orderT=3, orderQ=1);

To propagate sets through the neural network, we use the DeepZ algorithm:

algorithm_controller = DeepZ();

The verification benchmark is given below:

function benchmark(; T=T, silent::Bool=false)
    # Solve the controlled system:
    silent || println("Flowpipe construction:")
    res = @timed solve(prob; T=T, algorithm_controller=algorithm_controller,
                       algorithm_plant=algorithm_plant)
    sol = res.value
    silent || print_timed(res)

    # Check the property:
    silent || println("Property checking:")
    res = @timed predicate(sol)
    silent || print_timed(res)
    if res.value
        silent || println("  The property is satisfied.")
        result = "verified"
    else
        silent || println("  The property may be violated.")
        result = "not verified"
    end

    return sol, result
end;

Run the verification benchmark and compute some simulations:

benchmark(T=T_warmup, silent=true)  # warm-up
res = @timed benchmark(T=T)  # benchmark
sol, result = res.value
@assert (result == "verified") "verification failed"
println("Total analysis time:")
print_timed(res)

println("Simulation:")
res = @timed simulate(prob; T=T, trajectories=1, include_vertices=true)
sim = res.value
print_timed(res);
Flowpipe construction:
  1.189584 seconds (3.57 M allocations: 420.050 MiB, 79.32% gc time)
Property checking:
  0.023084 seconds (530.49 k allocations: 23.142 MiB)
  The property is satisfied.
Total analysis time:
  1.216905 seconds (4.10 M allocations: 443.784 MiB, 77.54% gc time, 0.00% compilation time)
Simulation:
  0.726494 seconds (1.81 M allocations: 102.597 MiB, 0.00% compilation time)

Results

Script to plot the results:

function plot_helper(vars)
    fig = plot()
    plot!(fig, sol; vars=vars, color=:yellow, lw=0, alpha=1, lab="")
    if vars[1] == 0
        initial_states_projected = cartesian_product(Singleton([0.0]), project(X₀, [vars[2]]))
        plot!(fig, initial_states_projected; c=:cornflowerblue, alpha=1, lab="X₀",
              m=:none, lw=3)
    else
        plot!(fig, project(X₀, vars); c=:cornflowerblue, alpha=1, lab="X₀")
    end
    plot_simulation!(fig, sim; vars=vars, color=:black, lab="")
    return fig
end;

Plot the results:

vars = (0, 1)
fig = plot_helper(vars)
plot!(fig; xlab="t", ylab="x₁")
# Plots.savefig(fig, "SpacecraftDocking.png")  # command to save the plot to a file
fig = DisplayAs.Text(DisplayAs.PNG(fig))
Example block output