Skip to content

Commit 5d9a0aa

Browse files
authored
Merge pull request #1026 from chriscoey/dualsindetbridge
add log/root-det tests and duals, and support duals through det bridge
2 parents 46f7064 + 9668f8d commit 5d9a0aa

6 files changed

Lines changed: 309 additions & 65 deletions

File tree

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
MathOptInterface (MOI) release notes
22
====================================
33

4+
v0.9.11 (February 13, 2020)
5+
---------------------
6+
7+
- Added more rootdet/logdet conic tests and bridge dual transformations and tests.
8+
49
v0.9.10 (January 31, 2020)
510
---------------------
611

src/Bridges/Constraint/det.jl

Lines changed: 67 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -135,24 +135,51 @@ MOI.get(b::LogDetBridge{T}, ::MOI.ListOfConstraintIndices{MOI.VectorAffineFuncti
135135
MOI.get(b::LogDetBridge{T}, ::MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T}, MOI.LessThan{T}}) where T = [b.tlindex]
136136

137137
# References
138-
function MOI.delete(model::MOI.ModelLike, c::LogDetBridge)
139-
MOI.delete(model, c.tlindex)
140-
MOI.delete(model, c.lcindex)
141-
MOI.delete(model, c.sdindex)
142-
MOI.delete(model, c.l)
143-
MOI.delete(model, c.Δ)
138+
function MOI.delete(model::MOI.ModelLike, bridge::LogDetBridge)
139+
MOI.delete(model, bridge.tlindex)
140+
MOI.delete(model, bridge.lcindex)
141+
MOI.delete(model, bridge.sdindex)
142+
MOI.delete(model, bridge.l)
143+
MOI.delete(model, bridge.Δ)
144144
end
145145

146146
# Attributes, Bridge acting as a constraint
147-
function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintPrimal, c::LogDetBridge)
148-
d = length(c.lcindex)
149-
Δ = MOI.get(model, MOI.VariablePrimal(), c.Δ)
150-
t = MOI.get(model, MOI.ConstraintPrimal(), c.tlindex) +
151-
sum(MOI.get(model, MOI.ConstraintPrimal(), ci)[1] for ci in c.lcindex)
152-
u = MOI.get(model, MOI.ConstraintPrimal(), first(c.lcindex))[2]
153-
x = MOI.get(model, MOI.ConstraintPrimal(), c.sdindex)[1:length(c.Δ)]
154-
return [t; u; x]
147+
function MOI.get(model::MOI.ModelLike, attr::Union{MOI.ConstraintPrimal, MOI.ConstraintPrimalStart}, bridge::LogDetBridge)
148+
d = length(bridge.lcindex)
149+
Δ = MOI.get(model, MOI.VariablePrimal(), bridge.Δ)
150+
t = MOI.get(model, attr, bridge.tlindex) +
151+
sum(MOI.get(model, attr, ci)[1] for ci in bridge.lcindex)
152+
u = MOI.get(model, attr, first(bridge.lcindex))[2]
153+
x = MOI.get(model, attr, bridge.sdindex)[1:length(bridge.Δ)]
154+
return vcat(t, u, x)
155155
end
156+
# [X Δ; Δ' Diag(Δ)] in PSD
157+
# t - sum(l) >= 0
158+
# (l_i, u, Δ_ii) in Exp
159+
# (t, u, x) in LogDet <=> exists Δ, l such that At + Bu + Cx + DΔ + El in (PSD, >=, Exp_i)
160+
# so LogDet* = [A'; B'; C'] (PSD, >=, Exp_i)*
161+
# and 0 = [D'; E'] (PSD, >=, Exp_i)*
162+
# where
163+
# A = [0, 0, 0, 1, 0, 0, 0]
164+
# B = [0, 0, 0, 0, 0, 1, 0]
165+
# C = [I, 0, 0, 0, 0, 0, 0]
166+
# D = [0, I, I(i=j), 0, 0, 0, I(i=j)]
167+
# E = [0, 0, 0, 1, I, 0, 0]
168+
# so given dual q = (a, b, c, d, e, f, g), we get
169+
# t = A' q = d => t = d
170+
# u = B' q = f => u = sum(f)
171+
# x = C' q = a => x = a
172+
# offdiag(b) = 0
173+
# 0 = D' q = diag(b) + diag(c) + g => g = -diag(b) - diag(c)
174+
# let b = 0, so g_i = -c_i
175+
# 0 = E' q = d + e => d = -e
176+
function MOI.get(model::MOI.ModelLike, attr::Union{MOI.ConstraintDual, MOI.ConstraintDualStart}, bridge::LogDetBridge)
177+
t_dual = MOI.get(model, attr, bridge.tlindex)
178+
u_dual = sum(MOI.get(model, attr, lcindex_i)[2] for lcindex_i in bridge.lcindex)
179+
x_dual = MOI.get(model, attr, bridge.sdindex)[1:length(bridge.Δ)]
180+
return vcat(t_dual, u_dual, x_dual)
181+
end
182+
156183

