Week #5: Visualizing a limit cycle

Summary of contributions

Visualizing properties using ReachabilityAnalysis.jl

Limit cycle

A limit cycle is an isolated closed trajectory. Isolated means that neighboring trajectories are not closed, they spiral away or towards the limit cycle. A limit cycle is deemed stable if all neighboring trajectories approach it, otherwise it is unstable. The Van der Pol oscillator has a stable limit cycle, we can visualize it watching how the flowpipe evolves with time.

First let's define the dynamics of the system:

using ReachabilityAnalysis, Plots, Optim, Polyhedra
@taylorize function vanderpol!(dx, x, params, t)
    local μ = 1.0
    dx[1] = x[2]
    dx[2] = (μ * x[2]) * (1 - x[1]^2) - x[1]
    return dx
end
Shift+Enter to run
Julia
Info: Precompiling ReachabilityAnalysis [1e97bd63-91d1-579d-8e8d-501d2b57c93f] @ Base loading.jl:1260

Then I have to choose a big enough set of initial conditions that will allow us to see the flowpipe shrink with time:

X0 = Hyperrectangle(low=[1.25, 2.35], high=[1.55, 2.45])
prob = @ivp(x' = vanderpol!(x), dim=2, x(0)  X0)
sol = solve(prob, T=7.0, alg=TMJets());
Shift+Enter to run
Julia

The reachability was done with the non-linear algorithm TMJets

To further analyse the flowpipe, I will overapproximate it with zonotopes.

solz = overapproximate(sol, Zonotope);
Shift+Enter to run
Julia
plot(solz, vars=(1, 2), lw=0.2, xlims=(-2.5, 2.5), xlab="x", ylab="y")
plot!(x -> 2.75, color=:red, lab="y = 2.75", style=:dash, legend=:bottomright)
Shift+Enter to run
Julia

I will highlight the list few sets and the last set such that they overlap, then I will intersect a line somewhat perpendicular to the trajectory, that will allow me to get a cross-section of the sets.

plot(solz, vars=(1, 2), lw=0.2, xlims=(0.0, 2.5), ylims=(1.6, 2.8), xlab="x", ylab="y")
plot!(X0, color=:orange, lab="X0")
plot!(solz[1:5], vars=(1, 2), color=:green, lw=1.0, alpha=0.5, lab="F[1:5]")
plot!(solz[200], vars=(1, 2), color=:red, lw=1.0, alpha=0.6, lab="F[200]")
plot!(LineSegment([1, 2.], [2., 2.5]), lw=2.0)
Shift+Enter to run
Julia

Here I define a function to get the cross section of the flowpipe, the function needs the flowpipe, a line segment that cuts the flowpipe and the indices of the subsets to cut

using ReachabilityAnalysis: ReachSolution
function cross_section(line::LineSegment, RS::ReachSolution, idx)
    i = reduce(convex_hull, map(x -> intersection(line, x), set.(RS[idx])))
    vl = vertices_list(i)
    return LineSegment(vl[1], vl[2])
end
Shift+Enter to run
Julia
cross_section (generic function with 1 method)

Then I get the cross section of the first five sets and the last set, I will call them i1 and i2 respectively

i1 = cross_section(line, solz, 1:5)
i2 = cross_section(line, solz, [200])
Shift+Enter to run
Julia
glp_simplex: unable to recover undefined or non-optimal solution glp_simplex: unable to recover undefined or non-optimal solution glp_simplex: unable to recover undefined or non-optimal solution glp_simplex: unable to recover undefined or non-optimal solution glp_simplex: unable to recover undefined or non-optimal solution glp_simplex: unable to recover undefined or non-optimal solution glp_simplex: unable to recover undefined or non-optimal solution glp_simplex: unable to recover undefined or non-optimal solution glp_simplex: unable to recover undefined or non-optimal solution glp_simplex: unable to recover undefined or non-optimal solution glp_simplex: unable to recover undefined or non-optimal solution glp_simplex: unable to recover undefined or non-optimal solution
LineSegment{Float64,Array{Float64,1}}([1.3929613350502192, 2.19648066752511], [1.474985285454607, 2.2374926427273034])

Then I calculate the length of each cross section, remember that the system is 2D, so the cross section will be a line segment.

l1 = norm(i1.q - i1.p)
l2 = norm(i2.q - i2.p);
Shift+Enter to run
Julia
@show l1
@show l2;
Shift+Enter to run
Julia
l1 = 0.3404168120091947 l2 = 0.09170556444364102
plot(i1, lw=3.0, alpha=1.0, label="First subsets", legend=:bottomright)
plot!(i2, lw=5.0, alpha=1.0, label="Last subset")
Shift+Enter to run
Julia
i2  i1
Shift+Enter to run
Julia
true

As we can see, the cross section of the las subset is a subset of the first few sets, thus, the cycle will continue, presumably getting smaller each revolution.

Runtimes (1)