Простые примеры полуопределенного программирования
В этом руководстве собраны примеры небольших конических программ из области полуопределенного программирования (SDP).
В этом руководстве используются следующие пакеты.
using JuMP
import LinearAlgebra
import Plots
import Random
import SCS
import Test
Максимальный разрез с использованием SDP
Задача о максимальном разрезе — классический пример из теории графов. В ней нужно разделить граф на два комплементарных множества так, чтобы вес ребер между ними был максимальным. Эта задача является трудной в нелинейном программировании, но можно получить приближенное решение с помощью ослабления полуопределенного программирования:
где — это взвешенный лапласиан графа, а — вектор единиц. Дополнительные сведения см. в работе Goemans1995.
"""
svd_cholesky(X::AbstractMatrix, rtol)
Return the matrix `U` of the Cholesky decomposition of `X` as `U' * U`.
Note that we do not use the `LinearAlgebra.cholesky` function because it
requires the matrix to be positive definite while `X` may be only
positive *semi*definite.
We use the convention `U' * U` instead of `U * U'` to be consistent with
`LinearAlgebra.cholesky`.
"""
function svd_cholesky(X::AbstractMatrix)
F = LinearAlgebra.svd(X)
# Теперь мы имеем `X ≈ `F.U * D² * F.U'`, где:
D = LinearAlgebra.Diagonal(sqrt.(F.S))
# Следовательно, `X ≈ U' * U`, где `U`:
return (F.U * D)'
end
function solve_max_cut_sdp(weights)
N = size(weights, 1)
# Вычисляем (взвешенный) лапласиан графа: L = D - W.
L = LinearAlgebra.diagm(0 => weights * ones(N)) - weights
model = Model(SCS.Optimizer)
set_silent(model)
@variable(model, X[1:N, 1:N], PSD)
for i in 1:N
set_start_value(X[i, i], 1.0)
end
@objective(model, Max, 0.25 * LinearAlgebra.dot(L, X))
@constraint(model, LinearAlgebra.diag(X) .== 1)
optimize!(model)
@assert is_solved_and_feasible(model)
V = svd_cholesky(value(X))
Random.seed!(N)
r = rand(N)
r /= LinearAlgebra.norm(r)
cut = [LinearAlgebra.dot(r, V[:, i]) > 0 for i in 1:N]
S = findall(cut)
T = findall(.!cut)
println("Solution:")
println(" (S, T) = ({", join(S, ", "), "}, {", join(T, ", "), "})")
return S, T
end
solve_max_cut_sdp (generic function with 1 method)
Для следующего графа:
[1] --- 5 --- [2]
Решением будет (S, T) = ({1}, {2})
:
S, T = solve_max_cut_sdp([0 5; 5 0])
([2], [1])
Для следующего графа:
[1] --- 5 --- [2]
| \ |
| \ |
7 6 1
| \ |
| \ |
[3] --- 1 --- [4]
Решением будет (S, T) = ({1}, {2, 3, 4})
:
S, T = solve_max_cut_sdp([0 5 7 6; 5 0 0 1; 7 0 0 1; 6 1 1 0])
([1], [2, 3, 4])
Для следующего графа:
[1] --- 1 --- [2]
| |
| |
5 9
| |
| |
[3] --- 2 --- [4]
Решением будет (S, T) = ({1, 4}, {2, 3})
:
S, T = solve_max_cut_sdp([0 1 5 0; 1 0 0 9; 5 0 0 2; 0 9 2 0])
([1, 4], [2, 3])
Кластеризация методом k-средних с использованием SDP
Данное множество точек в необходимо распределить по кластерам.
Дополнительные сведения см. в работе Peng2007.
function example_k_means_clustering()
a = [[2.0, 2.0], [2.5, 2.1], [7.0, 7.0], [2.2, 2.3], [6.8, 7.0], [7.2, 7.5]]
m = length(a)
num_clusters = 2
W = zeros(m, m)
for i in 1:m, j in i+1:m
W[i, j] = W[j, i] = exp(-LinearAlgebra.norm(a[i] - a[j]) / 1.0)
end
model = Model(SCS.Optimizer)
set_silent(model)
@variable(model, Z[1:m, 1:m] >= 0, PSD)
@objective(model, Min, LinearAlgebra.tr(W * (LinearAlgebra.I - Z)))
@constraint(model, [i = 1:m], sum(Z[i, :]) .== 1)
@constraint(model, LinearAlgebra.tr(Z) == num_clusters)
optimize!(model)
@assert is_solved_and_feasible(model)
Z_val = value.(Z)
current_cluster, visited = 0, Set{Int}()
for i in 1:m
if !(i in visited)
current_cluster += 1
println("Cluster $current_cluster")
for j in i:m
if isapprox(Z_val[i, i], Z_val[i, j]; atol = 1e-3)
println(a[j])
push!(visited, j)
end
end
end
end
return
end
example_k_means_clustering()
Cluster 1
[2.0, 2.0]
[2.5, 2.1]
[2.2, 2.3]
Cluster 2
[7.0, 7.0]
[6.8, 7.0]
[7.2, 7.5]
Задача о корреляции
Даны три случайные величины , и , а также границы двух из трех коэффициентов корреляции:
Задача заключается в определении верхней и нижней границ третьего коэффициента корреляции .
Мы решаем SDP, используя следующее положительно полуопределенное свойство матрицы корреляции:
function example_correlation_problem()
model = Model(SCS.Optimizer)
set_silent(model)
@variable(model, X[1:3, 1:3], PSD)
S = ["A", "B", "C"]
ρ = Containers.DenseAxisArray(X, S, S)
@constraint(model, [i in S], ρ[i, i] == 1)
@constraint(model, -0.2 <= ρ["A", "B"] <= -0.1)
@constraint(model, 0.4 <= ρ["B", "C"] <= 0.5)
@objective(model, Max, ρ["A", "C"])
optimize!(model)
@assert is_solved_and_feasible(model)
println("An upper bound for ρ_AC is $(value(ρ["A", "C"]))")
@objective(model, Min, ρ["A", "C"])
optimize!(model)
@assert is_solved_and_feasible(model)
println("A lower bound for ρ_AC is $(value(ρ["A", "C"]))")
return
end
example_correlation_problem()
An upper bound for ρ_AC is 0.8719220302987134
A lower bound for ρ_AC is -0.97799895940284
Задача о минимальном искажении
Этот пример взят из вычислительной геометрии, в частности из задачи вложения общего конечного метрического пространства в евклидово пространство.
Известно, что 4-точечное метрическое пространство, определяемое звездным графом:
[1]
\
1
\
[0] —- 1 -- [2]
/
1
/
[3]
не может быть точно вложено в евклидово пространство любой размерности, где расстояния вычисляются по длине кратчайшего пути между вершинами. Вложение с сохранением расстояний потребовало бы, чтобы три конечных узла образовывали равносторонний треугольник с длиной стороны 2, а центральный узел (0) отображался в равноудаленную точку на расстоянии 1. Это невозможно, так как из неравенства треугольника в евклидовом пространстве следует, что все точки должны быть одновременно коллинеарными.
Здесь мы сформулируем и решим задачу SDP для вычисления наилучшего возможного вложения, то есть вложения
для всех ребер
Любое вложение
Элемент
Поэтому мы налагаем ограничение
для всех ребер
Дополнительные сведения см. в работах Matousek 2013, Linial 2002.
function example_minimum_distortion()
model = Model(SCS.Optimizer)
set_silent(model)
D = [
0.0 1.0 1.0 1.0
1.0 0.0 2.0 2.0
1.0 2.0 0.0 2.0
1.0 2.0 2.0 0.0
]
@variable(model, c² >= 1.0)
@variable(model, Q[1:4, 1:4], PSD)
for i in 1:4, j in (i+1):4
@constraint(model, D[i, j]^2 <= Q[i, i] + Q[j, j] - 2 * Q[i, j])
@constraint(model, Q[i, i] + Q[j, j] - 2 * Q[i, j] <= c² * D[i, j]^2)
end
fix(Q[1, 1], 0)
@objective(model, Min, c²)
optimize!(model)
Test.@test is_solved_and_feasible(model)
Test.@test objective_value(model) ≈ 4 / 3 atol = 1e-4
# Восстановим вложение с минимальным искажением:
X = [zeros(3) sqrt(value.(Q)[2:end, 2:end])]
return Plots.plot(
X[1, :],
X[2, :],
X[3, :];
seriestype = :mesh3d,
connections = ([0, 0, 0, 1], [1, 2, 3, 2], [2, 3, 1, 3]),
legend = false,
fillalpha = 0.1,
lw = 3,
ratio = :equal,
xlim = (-1.1, 1.1),
ylim = (-1.1, 1.1),
zlim = (-1.5, 1.0),
zticks = -1:1,
camera = (60, 30),
)
end
example_minimum_distortion()
Числа Ловаса
Число Ловаса графа, также известное как тета-функция Ловаса, — это число, лежащее между двумя важными и связанными числами, которые трудно определить вычислительными методами, а именно хроматическим и кликовым числами графа. Однако число Ловаса можно эффективно вычислить как оптимальное значение полуопределенной программы.
Рассмотрим пятиугольный граф:
[5]
/ \
/ \
[1] [4]
| |
| |
[2] --- [3]
с пятью вершинами и ребрами. Известно, что его число Ловаса точно равно
Пусть
где
Дополнительные сведения см. в работах Barvinok 2002, Knuth 1994.
function example_theta_problem()
model = Model(SCS.Optimizer)
set_silent(model)
E = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 1)]
@variable(model, X[1:5, 1:5], PSD)
for i in 1:5
for j in (i+1):5
if !((i, j) in E || (j, i) in E)
A = zeros(Int, 5, 5)
A[i, j] = 1
A[j, i] = 1
@constraint(model, LinearAlgebra.dot(A, X) == 0)
end
end
end
@constraint(model, LinearAlgebra.tr(LinearAlgebra.I * X) == 1)
J = ones(Int, 5, 5)
@objective(model, Max, LinearAlgebra.dot(J, X))
optimize!(model)
Test.@test is_solved_and_feasible(model)
Test.@test objective_value(model) ≈ sqrt(5) rtol = 1e-4
println("The Lovász number is: $(objective_value(model))")
return
end
example_theta_problem()
The Lovász number is: 2.2360678617948677
Устойчивые множества неопределенности
В этом примере вычисляется значение риска для множества неопределенности, основанного на данных. Доступны выражения в замкнутой форме для оптимального значения. Дополнительные сведения см. в работе Bertsimas 2018.
function example_robust_uncertainty_sets()
R, d, 𝛿, ɛ = 1, 3, 0.05, 0.05
N = ceil((2 + 2 * log(2 / 𝛿))^2) + 1
c, μhat, M = randn(d), rand(d), rand(d, d)
Σhat = 1 / (d - 1) * (M - ones(d) * μhat')' * (M - ones(d) * μhat')
Γ1(𝛿, N) = R / sqrt(N) * (2 + sqrt(2 * log(1 / 𝛿)))
Γ2(𝛿, N) = 2 * R^2 / sqrt(N) * (2 + sqrt(2 * log(2 / 𝛿)))
model = Model(SCS.Optimizer)
set_silent(model)
@variable(model, Σ[1:d, 1:d], PSD)
@variable(model, u[1:d])
@variable(model, μ[1:d])
@constraint(model, [Γ1(𝛿 / 2, N); μ - μhat] in SecondOrderCone())
@constraint(model, [Γ2(𝛿 / 2, N); vec(Σ - Σhat)] in SecondOrderCone())
@constraint(model, [((1-ɛ)/ɛ) (u - μ)'; (u-μ) Σ] >= 0, PSDCone())
@objective(model, Max, c' * u)
optimize!(model)
@assert is_solved_and_feasible(model)
exact =
μhat' * c +
Γ1(𝛿 / 2, N) * LinearAlgebra.norm(c) +
sqrt((1 - ɛ) / ɛ) *
sqrt(c' * (Σhat + Γ2(𝛿 / 2, N) * LinearAlgebra.I) * c)
Test.@test objective_value(model) ≈ exact atol = 1e-2
return
end
example_robust_uncertainty_sets()