Skip to content

Commit 5896072

Browse files
authored
Add (R)SOCtoNonConvexQuad Bridge (#1046)
add soc to nonconvex quad bridge
1 parent bbe04e7 commit 5896072

File tree

4 files changed

+325
-0
lines changed

4 files changed

+325
-0
lines changed

docs/src/apireference.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,41 @@ constraints of different types. There are two important concepts to distinguish:
555555
allow introspection into the bridge selection rationale of
556556
[`Bridges.LazyBridgeOptimizer`](@ref).
557557

558+
Most bridges are added by default in [`Bridges.full_bridge_optimizer`](@ref).
559+
However, for technical reasons, some bridges are not added by default, for instance:
560+
[`Bridges.Constraint.SOCtoPSDBridge`](@ref), [`Bridges.Constraint.SOCtoNonConvexQuadBridge`](@ref)
561+
and [`Bridges.Constraint.RSOCtoNonConvexQuadBridge`](@ref). See the docs of those bridges
562+
for more information.
563+
564+
It is possible to add those bridges and also user defined bridges,
565+
following one of the two methods. We present the examples for:
566+
[`Bridges.Constraint.SOCtoNonConvexQuadBridge`](@ref).
567+
568+
The first option is to add the specific bridges to a
569+
`bridged_model` optimizer, with coefficient type `T`. The `bridged_model`
570+
optimizer itself must have been constructed with a
571+
[`Bridges.LazyBridgeOptimizer`](@ref). Once such a optimizer is available, we
572+
can proceed using using [`Bridges.add_bridge`](@ref):
573+
574+
```julia
575+
MOIB.add_bridge(bridged_model, SOCtoNonConvexQuadBridge{T})
576+
```
577+
578+
Alternatively, it is possible to create a [`Bridges.Constraint.SingleBridgeOptimizer`](@ref)
579+
and wrap an existing `model` with it:
580+
581+
```julia
582+
const SOCtoNonConvexQuad{T, OT<:ModelLike} = Bridges.Constraint.SingleBridgeOptimizer{Bridges.Constraint.SOCtoNonConvexQuadBridge{T}, OT}
583+
bridged_model = SOCtoNonConvexQuad{Float64}(model)
584+
```
585+
586+
Those procedures could be applied to user define bridges. For the
587+
bridges defined in MathOptInterface, the [`Bridges.Constraint.SingleBridgeOptimizer`](@ref)'s are already created, therefore, for the case of [`Bridges.Constraint.SOCtoNonConvexQuadBridge`](@ref), one could simply use the existing optimizer:
588+
589+
```julia
590+
bridged_model = Bridges.Constraint.SOCtoNonConvexQuad{Float64}(model)
591+
```
592+
558593
```@docs
559594
Bridges.AbstractBridge
560595
Bridges.AbstractBridgeOptimizer
@@ -769,6 +804,8 @@ Bridges.Constraint.SplitIntervalBridge
769804
Bridges.Constraint.RSOCBridge
770805
Bridges.Constraint.SOCRBridge
771806
Bridges.Constraint.QuadtoSOCBridge
807+
Bridges.Constraint.SOCtoNonConvexQuadBridge
808+
Bridges.Constraint.RSOCtoNonConvexQuadBridge
772809
Bridges.Constraint.NormInfinityBridge
773810
Bridges.Constraint.NormOneBridge
774811
Bridges.Constraint.GeoMeanBridge

src/Bridges/Constraint/Constraint.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ include("interval.jl")
4141
const SplitInterval{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{SplitIntervalBridge{T}, OT}
4242
include("quad_to_soc.jl")
4343
const QuadtoSOC{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{QuadtoSOCBridge{T}, OT}
44+
include("soc_to_nonconvex_quad.jl") # do not add these bridges by default
45+
const SOCtoNonConvexQuad{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{SOCtoNonConvexQuadBridge{T}, OT}
46+
const RSOCtoNonConvexQuad{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{RSOCtoNonConvexQuadBridge{T}, OT}
4447
include("norm_to_lp.jl")
4548
const NormInfinity{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NormInfinityBridge{T}, OT}
4649
const NormOne{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NormOneBridge{T}, OT}
@@ -84,6 +87,9 @@ function add_all_bridges(bridged_model, ::Type{T}) where {T}
8487
MOIB.add_bridge(bridged_model, VectorFunctionizeBridge{T})
8588
MOIB.add_bridge(bridged_model, SplitIntervalBridge{T})
8689
MOIB.add_bridge(bridged_model, QuadtoSOCBridge{T})
90+
# We do not add `(R)SOCtoNonConvexQuad` because it starts with a convex
91+
# conic constraint and generate a non-convex constraint (in the QCP
92+
# interpretation).
8793
MOIB.add_bridge(bridged_model, NormInfinityBridge{T})
8894
MOIB.add_bridge(bridged_model, NormOneBridge{T})
8995
MOIB.add_bridge(bridged_model, GeoMeanBridge{T})
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
abstract type AbstractSOCtoNonConvexQuadBridge{T} <: AbstractBridge end
2+
3+
"""
4+
SOCtoNonConvexQuadBridge{T}
5+
6+
Constraints of the form `VectorOfVariables`-in-`SecondOrderCone` can be
7+
transformed into a `ScalarQuadraticFunction`-in-`LessThan` and a
8+
`ScalarAffineFunction`-in-`GreaterThan`. Indeed, the definition of the
9+
second-order cone
10+
```math
11+
t \\ge \\lVert x \\rVert_2 \\ (1)
12+
```
13+
is equivalent to
14+
```math
15+
\\sum x_i^2 \\le t^2 (2)
16+
```
17+
with ``t \\ge 0``. (3)
18+
19+
*WARNING* This transformation starts from a convex constraint (1) and creates a
20+
non-convex constraint (2), because the Q matrix associated with the constraint 2
21+
has one negative eigenvalue. This might be wrongly interpreted by a solver.
22+
Some solvers can look at (2) and understand that it is a second order cone, but
23+
this is not a general rule.
24+
For these reasons this bridge is not automatically added by [`MOI.Bridges.full_bridge_optimizer`](@ref).
25+
Care is recommended when adding this bridge to a optimizer.
26+
"""
27+
struct SOCtoNonConvexQuadBridge{T} <: AbstractSOCtoNonConvexQuadBridge{T}
28+
quad::CI{MOI.ScalarQuadraticFunction{T}, MOI.LessThan{T}}
29+
var_pos::Vector{CI{MOI.ScalarAffineFunction{T}, MOI.GreaterThan{T}}}
30+
vars::Vector{MOI.VariableIndex}
31+
end
32+
function bridge_constraint(::Type{SOCtoNonConvexQuadBridge{T}}, model,
33+
func::MOI.VectorOfVariables,
34+
set::MOI.SecondOrderCone) where T
35+
36+
vis = func.variables
37+
38+
t = vis[1]
39+
x = vis[2:end]
40+
a_terms = MOI.ScalarAffineTerm{T}[]
41+
q_terms = MOI.ScalarQuadraticTerm{T}[]
42+
push!(q_terms, MOI.ScalarQuadraticTerm(-T(2), t, t))
43+
for var in x
44+
push!(q_terms, MOI.ScalarQuadraticTerm(T(2), var, var))
45+
end
46+
47+
fq = MOI.ScalarQuadraticFunction(a_terms, q_terms, zero(T))
48+
quad = MOI.add_constraint(model, fq, MOI.LessThan(zero(T)))
49+
# ScalarAffineFunction's are added instead of SingleVariable's
50+
# because models can only have one SingleVariable per variable.
51+
# Hence, adding a SingleVariable constraint here could conflict with
52+
# a user defined SingleVariable
53+
fp = convert(MOI.ScalarAffineFunction{T}, MOI.SingleVariable(t))
54+
var_pos = MOI.add_constraint(model, fp, MOI.GreaterThan(zero(T)))
55+
56+
return SOCtoNonConvexQuadBridge(quad, [var_pos], vis)
57+
end
58+
59+
"""
60+
RSOCtoNonConvexQuadBridge{T}
61+
62+
Constraints of the form `VectorOfVariables`-in-`SecondOrderCone` can be
63+
transformed into a `ScalarQuadraticFunction`-in-`LessThan` and a
64+
`ScalarAffineFunction`-in-`GreaterThan`. Indeed, the definition of the
65+
second-order cone
66+
```math
67+
2tu \\ge \\lVert x \\rVert_2^2, t,u \\ge 0 (1)
68+
```
69+
is equivalent to
70+
```math
71+
\\sum x_i^2 \\le 2tu (2)
72+
```
73+
with ``t,u \\ge 0``. (3)
74+
75+
*WARNING* This transformation starts from a convex constraint (1) and creates a
76+
non-convex constraint (2), because the Q matrix associated with the constraint 2
77+
has two negative eigenvalues. This might be wrongly interpreted by a solver.
78+
Some solvers can look at (2) and understand that it is a rotated second order cone, but
79+
this is not a general rule.
80+
For these reasons, this bridge is not automatically added by [`MOI.Bridges.full_bridge_optimizer`](@ref).
81+
Care is recommended when adding this bridge to an optimizer.
82+
"""
83+
struct RSOCtoNonConvexQuadBridge{T} <: AbstractSOCtoNonConvexQuadBridge{T}
84+
quad::CI{MOI.ScalarQuadraticFunction{T}, MOI.LessThan{T}}
85+
var_pos::Vector{CI{MOI.ScalarAffineFunction{T}, MOI.GreaterThan{T}}}
86+
vars::Vector{MOI.VariableIndex}
87+
end
88+
function bridge_constraint(::Type{RSOCtoNonConvexQuadBridge{T}}, model,
89+
func::MOI.VectorOfVariables,
90+
set::MOI.RotatedSecondOrderCone) where T
91+
92+
vis = func.variables
93+
94+
t = vis[1]
95+
u = vis[2]
96+
x = vis[3:end]
97+
a_terms = MOI.ScalarAffineTerm{T}[]
98+
q_terms = MOI.ScalarQuadraticTerm{T}[]
99+
push!(q_terms, MOI.ScalarQuadraticTerm(-T(2), t, u))
100+
for var in x
101+
push!(q_terms, MOI.ScalarQuadraticTerm(T(2), var, var))
102+
end
103+
104+
fq = MOI.ScalarQuadraticFunction(a_terms, q_terms, zero(T))
105+
quad = MOI.add_constraint(model, fq, MOI.LessThan(zero(T)))
106+
# ScalarAffineFunction's are added instead of SingleVariable's
107+
# because models can only have one SingleVariable per variable.
108+
# Hence, adding a SingleVariable constraint here could conflict with
109+
# a user defined SingleVariable
110+
fp1 = convert(MOI.ScalarAffineFunction{T}, MOI.SingleVariable(t))
111+
var_pos1 = MOI.add_constraint(model, fp1, MOI.GreaterThan(zero(T)))
112+
fp2 = convert(MOI.ScalarAffineFunction{T}, MOI.SingleVariable(u))
113+
var_pos2 = MOI.add_constraint(model, fp2, MOI.GreaterThan(zero(T)))
114+
115+
return RSOCtoNonConvexQuadBridge(quad, [var_pos1, var_pos2], vis)
116+
end
117+
118+
function MOI.supports_constraint(::Type{SOCtoNonConvexQuadBridge{T}},
119+
::Type{MOI.VectorOfVariables},
120+
::Type{MOI.SecondOrderCone}) where T
121+
return true
122+
end
123+
function MOI.supports_constraint(::Type{RSOCtoNonConvexQuadBridge{T}},
124+
::Type{MOI.VectorOfVariables},
125+
::Type{MOI.RotatedSecondOrderCone}) where T
126+
return true
127+
end
128+
129+
MOIB.added_constrained_variable_types(::Type{<:AbstractSOCtoNonConvexQuadBridge}) = Tuple{DataType}[]
130+
function MOIB.added_constraint_types(::Type{<:AbstractSOCtoNonConvexQuadBridge{T}}) where T
131+
return [
132+
(MOI.ScalarQuadraticFunction{T}, MOI.LessThan{T}),
133+
(MOI.ScalarAffineFunction{T}, MOI.GreaterThan{T}),
134+
]
135+
end
136+
137+
function concrete_bridge_type(::Type{SOCtoNonConvexQuadBridge{T}},
138+
::Type{MOI.VectorOfVariables},
139+
::Type{MOI.SecondOrderCone}) where T
140+
return SOCtoNonConvexQuadBridge{T}
141+
end
142+
function concrete_bridge_type(::Type{RSOCtoNonConvexQuadBridge{T}},
143+
::Type{MOI.VectorOfVariables},
144+
::Type{MOI.RotatedSecondOrderCone}) where T
145+
return RSOCtoNonConvexQuadBridge{T}
146+
end
147+
148+
# Attributes, Bridge acting as a model
149+
function MOI.get(::AbstractSOCtoNonConvexQuadBridge{T},
150+
::MOI.NumberOfConstraints{MOI.ScalarQuadraticFunction{T},
151+
MOI.LessThan{T}}) where T
152+
return 1
153+
end
154+
155+
function MOI.get(bridge::AbstractSOCtoNonConvexQuadBridge{T},
156+
::MOI.ListOfConstraintIndices{
157+
MOI.ScalarQuadraticFunction{T},
158+
MOI.LessThan{T}}) where T
159+
return [bridge.quad]
160+
end
161+
162+
function MOI.get(bridge::AbstractSOCtoNonConvexQuadBridge{T},
163+
::MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},
164+
MOI.GreaterThan{T}}) where T
165+
return length(bridge.var_pos)
166+
end
167+
168+
function MOI.get(bridge::AbstractSOCtoNonConvexQuadBridge{T},
169+
::MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T},
170+
MOI.GreaterThan{T}}) where T
171+
return bridge.var_pos
172+
end
173+
174+
# References
175+
function MOI.delete(model::MOI.ModelLike, bridge::AbstractSOCtoNonConvexQuadBridge)
176+
MOI.delete(model, bridge.quad)
177+
MOI.delete.(model, bridge.var_pos)
178+
end
179+
180+
# Attributes, Bridge acting as a constraint
181+
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal,
182+
bridge::AbstractSOCtoNonConvexQuadBridge)
183+
vals = MOI.get.(model, MOI.VariablePrimal(attr.N), bridge.vars)
184+
return vals
185+
end
186+
187+
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet,
188+
b::SOCtoNonConvexQuadBridge{T}) where T
189+
return MOI.SecondOrderCone(length(b.vars))
190+
end
191+
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet,
192+
b::RSOCtoNonConvexQuadBridge{T}) where T
193+
return MOI.RotatedSecondOrderCone(length(b.vars))
194+
end
195+
196+
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction,
197+
b::AbstractSOCtoNonConvexQuadBridge{T}) where T
198+
return MOI.VectorOfVariables(b.vars)
199+
end
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using Test
2+
3+
using MathOptInterface
4+
const MOI = MathOptInterface
5+
const MOIT = MathOptInterface.Test
6+
const MOIU = MathOptInterface.Utilities
7+
const MOIB = MathOptInterface.Bridges
8+
const MOIBC = MathOptInterface.Bridges.Constraint
9+
10+
include("../utilities.jl")
11+
12+
mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}()))
13+
config = MOIT.TestConfig(duals = false)
14+
15+
@testset "RSOCtoNonConvexQuad" begin
16+
17+
@test MOIBC.RSOCtoNonConvexQuadBridge{Float64} == MOIBC.concrete_bridge_type(
18+
MOIBC.RSOCtoNonConvexQuadBridge{Float64},
19+
MOI.VectorOfVariables,
20+
MOI.RotatedSecondOrderCone)
21+
@test MOI.supports_constraint(MOIBC.RSOCtoNonConvexQuadBridge{Float64},
22+
MOI.VectorOfVariables,
23+
MOI.RotatedSecondOrderCone)
24+
@test !MOI.supports_constraint(MOIBC.RSOCtoNonConvexQuadBridge{Float64},
25+
MOI.ScalarAffineFunction{Float64},
26+
MOI.RotatedSecondOrderCone)
27+
28+
bridged_mock = MOIB.Constraint.RSOCtoNonConvexQuad{Float64}(mock)
29+
30+
MOIT.basic_constraint_tests(
31+
bridged_mock, config,
32+
include = [(F, S)
33+
for F in [MOI.VectorOfVariables]
34+
for S in [MOI.RotatedSecondOrderCone]])
35+
36+
mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.5, 1.0, 1/√2, 1/√2])
37+
MOIT.rotatedsoc1vtest(bridged_mock, config)
38+
39+
ci = first(MOI.get(bridged_mock,
40+
MOI.ListOfConstraintIndices{MOI.VectorOfVariables,
41+
MOI.RotatedSecondOrderCone}()))
42+
43+
test_delete_bridge(bridged_mock, ci, 4,
44+
(
45+
(MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}, 0),
46+
(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}, 0),
47+
))
48+
end
49+
50+
@testset "SOCtoNonConvexQuad" begin
51+
52+
@test MOIBC.SOCtoNonConvexQuadBridge{Float64} == MOIBC.concrete_bridge_type(
53+
MOIBC.SOCtoNonConvexQuadBridge{Float64},
54+
MOI.VectorOfVariables,
55+
MOI.SecondOrderCone)
56+
@test MOI.supports_constraint(MOIBC.SOCtoNonConvexQuadBridge{Float64},
57+
MOI.VectorOfVariables,
58+
MOI.SecondOrderCone)
59+
@test !MOI.supports_constraint(MOIBC.SOCtoNonConvexQuadBridge{Float64},
60+
MOI.ScalarAffineFunction{Float64},
61+
MOI.SecondOrderCone)
62+
63+
bridged_mock = MOIB.Constraint.SOCtoNonConvexQuad{Float64}(mock)
64+
65+
MOIT.basic_constraint_tests(
66+
bridged_mock, config,
67+
include = [(F, S)
68+
for F in [MOI.VectorOfVariables]
69+
for S in [MOI.SecondOrderCone]])
70+
71+
mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1/√2, 1/√2])
72+
MOIT.soc1vtest(bridged_mock, config)
73+
74+
ci = first(MOI.get(bridged_mock,
75+
MOI.ListOfConstraintIndices{MOI.VectorOfVariables,
76+
MOI.SecondOrderCone}()))
77+
78+
test_delete_bridge(bridged_mock, ci, 3,
79+
(
80+
(MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}, 0),
81+
(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}, 0),
82+
))
83+
end

0 commit comments

Comments
 (0)