Skip to content

Commit 9770cfb

Browse files
authored
Add Julia Set example (#272)
* Add Julia Set example * Add ImplicitPlots * Fixes * Fixes * Add disk_integral * Fix * Add in_set * Add LA * Use sandboxing for examples * Better viz * Show solution_summary * Allow near optimality
1 parent a69db10 commit 9770cfb

File tree

3 files changed

+166
-1
lines changed

3 files changed

+166
-1
lines changed

docs/Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
99
DynamicPolynomials = "7c1d4256-1411-5781-91ec-d7bc3513ac07"
1010
GroupsCore = "d5909c97-4eac-4ecc-a3dc-fdd0858a4120"
1111
HomotopyContinuation = "f213a82b-91d6-5c5d-acf7-10f1c761b327"
12+
ImplicitPlots = "55ecb840-b828-11e9-1645-43f4a9f9ace7"
1213
Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
1314
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
1415
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
@@ -18,6 +19,7 @@ MutableArithmetics = "d8a4904e-b15c-11e9-3269-09a3773c0cb0"
1819
PermutationGroups = "8bc5a954-2dfc-11e9-10e6-cd969bffa420"
1920
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
2021
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
22+
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
2123
SumOfSquares = "4b9e565b-77fc-50a5-a571-1244f986bda1"
2224
SymbolicWedderburn = "858aa9a9-4c7c-4c62-b466-2421203962a2"
2325
TypedPolynomials = "afbbf031-7a57-5f58-a1b9-b774a0fad08d"
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# # Outer approximation of Julia set
2+
3+
#md # [![](https://mybinder.org/badge_logo.svg)](@__BINDER_ROOT_URL__/generated/Systems and Control/julia_set.ipynb)
4+
#md # [![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](@__NBVIEWER_ROOT_URL__/generated/Systems and Control/julia_set.ipynb)
5+
# **Adapted from**: Section 7.1.3 of [KHJ14]
6+
#
7+
# [KHJ14] Milan Korda, Didier Henrion, and Colin N. Jones.
8+
# *Convex computation of the maximum controlled invariant set for polynomial control systems*.
9+
# SIAM Journal on Control and Optimization 52.5 (2014): 2944-2969.
10+
11+
# The Julia map is defined as follows:
12+
13+
using Test #src
14+
function julia_map(point, c)
15+
a, b = point
16+
return [a^2 - b^2 + real(c), 2a * b + imag(c)]
17+
end
18+
19+
20+
# The *escape radius" is the radius `r` such that `r^2 ≥ r + abs(c)`.
21+
# Ouside of the circle of that radius, all points diverge so we know the Julia set belongs to that circle.
22+
23+
escape_radius(c) = (1 + (1 + 4 * abs(c))) / 2
24+
25+
# To check whether a point is in the Julia set, we can iterate and once the point leaves the circle of escape radius,
26+
# we consider that it is not in the Julia set, if it stays in the set, we consider that it is in the Julia set.
27+
# This gives an outer approximation that converges to the Julia set when `m` increases.
28+
29+
using LinearAlgebra
30+
function in_set(x, c, m=2000)
31+
r = escape_radius(c)
32+
for i in 1:m
33+
if norm(x) > r
34+
return false
35+
end
36+
x = julia_map(x, c)
37+
end
38+
return true
39+
end
40+
41+
# To sort of minimize a level set of a polynomial we minimize integral of that polynomial.
42+
# We borrow the following from https://doi.org/10.1080/00029890.2001.11919774
43+
44+
using SpecialFunctions
45+
using DynamicPolynomials
46+
β(α) =+ 1) / 2
47+
function circle_integral(mono::AbstractMonomial)
48+
if any(isodd, exponents(mono))
49+
return 0.0
50+
else
51+
return 2 * prod(gamma β, exponents(mono)) / gamma(sum(β, exponents(mono)))
52+
end
53+
end
54+
function disk_integral(mono::AbstractMonomial, r)
55+
d = degree(mono) + nvariables(mono)
56+
return circle_integral(mono) * r^d / d
57+
end
58+
function disk_integral(p::AbstractPolynomialLike, r)
59+
return sum(MultivariatePolynomials.coefficient(t) * disk_integral(monomial(t), r) for t in terms(p))
60+
end
61+
62+
# The following function implements [KHJ14, (8)].
63+
64+
using SumOfSquares
65+
function outer_approximation(solver, d::Int, c; α = 1/2)
66+
@polyvar x[1:2]
67+
model = SOSModel(solver)
68+
r = escape_radius(c)
69+
S = @set sum(x.^2) <= r^2
70+
@variable(model, v, Poly(monomials(x, 0:2d)))
71+
@variable(model, w0, SOSPoly(monomials(x, 0:d)))
72+
@variable(model, w1, SOSPoly(monomials(x, 0:(d - 1))))
73+
@constraint(model, α * v(x => julia_map(x, c)) <= v, domain = S)
74+
w = w0 + w1 * (r^2 - sum(x.^2))
75+
@constraint(model, w >= v + 1, domain = S)
76+
@objective(model, Min, disk_integral(w, r))
77+
optimize!(model)
78+
@test termination_status(model) in [MOI.OPTIMAL, MOI.ALMOST_OPTIMAL] #src
79+
@test primal_status(model) in [MOI.FEASIBLE_POINT, MOI.NEARLY_FEASIBLE_POINT] #src
80+
if primal_status(model) == MOI.NO_SOLUTION
81+
return
82+
end
83+
return model
84+
end
85+
86+
# The following function plots the Julia set with the outer approximation.
87+
88+
using ImplicitPlots
89+
using Plots
90+
function julia_plot(poly, c, n=200, m=1000; tol=1e-6, res = 1000)
91+
r = escape_radius(c)
92+
p = implicit_plot(poly; xlims=(-r, r) .* 1.1, ylims=(-r, r), resolution = res, label="")
93+
θ = range(0, stop=2π, length=100)
94+
points = Vector{Float64}[]
95+
as = range(-r, r, length=n)
96+
bs = range(-r, r, length=n)
97+
for a in as, b in bs
98+
point = [a, b]
99+
if in_set(point, c, m)
100+
push!(points, point)
101+
end
102+
end
103+
xs = [point[1] for point in points]
104+
ys = [point[2] for point in points]
105+
scatter!(p, xs, ys, label="", markerstrokewidth=0, markersize=1.5, m=:pixel)
106+
return p
107+
end
108+
109+
# We need to pick an SDP solver, see [here](https://jump.dev/JuMP.jl/v1.8/installation/#Supported-solvers) for a list of the available choices.
110+
111+
import CSDP
112+
solver = optimizer_with_attributes(CSDP.Optimizer, MOI.Silent() => true)
113+
114+
# Let's start with the value of `c` corresponding to the left image of [KHJ14, Figure 3] and with degree 2.
115+
116+
c = -0.7 + 0.2im
117+
model = outer_approximation(solver, 2, c)
118+
solution_summary(model)
119+
120+
# We visualize below:
121+
122+
julia_plot(value(model[:v]), c)
123+
124+
# Let's now look at degree 4.
125+
126+
model = outer_approximation(solver, 4, c)
127+
solution_summary(model)
128+
129+
# We visualize below:
130+
131+
julia_plot(value(model[:v]), c)
132+
133+
# Let's now use the value of `c` corresponding to the right image of [KHJ14, Figure 3] and with degree 2.
134+
135+
c = -0.9 + 0.2im
136+
model = outer_approximation(solver, 2, c)
137+
solution_summary(model)
138+
139+
# We visualize below:
140+
141+
julia_plot(value(model[:v]), c)
142+
143+
# Let's now look at degree 4.
144+
145+
model = outer_approximation(solver, 4, c)
146+
solution_summary(model)
147+
148+
# We visualize below:
149+
150+
julia_plot(value(model[:v]), c)

examples/run_examples.jl

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
using Test
22

3+
"""
4+
_include_sandbox(filename)
5+
6+
Include the `filename` in a temporary module that acts as a sandbox. (Ensuring
7+
no constants or functions leak into other files.)
8+
9+
This function was taken from `JuMP/docs/make.jl`.
10+
"""
11+
function _include_sandbox(filename)
12+
mod = @eval module $(gensym()) end
13+
return Base.include(mod, filename)
14+
end
15+
316
const _TUTORIAL_DIR = joinpath(@__DIR__, "..", "docs", "src", "tutorials")
417

518
@testset "run_examples.jl" begin
@@ -9,7 +22,7 @@ const _TUTORIAL_DIR = joinpath(@__DIR__, "..", "docs", "src", "tutorials")
922
continue
1023
end
1124
path = joinpath(_TUTORIAL_DIR, dir, filename)
12-
include(path)
25+
_include_sandbox(path)
1326
end
1427
end
1528
@testset "Chordal" begin

0 commit comments

Comments
 (0)