Inverted Pendulum
The Inverted Pendulum benchmark is a classical model of motion.
using ClosedLoopReachability
import OrdinaryDiffEq, Plots, DisplayAs
using ReachabilityBase.CurrentPath: @current_path
using ReachabilityBase.Timing: print_timed
using ClosedLoopReachability: SingleEntryVector
using Plots: plot, plot!, xlims!, ylims!
The following option determines whether the falsification settings should be used. The falsification settings are sufficient to show that the safety property is violated. Concretely, we start from an initial point and use a smaller time horizon.
const falsification = true;
Model
A ball of mass $m$ is attached to a massless beam of length $L$. The beam is actuated with a torque $T$. We assume viscous friction with coefficient $c$.
The governing equation of motion can be obtained as follows:
\[\ddot{θ} = \dfrac{g}{L} \sin(θ) + \dfrac{1}{m L^2} (T - c \dot{θ})\]
where $θ$ is the angle that the link makes with the upward vertical axis, $\dot{θ}$ is the angular velocity, and $g$ is the gravitational acceleration. The state vector is $(θ, \dot{θ})$. The model constants are chosen as $m = L = 0.5$, $c = 0$, and $g = 1$.
vars_idx = Dict(:states => 1:2, :controls => 3)
const m = 0.5
const L = 0.5
const c = 0.0
const g = 1.0
const gL = g / L
const mL = 1 / (m * L^2)
@taylorize function InvertedPendulum!(dx, x, p, t)
θ, θ′, T = x
dx[1] = θ′
dx[2] = gL * sin(θ) + mL * (T - c * θ′)
dx[3] = zero(T)
return dx
end;
We are given a neural-network controller with 2 hidden layers of 25 neurons each and ReLU activations. The controller has 2 inputs (the state variables) and 1 output ($T$).
path = @current_path("InvertedPendulum", "InvertedPendulum_controller.polar")
controller = read_POLAR(path);
The control period is 0.05 time units.
period = 0.05;
Specification
The uncertain initial condition is $θ \in [1, 1.2], \dot{θ} \in [0, 0.2]$.
X₀ = BallInf([1.1, 0.1], 0.1)
if falsification
# Choose a single point in the initial states (here: the top-most one):
X₀ = Singleton(high(X₀))
end
U₀ = ZeroSet(1);
The control problem is:
ivp = @ivp(x' = InvertedPendulum!(x), dim: 3, x(0) ∈ X₀ × U₀)
prob = ControlledPlant(ivp, controller, vars_idx, period);
The safety specification is that $θ ∈ [0, 1]$ for $t ∈ [0.5, 1]$ (i.e., the control periods $10 ≤ k ≤ 20$). A sufficient condition for guaranteed violation is to overapproximate the result with hyperrectangles.
unsafe_states = HalfSpace(SingleEntryVector(1, 3, -1.0), -1.0)
function predicate_set(R)
t = tspan(R)
return t.lo >= 0.5 && t.hi <= 1.0 &&
overapproximate(R, Hyperrectangle) ⊆ unsafe_states
end
function predicate(sol; silent::Bool=false)
for F in sol
t = tspan(F)
if t.hi < 0.5 || t.lo > 1.0
continue
end
for R in F
if predicate_set(R)
silent || println(" Violation for time range $(tspan(R)).")
return true
end
end
end
return false
end
if falsification
k = 11 # falsification can run for a shorter time horizon
else
k = 20
end
T = k * period
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=1e-7, orderT=4, orderQ=1);
To propagate sets through the neural network, we use the DeepZ
algorithm:
algorithm_controller = DeepZ();
The falsification 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=silent)
silent || print_timed(res)
if res.value
silent || println(" The property is violated.")
result = "falsified"
else
silent || println(" The property may be satisfied.")
result = "not falsified"
end
return sol, result
end;
Run the falsification 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 == "falsified") "falsification failed"
println("Total analysis time:")
print_timed(res)
println("Simulation:")
res = @timed simulate(prob; T=T, trajectories=falsification ? 1 : 10,
include_vertices=!falsification)
sim = res.value
print_timed(res);
Flowpipe construction:
0.280771 seconds (2.93 M allocations: 206.442 MiB, 9.50% gc time)
Property checking:
Violation for time range [0.5, 0.509134].
0.277935 seconds (288.40 k allocations: 21.519 MiB)
The property is violated.
Total analysis time:
0.562111 seconds (3.21 M allocations: 228.825 MiB, 4.74% gc time)
Simulation:
0.378707 seconds (501.09 k allocations: 36.696 MiB)
Results
Script to plot the results:
function plot_helper()
vars = (0, 1)
fig = plot()
unsafe_states_projected = cartesian_product(Interval(0.5, 1.0),
project(unsafe_states, [vars[2]]))
plot!(fig, unsafe_states_projected; color=:red, alpha=:0.2, lab="unsafe")
plot!(fig, sol; vars=vars, color=:yellow, lw=0, alpha=1, lab="")
initial_states_projected = cartesian_product(Singleton([0.0]), project(X₀, [vars[2]]))
plot!(fig, initial_states_projected; c=:cornflowerblue, alpha=1, lab="X₀")
if falsification
xlims!(0, T)
ylims!(0.95, 1.22)
else
xlims!(0, T)
ylims!(0.55, 1.3)
end
lab_sim = falsification ? "simulation" : ""
plot_simulation!(fig, sim; vars=vars, color=:black, lab=lab_sim)
plot!(fig; xlab="t", ylab="θ")
return fig
end;
Plot the results:
fig = plot_helper()
# Plots.savefig(fig, "InvertedPendulum.png") # command to save the plot to a file
fig = DisplayAs.Text(DisplayAs.PNG(fig))