|
| 1 | +# Copyright 2017, Iain Dunning, Joey Huchette, Miles Lubin, and contributors #src |
| 2 | +# This Source Code Form is subject to the terms of the Mozilla Public License #src |
| 3 | +# v.2.0. If a copy of the MPL was not distributed with this file, You can #src |
| 4 | +# obtain one at https://mozilla.org/MPL/2.0/. #src |
| 5 | + |
| 6 | +# # Ellipsoid approximation |
| 7 | + |
| 8 | +# This example considers the problem of computing _extremal ellipsoids_: |
| 9 | +# finding ellipsoids that best approximate a given set. |
| 10 | +# Our example will focus on outer approximations of finite sets |
| 11 | +# of points. |
| 12 | + |
| 13 | +# This example comes from section 4.9 "Applications VII: Extremal Ellipsoids" |
| 14 | +# of the book *Lectures on Modern Convex Optimization* by |
| 15 | +# [Ben-Tal and Nemirovski (2001)](http://epubs.siam.org/doi/book/10.1137/1.9780898718829). |
| 16 | + |
| 17 | +# For a related example, see also the [Minimal ellipses](@ref) tutorial. |
| 18 | + |
| 19 | +# ## Problem formulation |
| 20 | + |
| 21 | +# Suppose that we are given a set ``S`` consisting of ``m`` points in ``n``-dimensional space: |
| 22 | +# ```math |
| 23 | +# \mathcal{S} = \{ x_1, \ldots, x_m \} \subset \mathbb{R}^n |
| 24 | +# ``` |
| 25 | +# Our goal is to determine an optimal vector ``c \in \mathbb{R}^n`` and |
| 26 | +# an optimal ``n \times n`` real symmetric matrix ``D`` such that the ellipse |
| 27 | +# ```math |
| 28 | +# E(D, c) = \{ x : (x - c)^\top D ( x - c) \leq 1 \}, |
| 29 | +# ``` |
| 30 | +# contains ``\mathcal{S}`` and such that this ellipse has |
| 31 | +# the smallest possible volume. |
| 32 | + |
| 33 | +# The optimal ``D`` and ``c`` are given by the convex semidefinite program |
| 34 | +# ```math |
| 35 | +# \begin{aligned} |
| 36 | +# \text{maximize } && \quad (\det(Z))^{\frac{1}{n}} & \\ |
| 37 | +# \text{subject to } && \quad Z \; & \succeq \; 0 & \text{ (PSD) }, & \\ |
| 38 | +# && \quad\begin{bmatrix} |
| 39 | +# s & z^\top \\ |
| 40 | +# z & Z \\ |
| 41 | +# \end{bmatrix} |
| 42 | +# \; & \succeq \; 0 & \text{ (PSD) }, & \\ |
| 43 | +# && x_i^\top Z x_i - 2x_i^\top z + s \; & \leq \; 0 & i=1, \ldots, m & |
| 44 | +# \end{aligned} |
| 45 | +# ``` |
| 46 | +# with matrix variable ``Z``, vector variable ``z`` and real variables ``t, s``. |
| 47 | +# The optimal solution ``(t_*, Z_*, z_*, s_*)`` gives the optimal ellipse data as |
| 48 | +# ```math |
| 49 | +# D = Z_*, \quad c = Z_*^{-1} z_*. |
| 50 | +# ``` |
| 51 | + |
| 52 | +# ## Required packages |
| 53 | + |
| 54 | +# This tutorial uses the following packages: |
| 55 | + |
| 56 | +using JuMP |
| 57 | +import LinearAlgebra |
| 58 | +import Random |
| 59 | +import Plots |
| 60 | +import SCS |
| 61 | +import Test #src |
| 62 | + |
| 63 | +# ## Data |
| 64 | + |
| 65 | +# We first need to generate some points to work with. |
| 66 | + |
| 67 | +function generate_point_cloud( |
| 68 | + m; # number of 2-dimensional points |
| 69 | + a = 10, # scaling in x direction |
| 70 | + b = 2, # scaling in y direction |
| 71 | + rho = deg2rad(30), # rotation of points around origin |
| 72 | + random_seed = 1, |
| 73 | +) |
| 74 | + rng = Random.MersenneTwister(random_seed) |
| 75 | + P = randn(rng, Float64, m, 2) |
| 76 | + Phi = [a*cos(rho) a*sin(rho); -b*sin(rho) b*cos(rho)] |
| 77 | + S = P * Phi |
| 78 | + return S |
| 79 | +end |
| 80 | + |
| 81 | +# For the sake of this example, let's take ``m = 600``: |
| 82 | +S = generate_point_cloud(600) |
| 83 | + |
| 84 | +# We will visualise the points (and ellipse) using the Plots package: |
| 85 | + |
| 86 | +function plot_point_cloud(plot, S; r = 1.1 * maximum(abs.(S)), colour = :green) |
| 87 | + Plots.scatter!( |
| 88 | + plot, |
| 89 | + S[:, 1], |
| 90 | + S[:, 2]; |
| 91 | + xlim = (-r, r), |
| 92 | + ylim = (-r, r), |
| 93 | + label = nothing, |
| 94 | + c = colour, |
| 95 | + shape = :x, |
| 96 | + ) |
| 97 | + return |
| 98 | +end |
| 99 | + |
| 100 | +plot = Plots.plot(; size = (600, 600)) |
| 101 | +plot_point_cloud(plot, S) |
| 102 | +plot |
| 103 | + |
| 104 | +# ## JuMP formulation |
| 105 | + |
| 106 | +# Now let's build the JuMP model. We'll be able to compute |
| 107 | +# ``D`` and ``c`` after the solve. |
| 108 | + |
| 109 | +model = Model(SCS.Optimizer) |
| 110 | +## We need to use a tighter tolerance for this example, otherwise the bounding |
| 111 | +## ellipse won't actually be bounding... |
| 112 | +set_optimizer_attribute(model, "eps_rel", 1e-6) |
| 113 | +set_silent(model) |
| 114 | +m, n = size(S) |
| 115 | +@variable(model, z[1:n]) |
| 116 | +@variable(model, Z[1:n, 1:n], PSD) |
| 117 | +@variable(model, t) |
| 118 | +@variable(model, s) |
| 119 | + |
| 120 | +X = [ |
| 121 | + s z' |
| 122 | + z Z |
| 123 | +] |
| 124 | +@constraint(model, LinearAlgebra.Symmetric(X) >= 0, PSDCone()) |
| 125 | + |
| 126 | +for i in 1:m |
| 127 | + x = S[i, :] |
| 128 | + @constraint(model, (x' * Z * x) - (2 * x' * z) + s <= 1) |
| 129 | +end |
| 130 | + |
| 131 | +# We cannot directly represent the objective ``(\det(Z))^{\frac{1}{n}}``, so we introduce |
| 132 | +# the conic reformulation: |
| 133 | + |
| 134 | +@variable(model, nth_root_det_Z) |
| 135 | +@constraint(model, [nth_root_det_Z; vec(Z)] in MOI.RootDetConeSquare(n)) |
| 136 | +@objective(model, Max, nth_root_det_Z) |
| 137 | + |
| 138 | +# Now, solve the program: |
| 139 | + |
| 140 | +optimize!(model) |
| 141 | +Test.@test termination_status(model) == OPTIMAL #src |
| 142 | +Test.@test primal_status(model) == FEASIBLE_POINT #src |
| 143 | +solution_summary(model) |
| 144 | + |
| 145 | +# ## Results |
| 146 | + |
| 147 | +# After solving the model to optimality we can recover the solution in terms of |
| 148 | +# ``D`` and ``c``: |
| 149 | + |
| 150 | +D = value.(Z) |
| 151 | +c = D \ value.(z) |
| 152 | + |
| 153 | +# Finally, overlaying the solution in the plot we see the minimal volume approximating |
| 154 | +# ellipsoid: |
| 155 | + |
| 156 | +Test.@test isapprox(D, [0.00707 -0.0102; -0.0102173 0.0175624]; atol = 1e-2) #src |
| 157 | +Test.@test isapprox(c, [-3.24802, -1.842825]; atol = 1e-2) #src |
| 158 | + |
| 159 | +P = sqrt(D) |
| 160 | +q = -P * c |
| 161 | + |
| 162 | +Plots.plot!( |
| 163 | + plot, |
| 164 | + [tuple(P \ [cos(θ) - q[1], sin(θ) - q[2]]...) for θ in 0:0.05:(2pi+0.05)]; |
| 165 | + c = :crimson, |
| 166 | + label = nothing, |
| 167 | +) |
0 commit comments