Skip to content

Commit 27a9a83

Browse files
authored
[Bridges] add GeoMeanToPowerBridge (#1968)
1 parent ae0ad01 commit 27a9a83

File tree

4 files changed

+439
-0
lines changed

4 files changed

+439
-0
lines changed

docs/src/submodules/Bridges/list_of_bridges.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Bridges.Constraint.RSOCtoPSDBridge
4242
Bridges.Constraint.NormInfinityBridge
4343
Bridges.Constraint.NormOneBridge
4444
Bridges.Constraint.GeoMeantoRelEntrBridge
45+
Bridges.Constraint.GeoMeanToPowerBridge
4546
Bridges.Constraint.GeoMeanBridge
4647
Bridges.Constraint.RelativeEntropyBridge
4748
Bridges.Constraint.NormSpectralBridge

src/Bridges/Constraint/Constraint.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ include("bridges/count_greater_than.jl")
2929
include("bridges/det.jl")
3030
include("bridges/flip_sign.jl")
3131
include("bridges/functionize.jl")
32+
include("bridges/geomean_to_power.jl")
3233
include("bridges/geomean_to_relentr.jl")
3334
include("bridges/geomean.jl")
3435
include("bridges/indicator_activate_on_zero.jl")
@@ -88,6 +89,7 @@ function add_all_bridges(bridged_model, ::Type{T}) where {T}
8889
MOI.Bridges.add_bridge(bridged_model, NormOneBridge{T})
8990
MOI.Bridges.add_bridge(bridged_model, GeoMeantoRelEntrBridge{T})
9091
MOI.Bridges.add_bridge(bridged_model, GeoMeanBridge{T})
92+
MOI.Bridges.add_bridge(bridged_model, GeoMeanToPowerBridge{T})
9193
MOI.Bridges.add_bridge(bridged_model, RelativeEntropyBridge{T})
9294
MOI.Bridges.add_bridge(bridged_model, NormSpectralBridge{T})
9395
MOI.Bridges.add_bridge(bridged_model, NormNuclearBridge{T})
Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
# Copyright (c) 2017: Miles Lubin and contributors
2+
# Copyright (c) 2017: Google Inc.
3+
#
4+
# Use of this source code is governed by an MIT-style license that can be found
5+
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.
6+
7+
"""
8+
GeoMeanToPowerBridge{T,F} <: Bridges.Constraint.AbstractBridge
9+
10+
`GeoMeanToPowerBridge` implements the following reformulation:
11+
12+
* ``(y, x...) \\in GeometricMeanCone(1+d)`` into
13+
``(x_1, t, y) \\in PowerCone(1/d)`` and ``(t, x_2, ..., x_d) in GeometricMeanCone(d)``,
14+
which is then recursively expanded into more `PowerCone` constraints.
15+
16+
## Source node
17+
18+
`GeoMeanToPowerBridge` supports:
19+
20+
* `F` in [`MOI.GeometricMeanCone`](@ref)
21+
22+
## Target nodes
23+
24+
`GeoMeanToPowerBridge` creates:
25+
26+
* `F` in [`MOI.PowerCone{T}`](@ref)
27+
* [`MOI.VectorOfVariables`](@ref) in [`MOI.Nonnegatives`](@ref)
28+
"""
29+
struct GeoMeanToPowerBridge{T,F} <: AbstractBridge
30+
power::Vector{MOI.ConstraintIndex{F,MOI.PowerCone{T}}}
31+
t::Vector{MOI.VariableIndex}
32+
nn::Union{
33+
Nothing,
34+
MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.Nonnegatives},
35+
}
36+
dimension::Int
37+
end
38+
39+
const GeoMeanToPower{T,OT<:MOI.ModelLike} =
40+
SingleBridgeOptimizer{GeoMeanToPowerBridge{T},OT}
41+
42+
function bridge_constraint(
43+
::Type{GeoMeanToPowerBridge{T,F}},
44+
model::MOI.ModelLike,
45+
f::F,
46+
s::MOI.GeometricMeanCone,
47+
) where {T,F}
48+
d = MOI.dimension(s)
49+
fi_s = MOI.Utilities.eachscalar(f)
50+
if d == 2
51+
# We could do something cleverer here, but this is a weird constraint,
52+
# and we don't want to overcomplicate the bridge.
53+
# t <= 1√(x_1) <=> t <= √(x_1 * x_1)
54+
ci = MOI.add_constraint(model, fi_s[[2, 2, 1]], MOI.PowerCone(T(1 / 2)))
55+
return GeoMeanToPowerBridge{T,F}([ci], MOI.VariableIndex[], nothing, d)
56+
elseif d == 3
57+
# t <= √(x_1 * x_2)
58+
ci = MOI.add_constraint(model, fi_s[[2, 3, 1]], MOI.PowerCone(T(1 / 2)))
59+
return GeoMeanToPowerBridge{T,F}([ci], MOI.VariableIndex[], nothing, d)
60+
else
61+
ts, nn = MOI.add_constrained_variables(model, MOI.Nonnegatives(d - 3))
62+
cis = MOI.ConstraintIndex{F,MOI.PowerCone{T}}[]
63+
z, x, n = fi_s[1], fi_s[2], d - 1
64+
for i in 1:(d-3)
65+
y = ts[i]
66+
fi = MOI.Utilities.operate(vcat, T, x, y, z)
67+
push!(cis, MOI.add_constraint(model, fi, MOI.PowerCone(T(1 / n))))
68+
n -= 1
69+
x, z = fi_s[2+i], y
70+
end
71+
fi = MOI.Utilities.operate(vcat, T, fi_s[end-1], fi_s[end], ts[end])
72+
push!(cis, MOI.add_constraint(model, fi, MOI.PowerCone(T(1 / 2))))
73+
return GeoMeanToPowerBridge{T,F}(cis, ts, nn, d)
74+
end
75+
end
76+
77+
function MOI.supports_constraint(
78+
::Type{<:GeoMeanToPowerBridge{T}},
79+
::Type{<:MOI.AbstractVectorFunction},
80+
::Type{MOI.GeometricMeanCone},
81+
) where {T}
82+
return true
83+
end
84+
85+
function MOI.Bridges.added_constrained_variable_types(
86+
::Type{<:GeoMeanToPowerBridge},
87+
)
88+
return Tuple{Type}[(MOI.Nonnegatives,)]
89+
end
90+
91+
function MOI.Bridges.added_constraint_types(
92+
::Type{<:GeoMeanToPowerBridge{T,F}},
93+
) where {T,F}
94+
return Tuple{Type,Type}[(F, MOI.PowerCone{T})]
95+
end
96+
97+
function concrete_bridge_type(
98+
::Type{<:GeoMeanToPowerBridge{T}},
99+
F::Type{<:MOI.AbstractVectorFunction},
100+
::Type{MOI.GeometricMeanCone},
101+
) where {T}
102+
return GeoMeanToPowerBridge{T,F}
103+
end
104+
105+
function MOI.get(bridge::GeoMeanToPowerBridge, ::MOI.NumberOfVariables)::Int64
106+
return length(bridge.t)
107+
end
108+
109+
function MOI.get(bridge::GeoMeanToPowerBridge, ::MOI.ListOfVariableIndices)
110+
return copy(bridge.t)
111+
end
112+
113+
function MOI.get(
114+
bridge::GeoMeanToPowerBridge,
115+
::MOI.NumberOfConstraints{MOI.VectorOfVariables,MOI.Nonnegatives},
116+
)::Int64
117+
if bridge.nn === nothing
118+
return 0
119+
end
120+
return 1
121+
end
122+
123+
function MOI.get(
124+
bridge::GeoMeanToPowerBridge,
125+
::MOI.ListOfConstraintIndices{MOI.VectorOfVariables,MOI.Nonnegatives},
126+
)
127+
if bridge.nn === nothing
128+
return MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.Nonnegatives}[]
129+
end
130+
return [bridge.nn]
131+
end
132+
133+
function MOI.get(
134+
bridge::GeoMeanToPowerBridge{T,F},
135+
::MOI.NumberOfConstraints{F,MOI.PowerCone{T}},
136+
)::Int64 where {T,F}
137+
return length(bridge.power)
138+
end
139+
140+
function MOI.get(
141+
bridge::GeoMeanToPowerBridge{T,F},
142+
::MOI.ListOfConstraintIndices{F,MOI.PowerCone{T}},
143+
) where {T,F}
144+
return copy(bridge.power)
145+
end
146+
147+
function MOI.delete(model::MOI.ModelLike, bridge::GeoMeanToPowerBridge)
148+
MOI.delete(model, bridge.power)
149+
if bridge.nn !== nothing
150+
MOI.delete(model, bridge.nn)
151+
MOI.delete(model, bridge.t)
152+
end
153+
return
154+
end
155+
156+
function MOI.get(
157+
model::MOI.ModelLike,
158+
::MOI.ConstraintFunction,
159+
bridge::GeoMeanToPowerBridge{T,F},
160+
) where {T,F}
161+
f = MOI.get(model, MOI.ConstraintFunction(), bridge.power[1])
162+
fi_s = MOI.Utilities.eachscalar(f)
163+
if bridge.dimension == 2
164+
return fi_s[[3, 1]]
165+
elseif bridge.dimension == 3
166+
return fi_s[[3, 1, 2]]
167+
end
168+
g = fi_s[[3, 1]]
169+
for i in 2:(length(bridge.power)-1)
170+
fi = MOI.get(model, MOI.ConstraintFunction(), bridge.power[i])
171+
fi_s = first(MOI.Utilities.eachscalar(fi))
172+
g = MOI.Utilities.operate(vcat, T, g, fi_s)
173+
end
174+
fi = MOI.get(model, MOI.ConstraintFunction(), bridge.power[end])
175+
fi_s = MOI.Utilities.eachscalar(fi)
176+
return MOI.Utilities.operate(vcat, T, g, fi_s[1:2])
177+
end
178+
179+
function MOI.get(
180+
::MOI.ModelLike,
181+
::MOI.ConstraintSet,
182+
bridge::GeoMeanToPowerBridge,
183+
)
184+
return MOI.GeometricMeanCone(bridge.dimension)
185+
end
186+
187+
function MOI.supports(
188+
model::MOI.ModelLike,
189+
attr::MOI.ConstraintPrimalStart,
190+
::Type{GeoMeanToPowerBridge{T,F}},
191+
) where {T,F}
192+
return MOI.supports(model, attr, MOI.ConstraintIndex{F,MOI.PowerCone{T}})
193+
end
194+
195+
function MOI.get(
196+
model::MOI.ModelLike,
197+
attr::Union{MOI.ConstraintPrimalStart,MOI.ConstraintPrimal},
198+
bridge::GeoMeanToPowerBridge{T,F},
199+
) where {T,F}
200+
fi_s = MOI.get(model, attr, bridge.power[1])
201+
if fi_s === nothing
202+
return nothing
203+
end
204+
if bridge.dimension == 2
205+
return fi_s[[3, 1]]
206+
elseif bridge.dimension == 3
207+
return fi_s[[3, 1, 2]]
208+
end
209+
g = fi_s[[3, 1]]
210+
for i in 2:(length(bridge.power)-1)
211+
fi_s = MOI.get(model, attr, bridge.power[i])
212+
push!(g, fi_s[1])
213+
end
214+
fi_s = MOI.get(model, attr, bridge.power[end])
215+
append!(g, fi_s[1:2])
216+
return g
217+
end
218+
219+
function MOI.set(
220+
model::MOI.ModelLike,
221+
attr::MOI.ConstraintPrimalStart,
222+
bridge::GeoMeanToPowerBridge{T,F},
223+
start::AbstractVector{T},
224+
) where {T,F}
225+
if bridge.dimension == 2
226+
MOI.set(model, attr, bridge.power[1], start[[2, 2, 1]])
227+
return
228+
elseif bridge.dimension == 3
229+
MOI.set(model, attr, bridge.power[1], start[[2, 3, 1]])
230+
return
231+
end
232+
# [x, y, z] in PowerCone(a)
233+
# ⟺ x^a * y^(1-a) >= z
234+
# ⟺ y = (z / x^a)^(1 / (1-a))
235+
z, x = start[1:2]
236+
y = zero(T)
237+
for i in 1:(length(bridge.power)-1)
238+
ci = bridge.power[i]
239+
set = MOI.get(model, MOI.ConstraintSet(), ci)
240+
y = (z / x^set.exponent)^inv(1 - set.exponent)
241+
MOI.set(model, attr, ci, [x, y, z])
242+
z, x = y, start[i+2]
243+
end
244+
MOI.set(model, attr, bridge.power[end], [start[end-1], start[end], y])
245+
return
246+
end
247+
248+
function MOI.set(
249+
model::MOI.ModelLike,
250+
attr::MOI.ConstraintPrimalStart,
251+
bridge::GeoMeanToPowerBridge,
252+
::Nothing,
253+
)
254+
for ci in bridge.power
255+
MOI.set(model, attr, ci, nothing)
256+
end
257+
return
258+
end
259+
260+
function MOI.supports(
261+
model::MOI.ModelLike,
262+
attr::MOI.ConstraintDualStart,
263+
::Type{GeoMeanToPowerBridge{T,F}},
264+
) where {T,F}
265+
return MOI.supports(model, attr, MOI.ConstraintIndex{F,MOI.PowerCone{T}})
266+
end
267+
268+
function MOI.get(
269+
model::MOI.ModelLike,
270+
attr::Union{MOI.ConstraintDualStart,MOI.ConstraintDual},
271+
bridge::GeoMeanToPowerBridge{T,F},
272+
) where {T,F}
273+
fi_s = MOI.get(model, attr, bridge.power[1])
274+
if fi_s === nothing
275+
return nothing
276+
end
277+
if bridge.dimension == 2
278+
# Power constraint is [x, x, t] in PowerCone(0.5), so we need to sum the
279+
# first two elements to get the dual of x.
280+
return [fi_s[3], fi_s[1] + fi_s[2]]
281+
elseif bridge.dimension == 3
282+
return fi_s[[3, 1, 2]]
283+
end
284+
g = fi_s[[3, 1]]
285+
for i in 2:(length(bridge.power)-1)
286+
fi_s = MOI.get(model, attr, bridge.power[i])
287+
push!(g, fi_s[1])
288+
end
289+
fi_s = MOI.get(model, attr, bridge.power[end])
290+
append!(g, fi_s[1:2])
291+
return g
292+
end
293+
294+
function MOI.set(
295+
model::MOI.ModelLike,
296+
attr::MOI.ConstraintDualStart,
297+
bridge::GeoMeanToPowerBridge{T,F},
298+
start::AbstractVector{T},
299+
) where {T,F}
300+
if bridge.dimension == 2
301+
new_start = [start[2] / 2, start[2] / 2, start[1]]
302+
MOI.set(model, attr, bridge.power[1], new_start)
303+
return
304+
elseif bridge.dimension == 3
305+
MOI.set(model, attr, bridge.power[1], start[[2, 3, 1]])
306+
return
307+
end
308+
# [x, y, z] in PowerCone(a)
309+
# [u, v, w] in PowerCone*(a)
310+
# ⟺ (u/a)^a * (v / (1-a))^(1-a) >= |w|
311+
# ⟺ (v / (1-a))^(1-a) >= |w| / (u/a)^a
312+
# ⟺ v / (1-a) >= (|w| / (u/a)^a)^(1/(1-a))
313+
# ⟺ v = (1-a) * (|w| / (u/a)^a)^(1/(1-a))
314+
w, u = start[1:2]
315+
v = zero(T)
316+
for i in 1:(length(bridge.power)-1)
317+
ci = bridge.power[i]
318+
set = MOI.get(model, MOI.ConstraintSet(), ci)
319+
a = set.exponent
320+
v = (1 - a) * (abs(w) / (u / a)^a)^inv(1 - a)
321+
MOI.set(model, attr, ci, [u, v, w])
322+
w, u = v, start[i+2]
323+
end
324+
MOI.set(model, attr, bridge.power[end], [start[end-1], start[end], v])
325+
return
326+
end
327+
328+
function MOI.set(
329+
model::MOI.ModelLike,
330+
attr::MOI.ConstraintDualStart,
331+
bridge::GeoMeanToPowerBridge,
332+
::Nothing,
333+
)
334+
for ci in bridge.power
335+
MOI.set(model, attr, ci, nothing)
336+
end
337+
return
338+
end

0 commit comments

Comments
 (0)