157184
"""
158185
RootDetBridge{T}
@@ -208,15 +235,33 @@ MOI.get(b::RootDetBridge{T}, ::MOI.ListOfConstraintIndices{MOI.VectorAffineFunct
208235
MOI.get(b::RootDetBridge{T}, ::MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{T}, MOI.GeometricMeanCone}) where T = [b.gmindex]
209236

210237
# References
211-
function MOI.delete(model::MOI.ModelLike, c::RootDetBridge)
212-
MOI.delete(model, c.gmindex)
213-
MOI.delete(model, c.sdindex)
214-
MOI.delete(model, c.Δ)
238+
function MOI.delete(model::MOI.ModelLike, bridge::RootDetBridge)
239+
MOI.delete(model, bridge.gmindex)
240+
MOI.delete(model, bridge.sdindex)
241+
MOI.delete(model, bridge.Δ)
215242
end
216243

217244
# Attributes, Bridge acting as a constraint
218-
function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintPrimal, c::RootDetBridge)
219-
t = MOI.get(model, MOI.ConstraintPrimal(), c.gmindex)[1]
220-
x = MOI.get(model, MOI.ConstraintPrimal(), c.sdindex)[1:length(c.Δ)]
221-
[t; x]
245+
function MOI.get(model::MOI.ModelLike, attr::Union{MOI.ConstraintPrimal, MOI.ConstraintPrimalStart}, bridge::RootDetBridge)
246+
t = MOI.get(model, attr, bridge.gmindex)[1]
247+
x = MOI.get(model, attr, bridge.sdindex)[1:length(bridge.Δ)]
248+
return vcat(t, x)
249+
end
250+
# (t, x) in RootDet <=> exists Δ such that At + Bx + CΔ in (PSD, GeoMean)
251+
# so RootDet* = [A'; B'] (PSD, GeoMean)*
252+
# and 0 = [C'] (PSD, GeoMean)*
253+
# where
254+
# A = [0, 0, 0, 1, 0]
255+
# B = [I, 0, 0, 0, 0]
256+
# C = [0, I, I(i=j), 0, I(i=j)]
257+
# so given dual q = (a, b, c, d, e), we get
258+
# t = A' q = d => t = d
259+
# x = B' q = a => x = a
260+
# offdiag(b) = 0
261+
# 0 = C' q = diag(b) + diag(c) + e => e = -diag(b) - diag(c)
262+
# let b = 0, so e_i = -c_i
263+
function MOI.get(model::MOI.ModelLike, attr::Union{MOI.ConstraintDual, MOI.ConstraintDualStart}, bridge::RootDetBridge)
264+
t_dual = MOI.get(model, attr, bridge.gmindex)[1]
265+
x_dual = MOI.get(model, attr, bridge.sdindex)[1:length(bridge.Δ)]
266+
return vcat(t_dual, x_dual)
222267
end

src/Test/contconic.jl

Lines changed: 99 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2592,7 +2592,7 @@ function _det1test(model::MOI.ModelLike, config::TestConfig, vecofvars::Bool, de
25922592
atol = config.atol
25932593
rtol = config.rtol
25942594
square = detcone == MOI.LogDetConeSquare || detcone == MOI.RootDetConeSquare
2595-
logdet = detcone == MOI.LogDetConeTriangle || detcone == MOI.LogDetConeSquare
2595+
use_logdet = detcone == MOI.LogDetConeTriangle || detcone == MOI.LogDetConeSquare
25962596
# We look for an ellipsoid x^T P x ≤ 1 contained in the square.
25972597
# Let Q = inv(P) (x^T Q x ≤ 1 is its polar ellipsoid), we have
25982598
# max t
@@ -2609,7 +2609,7 @@ function _det1test(model::MOI.ModelLike, config::TestConfig, vecofvars::Bool, de
26092609
@test MOIU.supports_default_copy_to(model, #=copy_names=# false)
26102610
@test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}())
26112611
@test MOI.supports(model, MOI.ObjectiveSense())
2612-
if logdet
2612+
if use_logdet
26132613
@test MOI.supports_constraint(model, MOI.SingleVariable, MOI.EqualTo{Float64})
26142614
end
26152615
if vecofvars
@@ -2627,7 +2627,7 @@ function _det1test(model::MOI.ModelLike, config::TestConfig, vecofvars::Bool, de
26272627
Q = MOI.add_variables(model, square ? 4 : 3)
26282628
@test MOI.get(model, MOI.NumberOfVariables()) == (square ? 5 : 4)
26292629

2630-
if logdet
2630+
if use_logdet
26312631
u = MOI.add_variable(model)
26322632
vc = MOI.add_constraint(model, MOI.SingleVariable(u), MOI.EqualTo(1.0))
26332633
@test vc.value == u.value
@@ -2657,12 +2657,12 @@ function _det1test(model::MOI.ModelLike, config::TestConfig, vecofvars::Bool, de
26572657

26582658
@test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
26592659

2660-
expectedobjval = logdet ? 0. : 1.
2660+
expectedobjval = use_logdet ? 0. : 1.
26612661
@test MOI.get(model, MOI.ObjectiveValue()) expectedobjval atol=atol rtol=rtol
26622662

26632663
@test MOI.get(model, MOI.VariablePrimal(), t) expectedobjval atol=atol rtol=rtol
26642664

2665-
if logdet
2665+
if use_logdet
26662666
@test MOI.get(model, MOI.VariablePrimal(), u) 1.0 atol=atol rtol=rtol
26672667
end
26682668

@@ -2676,12 +2676,24 @@ function _det1test(model::MOI.ModelLike, config::TestConfig, vecofvars::Bool, de
26762676

26772677
tQv = MOI.get(model, MOI.ConstraintPrimal(), cX)
26782678
@test tQv[1] expectedobjval atol=atol rtol=rtol
2679-
@test tQv[(logdet ? 3 : 2):end] Qv atol=atol rtol=rtol
2679+
@test tQv[(use_logdet ? 3 : 2):end] Qv atol=atol rtol=rtol
26802680

26812681
@test MOI.get(model, MOI.ConstraintPrimal(), c) [0., 0.] atol=atol rtol=rtol
2682-
if logdet
2682+
if use_logdet
26832683
@test MOI.get(model, MOI.ConstraintPrimal(), vc) 1.0 atol=atol rtol=rtol
26842684
end
2685+
2686+
if config.duals
2687+
if use_logdet
2688+
@test MOI.get(model, MOI.ConstraintDual(), c) [1, 1] atol=atol rtol=rtol
2689+
@test MOI.get(model, MOI.ConstraintDual(), vc) 2 atol=atol rtol=rtol
2690+
dual = square ? [-1, -2, 1, 0, 0, 1] : [-1, -2, 1, 0, 1]
2691+
else
2692+
@test MOI.get(model, MOI.ConstraintDual(), c) [0.5, 0.5] atol=atol rtol=rtol
2693+
dual = square ? [-1.0, 0.5, 0.0, 0.0, 0.5] : [-1.0, 0.5, 0.0, 0.5]
2694+
end
2695+
@test MOI.get(model, MOI.ConstraintDual(), cX) dual atol=atol rtol=rtol
2696+
end
26852697
end
26862698
end
26872699

@@ -2690,13 +2702,85 @@ logdett1ftest(model::MOI.ModelLike, config::TestConfig) = _det1test(model, confi
26902702
logdets1vtest(model::MOI.ModelLike, config::TestConfig) = _det1test(model, config, true, MOI.LogDetConeSquare)
26912703
logdets1ftest(model::MOI.ModelLike, config::TestConfig) = _det1test(model, config, false, MOI.LogDetConeSquare)
26922704

2705+
function _det2test(model::MOI.ModelLike, config::TestConfig, detcone)
2706+
atol = config.atol
2707+
rtol = config.rtol
2708+
square = detcone == MOI.LogDetConeSquare || detcone == MOI.RootDetConeSquare
2709+
use_logdet = detcone == MOI.LogDetConeTriangle || detcone == MOI.LogDetConeSquare
2710+
# We find logdet or rootdet of a symmetric PSD matrix:
2711+
# mat = |3 2 1|
2712+
# |2 2 1|
2713+
# |1 1 3|
2714+
# det(mat) = 5, so:
2715+
# rootdet(mat) ≈ 1.709976
2716+
# logdet(mat) ≈ 1.609438
2717+
2718+
mat = Float64[3 2 1; 2 2 1; 1 1 3]
2719+
matL = Float64[3, 2, 2, 1, 1, 3]
2720+
2721+
@test MOIU.supports_default_copy_to(model, #=copy_names=# false)
2722+
@test MOI.supports(model, MOI.ObjectiveFunction{MOI.SingleVariable}())
2723+
@test MOI.supports(model, MOI.ObjectiveSense())
2724+
@test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, detcone)
2725+
2726+
MOI.empty!(model)
2727+
@test MOI.is_empty(model)
2728+
2729+
t = MOI.add_variable(model)
2730+
@test MOI.get(model, MOI.NumberOfVariables()) == 1
2731+
2732+
MOI.set(model, MOI.ObjectiveFunction{MOI.SingleVariable}(), MOI.SingleVariable(t))
2733+
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
2734+
2735+
constant_mat = square ? vec(mat) : matL
2736+
constant_vec = use_logdet ? vcat(0, 1, constant_mat) : vcat(0, constant_mat)
2737+
vaf = MOI.VectorAffineFunction([MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, t))], constant_vec)
2738+
det_constraint = MOI.add_constraint(model, vaf, detcone(3))
2739+
@test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Float64}, detcone}()) == 1
2740+
2741+
if config.solve
2742+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED
2743+
2744+
MOI.optimize!(model)
2745+
2746+
@test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
2747+
2748+
@test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
2749+
2750+
expected_objval = use_logdet ? log(5) : (5 ^ inv(3))
2751+
@test MOI.get(model, MOI.ObjectiveValue()) expected_objval atol=atol rtol=rtol
2752+
@test MOI.get(model, MOI.VariablePrimal(), t) expected_objval atol=atol rtol=rtol
2753+
2754+
det_value = MOI.get(model, MOI.ConstraintPrimal(), det_constraint)
2755+
@test det_value[1] expected_objval atol=atol rtol=rtol
2756+
if use_logdet
2757+
@test det_value[2] 1.0 atol=atol rtol=rtol
2758+
end
2759+
@test det_value[(use_logdet ? 3 : 2):end] (square ? vec(mat) : matL) atol=atol rtol=rtol
2760+
2761+
if config.duals
2762+
psd_dual = square ? [1, -1, 0, -1, 1.6, -0.2, 0, -0.2, 0.4] : [1, -1, 1.6, 0, -0.2, 0.4]
2763+
dual = use_logdet ? vcat(-1, log(5) - 3, psd_dual) : vcat(-1, psd_dual / 3 * expected_objval)
2764+
@test MOI.get(model, MOI.ConstraintDual(), det_constraint) dual atol=atol rtol=rtol
2765+
end
2766+
end
2767+
end
2768+
2769+
logdett2test(model::MOI.ModelLike, config::TestConfig) = _det2test(model, config, MOI.LogDetConeTriangle)
2770+
logdets2test(model::MOI.ModelLike, config::TestConfig) = _det2test(model, config, MOI.LogDetConeSquare)
2771+
2772+
26932773
const logdetttests = Dict("logdett1v" => logdett1vtest,
2694-
"logdett1f" => logdett1ftest)
2774+
"logdett1f" => logdett1ftest,
2775+
"logdett2" => logdett2test,
2776+
)
26952777

26962778
@moitestset logdett
26972779

26982780
const logdetstests = Dict("logdets1v" => logdets1vtest,
2699-
"logdets1f" => logdets1ftest)
2781+
"logdets1f" => logdets1ftest,
2782+
"logdets2" => logdets2test,
2783+
)
27002784

27012785
@moitestset logdets
27022786

@@ -2709,14 +2793,18 @@ rootdett1vtest(model::MOI.ModelLike, config::TestConfig) = _det1test(model, conf
27092793
rootdett1ftest(model::MOI.ModelLike, config::TestConfig) = _det1test(model, config, false, MOI.RootDetConeTriangle)
27102794
rootdets1vtest(model::MOI.ModelLike, config::TestConfig) = _det1test(model, config, true, MOI.RootDetConeSquare)
27112795
rootdets1ftest(model::MOI.ModelLike, config::TestConfig) = _det1test(model, config, false, MOI.RootDetConeSquare)
2796+
rootdett2test(model::MOI.ModelLike, config::TestConfig) = _det2test(model, config, MOI.RootDetConeTriangle)
2797+
rootdets2test(model::MOI.ModelLike, config::TestConfig) = _det2test(model, config, MOI.RootDetConeSquare)
27122798

27132799
const rootdetttests = Dict("rootdett1v" => rootdett1vtest,
2714-
"rootdett1f" => rootdett1ftest)
2800+
"rootdett1f" => rootdett1ftest,
2801+
"rootdett2" => rootdett2test)
27152802

27162803
@moitestset rootdett
27172804

27182805
const rootdetstests = Dict("rootdets1v" => rootdets1vtest,
2719-
"rootdets1f" => rootdets1ftest)
2806+
"rootdets1f" => rootdets1ftest,
2807+
"rootdets2" => rootdets2test)
27202808

27212809
@moitestset rootdets
27222810

0 commit comments

Comments
 (0)