Dualization
Цель данного руководства — объяснить использование Dualization.jl для повышения производительности некоторых моделей конической оптимизации. Важно отметить два момента:
-
JuMP переформулирует задачи в соответствии с входными требованиями
решателя, из-за чего размер задачи может увеличиваться вследствие добавления ослабляющих переменных и ограничений.
-
Решение двойственной формы конической модели может быть более эффективным, чем
прямой.
Пакет Dualization.jl устраняет эти проблемы, позволяя решать двойственную задачу вместо прямой, изменив всего одну строку кода.
В этом руководстве используются следующие пакеты.
using JuMP
import Dualization
import SCS
Вводная информация
Решатели конической оптимизации обычно принимают одну из двух входных формулировок.
Первая — это стандартная коническая форма:
в которой имеется набор линейных ограничений равенства , а переменные принадлежат конусу .
Вторая — это геометрическая коническая форма:
в которой аффинная функция принадлежит конусу , а переменные свободны.
Преобразование между этими двумя формами не представляет никакой сложности. Например, чтобы перейти от геометрической конической формы к стандартной, мы вводим ослабляющие переменные :
А чтобы перейти от стандартной конической формы к геометрической, можно переписать ограничение равенства как функцию, принадлежащую конусу {0}
:
С теоретической точки зрения эти две формулировки эквивалентны, и если реализовать модель в стандартной конической форме и передать ее решателю геометрической конической формы (или наоборот), то JuMP автоматически переформулирует задачу в правильную форму.
Однако на практике переформулировки вызывают проблемы, так как дополнительные ослабляющие переменные и ограничения могут значительно усложнить задачу и, следовательно, затруднить ее решение.
Также следует отметить, что многие задачи содержат комбинацию конических ограничений и переменных, поэтому их нельзя однозначно отнести ни к одной из двух формулировок. В таких случаях JuMP переформулирует только те переменные и ограничения, которые необходимы для преобразования задачи в требуемую форму.
Прямая и двойственная формулировки
Двойственность играет большую роль в конической оптимизации. Подробное описание конической двойственности см. в разделе Duality.
Полезно будет отметить, что если прямая задача имеет стандартную коническую форму, то двойственная задача имеет геометрическую коническую форму, и наоборот. Более того, прямая и двойственная задачи могут содержать разное количество переменных и ограничений, причем то, где их будет меньше, зависит от задачи. Поэтому вместо переформулирования задачи из одной формы в другую может быть эффективнее решить двойственную задачу вместо прямой.
Для демонстрации мы используем вариацию примера из раздела Maximum cut via SDP.
Прямая формулировка (в стандартной конической форме):
model_primal = Model()
@variable(model_primal, X[1:2, 1:2], PSD)
@objective(model_primal, Max, sum([1 -1; -1 1] .* X))
@constraint(model_primal, primal_c[i = 1:2], 1 - X[i, i] == 0)
print(model_primal)
Max X[1,1] - 2 X[1,2] + X[2,2]
Subject to
primal_c[1] : -X[1,1] = -1
primal_c[2] : -X[2,2] = -1
[X[1,1], X[1,2], X[2,2]] ∈ MathOptInterface.PositiveSemidefiniteConeTriangle(2)
В этой задаче есть три скалярные переменные решения (матрица X
симметрична), два скалярных ограничения равенства и ограничение, согласно которому матрица X
является положительно полуопределенной.
Двойственная по отношению к model_primal
задача:
model_dual = Model()
@variable(model_dual, y[1:2])
@objective(model_dual, Min, sum(y))
@constraint(model_dual, dual_c, [y[1]-1 1; 1 y[2]-1] in PSDCone())
print(model_dual)
Min y[1] + y[2]
Subject to
dual_c : [y[1] - 1 1
1 y[2] - 1] ∈ PSDCone()
В этой задаче две скалярные переменные решения и ограничение в виде положительно полуопределенной матрицы 2x2.
При решении model_primal
с помощью SCS.Optimizer
SCS сообщает о трех переменных (variables n: 3
), пяти строках в матрице ограничений (constraints m: 5
) и пяти ненулевых элементах в матрице (nnz(A): 5
):
set_optimizer(model_primal, SCS.Optimizer)
optimize!(model_primal)
@assert is_solved_and_feasible(model_primal; dual = true)
------------------------------------------------------------------
SCS v3.2.6 - Splitting Conic Solver
(c) Brendan O'Donoghue, Stanford University, 2012
------------------------------------------------------------------
problem: variables n: 3, constraints m: 5
cones: z: primal zero / dual free vars: 2
s: psd vars: 3, ssize: 1
settings: eps_abs: 1.0e-04, eps_rel: 1.0e-04, eps_infeas: 1.0e-07
alpha: 1.50, scale: 1.00e-01, adaptive_scale: 1
max_iters: 100000, normalize: 1, rho_x: 1.00e-06
acceleration_lookback: 10, acceleration_interval: 10
compiled with openmp parallelization enabled
lin-sys: sparse-direct-amd-qdldl
nnz(A): 5, nnz(P): 0
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 1.65e+01 1.60e-01 5.09e+01 -2.91e+01 1.00e-01 2.18e-03
50| 1.74e-08 2.70e-10 4.88e-08 -4.00e+00 1.00e-01 2.28e-03
------------------------------------------------------------------
status: solved
timings: total: 2.28e-03s = setup: 1.44e-04s + solve: 2.14e-03s
lin-sys: 1.36e-05s, cones: 1.88e-04s, accel: 3.87e-06s
------------------------------------------------------------------
objective = -4.000000
------------------------------------------------------------------
(В матрице ограничений пять строк по той причине, что SCS ожидает задачи в геометрической конической форме и поэтому в JuMP переменное ограничение X, PSD
было переформулировано в аффинное ограничение X .+ 0 in PSDCone()
.)
Получаем следующее решение:
value.(X)
2×2 Matrix{Float64}:
1.0 -1.0
-1.0 1.0
dual.(primal_c)
2-element Vector{Float64}:
1.9999999997299098
1.9999999997299098
objective_value(model_primal)
3.9999999506359964
При решении model_dual
с помощью SCS.Optimizer
SCS сообщает о двух переменных (variables n: 2
), трех строках в матрице ограничений (constraints m: 3
) и двух ненулевых элементах в матрице (nnz(A): 2
):
set_optimizer(model_dual, SCS.Optimizer)
optimize!(model_dual)
@assert is_solved_and_feasible(model_dual; dual = true)
------------------------------------------------------------------
SCS v3.2.6 - Splitting Conic Solver
(c) Brendan O'Donoghue, Stanford University, 2012
------------------------------------------------------------------
problem: variables n: 2, constraints m: 3
cones: s: psd vars: 3, ssize: 1
settings: eps_abs: 1.0e-04, eps_rel: 1.0e-04, eps_infeas: 1.0e-07
alpha: 1.50, scale: 1.00e-01, adaptive_scale: 1
max_iters: 100000, normalize: 1, rho_x: 1.00e-06
acceleration_lookback: 10, acceleration_interval: 10
compiled with openmp parallelization enabled
lin-sys: sparse-direct-amd-qdldl
nnz(A): 2, nnz(P): 0
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 1.23e+01 1.00e+00 2.73e+01 -9.03e+00 1.00e-01 2.94e-04
50| 1.13e-07 1.05e-09 3.23e-07 4.00e+00 1.00e-01 3.80e-04
------------------------------------------------------------------
status: solved
timings: total: 3.81e-04s = setup: 6.18e-05s + solve: 3.19e-04s
lin-sys: 8.08e-06s, cones: 5.23e-05s, accel: 2.94e-06s
------------------------------------------------------------------
objective = 4.000000
------------------------------------------------------------------
Решение получается таким:
dual.(dual_c)
2×2 Matrix{Float64}:
1.0 -1.0
-1.0 1.0
value.(y)
2-element Vector{Float64}:
2.0000001592726786
2.0000001592726786
objective_value(model_dual)
4.000000318545357
Данная задача настолько мала, что сравнивать время ее решения не имеет смысла, но в целом следует ожидать, что форма model_dual
решится быстрее, чем model_primal
, так как она содержит меньше переменных и ограничений. Разница особенно заметна при решении масштабных задач оптимизации.
dual_optimizer
Вывод конической двойственной задачи вручную сложен, и высока вероятность ошибок. Пакет Dualization.jl предоставляет мета-решатель Dualization.dual_optimizer
, который заключает любой совместимый с MathOptInterface решатель в интерфейс, автоматически формулирующий и решающий двойственную по отношению к входной задаче.
Для демонстрации мы используем Dualization.dual_optimizer
для решения model_primal
:
set_optimizer(model_primal, Dualization.dual_optimizer(SCS.Optimizer))
optimize!(model_primal)
@assert is_solved_and_feasible(model_primal; dual = true)
------------------------------------------------------------------
SCS v3.2.6 - Splitting Conic Solver
(c) Brendan O'Donoghue, Stanford University, 2012
------------------------------------------------------------------
problem: variables n: 2, constraints m: 3
cones: s: psd vars: 3, ssize: 1
settings: eps_abs: 1.0e-04, eps_rel: 1.0e-04, eps_infeas: 1.0e-07
alpha: 1.50, scale: 1.00e-01, adaptive_scale: 1
max_iters: 100000, normalize: 1, rho_x: 1.00e-06
acceleration_lookback: 10, acceleration_interval: 10
compiled with openmp parallelization enabled
lin-sys: sparse-direct-amd-qdldl
nnz(A): 2, nnz(P): 0
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 1.23e+01 1.00e+00 2.73e+01 -9.03e+00 1.00e-01 3.55e-04
50| 1.13e-07 1.05e-09 3.23e-07 4.00e+00 1.00e-01 4.35e-04
------------------------------------------------------------------
status: solved
timings: total: 4.36e-04s = setup: 5.39e-05s + solve: 3.82e-04s
lin-sys: 8.55e-06s, cones: 6.48e-05s, accel: 3.15e-06s
------------------------------------------------------------------
objective = 4.000000
------------------------------------------------------------------
Производительность такая же, как при решении model_dual
, а в X
возвращается правильное решение:
value.(X)
2×2 Matrix{Float64}:
1.0 -1.0
-1.0 1.0
dual.(primal_c)
2-element Vector{Float64}:
2.0000001592726786
2.0000001592726786
Более того, если мы используем dual_optimizer
применительно к model_dual
, то получим ту же производительность, что и при решении model_primal
:
set_optimizer(model_dual, Dualization.dual_optimizer(SCS.Optimizer))
optimize!(model_dual)
@assert is_solved_and_feasible(model_dual; dual = true)
------------------------------------------------------------------
SCS v3.2.6 - Splitting Conic Solver
(c) Brendan O'Donoghue, Stanford University, 2012
------------------------------------------------------------------
problem: variables n: 3, constraints m: 5
cones: z: primal zero / dual free vars: 2
s: psd vars: 3, ssize: 1
settings: eps_abs: 1.0e-04, eps_rel: 1.0e-04, eps_infeas: 1.0e-07
alpha: 1.50, scale: 1.00e-01, adaptive_scale: 1
max_iters: 100000, normalize: 1, rho_x: 1.00e-06
acceleration_lookback: 10, acceleration_interval: 10
compiled with openmp parallelization enabled
lin-sys: sparse-direct-amd-qdldl
nnz(A): 5, nnz(P): 0
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 1.65e+01 1.60e-01 5.09e+01 -2.91e+01 1.00e-01 3.46e-04
50| 1.74e-08 2.70e-10 4.88e-08 -4.00e+00 1.00e-01 4.24e-04
------------------------------------------------------------------
status: solved
timings: total: 4.25e-04s = setup: 5.45e-05s + solve: 3.71e-04s
lin-sys: 1.04e-05s, cones: 5.56e-05s, accel: 3.21e-06s
------------------------------------------------------------------
objective = -4.000000
------------------------------------------------------------------
dual.(dual_c)
2×2 Matrix{Float64}:
1.0 -1.0
-1.0 1.0
value.(y)
2-element Vector{Float64}:
1.9999999997299098
1.9999999997299098
Смешанный пример
Пример из раздела Maximum cut via SDP хорошо поддается определению, так как прямая задача имеет стандартную коническую форму, а двойственная — геометрическую коническую форму. Однако на практике многие модели содержат комбинацию этих двух формулировок. Примером может служить задача из раздела The minimum distortion problem:
D = [0 1 1 1; 1 0 2 2; 1 2 0 2; 1 2 2 0]
model = Model()
@variable(model, c²)
@variable(model, Q[1:4, 1:4], PSD)
@objective(model, Min, c²)
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
@constraint(model, Q[1, 1] == 0)
@constraint(model, c² >= 1)
c² ≥ 1
В этой формулировке переменная Q
имеет вид , но также имеются свободная переменная c²
, линейное ограничение равенства Q[1, 1] == 0
и ряд линейных ограничений неравенства. Вместо того чтобы пытаться вывести формулировку, которую JuMP передаст в SCS, и двойственную по отношению к ней формулировку, простейшим решением будет попытаться решить задачу с использованием dual_optimizer
и без него, чтобы узнать, какая формулировка эффективнее.
set_optimizer(model, SCS.Optimizer)
optimize!(model)
------------------------------------------------------------------
SCS v3.2.6 - Splitting Conic Solver
(c) Brendan O'Donoghue, Stanford University, 2012
------------------------------------------------------------------
problem: variables n: 11, constraints m: 24
cones: z: primal zero / dual free vars: 1
l: linear vars: 13
s: psd vars: 10, ssize: 1
settings: eps_abs: 1.0e-04, eps_rel: 1.0e-04, eps_infeas: 1.0e-07
alpha: 1.50, scale: 1.00e-01, adaptive_scale: 1
max_iters: 100000, normalize: 1, rho_x: 1.00e-06
acceleration_lookback: 10, acceleration_interval: 10
compiled with openmp parallelization enabled
lin-sys: sparse-direct-amd-qdldl
nnz(A): 54, nnz(P): 0
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 4.73e+00 1.00e+00 2.92e+00 1.23e+00 1.00e-01 3.44e-04
150| 1.01e-04 3.07e-05 6.08e-05 1.33e+00 1.00e-01 1.09e-03
------------------------------------------------------------------
status: solved
timings: total: 1.09e-03s = setup: 9.65e-05s + solve: 9.91e-04s
lin-sys: 1.16e-04s, cones: 4.39e-04s, accel: 1.30e-04s
------------------------------------------------------------------
objective = 1.333363
------------------------------------------------------------------
set_optimizer(model, Dualization.dual_optimizer(SCS.Optimizer))
optimize!(model)
------------------------------------------------------------------
SCS v3.2.6 - Splitting Conic Solver
(c) Brendan O'Donoghue, Stanford University, 2012
------------------------------------------------------------------
problem: variables n: 14, constraints m: 24
cones: z: primal zero / dual free vars: 1
l: linear vars: 13
s: psd vars: 10, ssize: 1
settings: eps_abs: 1.0e-04, eps_rel: 1.0e-04, eps_infeas: 1.0e-07
alpha: 1.50, scale: 1.00e-01, adaptive_scale: 1
max_iters: 100000, normalize: 1, rho_x: 1.00e-06
acceleration_lookback: 10, acceleration_interval: 10
compiled with openmp parallelization enabled
lin-sys: sparse-direct-amd-qdldl
nnz(A): 57, nnz(P): 0
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 3.71e+01 1.48e+00 2.23e+02 -1.13e+02 1.00e-01 3.86e-04
150| 1.57e-04 2.28e-05 2.08e-04 -1.33e+00 1.00e-01 1.05e-03
------------------------------------------------------------------
status: solved
timings: total: 1.05e-03s = setup: 9.79e-05s + solve: 9.52e-04s
lin-sys: 1.33e-04s, cones: 4.24e-04s, accel: 3.31e-05s
------------------------------------------------------------------
objective = -1.333460
------------------------------------------------------------------
Для этой задачи SCS сообщает, что в прямой задаче имеется variables n: 11, constraints m: 24
, а в двойственной — variables n: 14, constraints m: 24
. Поэтому, вероятно, следует использовать прямую формулировку, так как в ней меньше переменных и столько же ограничений.
Когда следует использовать dual_optimizer
Определенного правила касательно того, когда следует использовать dual_optimizer
, не существует, так как в зависимости от конкретной задачи и выбора решателя задача может становиться больше или меньше. Однако вы можете попробовать использовать dual_optimizer
, если решение задачи конической оптимизации занимает много времени или необходимо многократно решать задачи с похожей структурой, но с разными данными. В некоторых случаях решение двойственной задачи вместо прямой может давать существенный выигрыш.