Skip to content

Commit fc5336b

Browse files
authored
Add DualGeometricMeanCone and bridge (#3041)
1 parent 4144846 commit fc5336b

12 files changed

Lines changed: 632 additions & 8 deletions

File tree

docs/src/manual/standard_form.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ The vector-valued set types implemented in MathOptInterface.jl are:
7676
| [`ExponentialCone()`](@ref) | ``\{ (x,y,z) \in \mathbb{R}^3 : y \exp (x/y) \le z, y > 0 \}`` |
7777
| [`DualExponentialCone()`](@ref) | ``\{ (u,v,w) \in \mathbb{R}^3 : -u \exp (v/u) \le \exp(1) w, u < 0 \}`` |
7878
| [`GeometricMeanCone(d)`](@ref) | ``\{ (t,x) \in \mathbb{R}^{1+n} : x \ge 0, t \le \sqrt[n]{x_1 x_2 \cdots x_n} \}`` where ``n`` is ``d - 1`` |
79+
| [`DualGeometricMeanCone(d)`](@ref) | ``\{ (u,v) \in \mathbb{R}^{1+n} : v \ge 0, 0 \ge u \ge -n \sqrt[n]{\prod_i v_i} \}``, where ``n`` is ``d - 1`` |
7980
| [`PowerCone(α)`](@ref) | ``\{ (x,y,z) \in \mathbb{R}^3 : x^{\alpha} y^{1-\alpha} \ge \|z\|, x \ge 0,y \ge 0 \}`` |
8081
| [`DualPowerCone(α)`](@ref) | ``\{ (u,v,w) \in \mathbb{R}^3 : \left(\frac{u}{\alpha}\right)^{\alpha}\left(\frac{v}{1-\alpha}\right)^{1-\alpha} \ge \|w\|, u,v \ge 0 \}`` |
8182
| [`NormOneCone(d)`](@ref) | ``\{ (t,x) \in \mathbb{R}^{d} : t \ge \sum_i \lvert x_i \rvert \}`` |

docs/src/reference/standard_form.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ NormCone
8888
SecondOrderCone
8989
RotatedSecondOrderCone
9090
GeometricMeanCone
91+
DualGeometricMeanCone
9192
ExponentialCone
9293
DualExponentialCone
9394
PowerCone

src/Bridges/Constraint/Constraint.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ function add_all_bridges(model, ::Type{T}) where {T}
3939
MOI.Bridges.add_bridge(model, CountBelongsToMILPBridge{T})
4040
MOI.Bridges.add_bridge(model, CountDistinctToMILPBridge{T})
4141
MOI.Bridges.add_bridge(model, CountGreaterThanToMILPBridge{T})
42+
MOI.Bridges.add_bridge(model, DualGeoMeanBridge{T})
4243
# * ExponentialConeToScalarNonlinearFunctionBridge{T}
4344
# This bridge is not added by default because it starts with a convex
4445
# conic constraint and adds a nonlinear constraint that local NLP
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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+
DualGeoMeanBridge{T,G,H} <: Bridges.Constraint.AbstractBridge
9+
10+
`DualGeoMeanBridge` implements the following reformulation:
11+
12+
* ``(u, v) \\in DualGeometricMeanCone`` into
13+
``(-u/length(v), v) \\in GeometricMeanCone`` and ``u \\le 0``
14+
15+
## Source node
16+
17+
`DualGeoMeanBridge` supports:
18+
19+
* `H` in [`MOI.DualGeometricMeanCone`](@ref)
20+
21+
## Target nodes
22+
23+
`DualGeoMeanBridge` creates:
24+
25+
* `G` in [`MOI.GeometricMeanCone`](@ref)
26+
* `G` in [`MOI.Nonnegatives`](@ref)
27+
"""
28+
struct DualGeoMeanBridge{T,G,H} <: AbstractBridge
29+
nn_index::MOI.ConstraintIndex{G,MOI.Nonnegatives}
30+
geomean_index::MOI.ConstraintIndex{G,MOI.GeometricMeanCone}
31+
end
32+
33+
const DualGeoMean{T,OT<:MOI.ModelLike} =
34+
SingleBridgeOptimizer{DualGeoMeanBridge{T},OT}
35+
36+
function bridge_constraint(
37+
::Type{DualGeoMeanBridge{T,G,H}},
38+
model::MOI.ModelLike,
39+
f::H,
40+
s::MOI.DualGeometricMeanCone,
41+
) where {T,G,H}
42+
f_scalars = MOI.Utilities.eachscalar(f)
43+
u_neg = MOI.Utilities.vectorize([MOI.Utilities.operate(-, T, f_scalars[1])])
44+
nn_index = MOI.add_constraint(model, u_neg, MOI.Nonnegatives(1))
45+
u_n = MOI.Utilities.operate(/, T, f_scalars[1], -T(MOI.dimension(s) - 1))
46+
ci = MOI.add_constraint(
47+
model,
48+
MOI.Utilities.operate(vcat, T, u_n, f_scalars[2:end]),
49+
MOI.GeometricMeanCone(MOI.dimension(s)),
50+
)
51+
return DualGeoMeanBridge{T,G,H}(nn_index, ci)
52+
end
53+
54+
function MOI.supports_constraint(
55+
::Type{<:DualGeoMeanBridge{T}},
56+
::Type{<:MOI.AbstractVectorFunction},
57+
::Type{MOI.DualGeometricMeanCone},
58+
) where {T}
59+
return true
60+
end
61+
62+
function MOI.Bridges.added_constrained_variable_types(
63+
::Type{<:DualGeoMeanBridge},
64+
)
65+
return Tuple{Type}[]
66+
end
67+
68+
function MOI.Bridges.added_constraint_types(
69+
::Type{<:DualGeoMeanBridge{T,G}},
70+
) where {T,G}
71+
return Tuple{Type,Type}[(G, MOI.Nonnegatives), (G, MOI.GeometricMeanCone)]
72+
end
73+
74+
function concrete_bridge_type(
75+
::Type{<:DualGeoMeanBridge{T}},
76+
::Type{H},
77+
::Type{MOI.DualGeometricMeanCone},
78+
) where {T,H<:MOI.AbstractVectorFunction}
79+
S = MOI.Utilities.scalar_type(H)
80+
TS = MOI.Utilities.promote_operation(+, T, S, MOI.VariableIndex)
81+
G = MOI.Utilities.promote_operation(vcat, T, T, S, TS)
82+
return DualGeoMeanBridge{T,G,H}
83+
end
84+
85+
function MOI.get(
86+
::DualGeoMeanBridge{T,G},
87+
::MOI.NumberOfConstraints{G,MOI.Nonnegatives},
88+
)::Int64 where {T,G}
89+
return 1
90+
end
91+
92+
function MOI.get(
93+
bridge::DualGeoMeanBridge{T,G},
94+
::MOI.ListOfConstraintIndices{G,MOI.Nonnegatives},
95+
) where {T,G}
96+
return [bridge.nn_index]
97+
end
98+
99+
function MOI.get(
100+
::DualGeoMeanBridge{T,G},
101+
::MOI.NumberOfConstraints{G,MOI.GeometricMeanCone},
102+
)::Int64 where {T,G}
103+
return 1
104+
end
105+
106+
function MOI.get(
107+
bridge::DualGeoMeanBridge{T,G},
108+
::MOI.ListOfConstraintIndices{G,MOI.GeometricMeanCone},
109+
) where {T,G}
110+
return [bridge.geomean_index]
111+
end
112+
113+
function MOI.delete(model::MOI.ModelLike, bridge::DualGeoMeanBridge)
114+
MOI.delete(model, bridge.geomean_index)
115+
MOI.delete(model, bridge.nn_index)
116+
return
117+
end
118+
119+
function MOI.get(
120+
model::MOI.ModelLike,
121+
::MOI.ConstraintFunction,
122+
bridge::DualGeoMeanBridge{T,G,H},
123+
) where {T,G,H}
124+
g = MOI.get(model, MOI.ConstraintFunction(), bridge.geomean_index)
125+
scalars = MOI.Utilities.eachscalar(g)
126+
n = -T(length(scalars) - 1)
127+
u = MOI.Utilities.operate(*, T, scalars[1], n)
128+
f = MOI.Utilities.operate(vcat, T, u, scalars[2:end])
129+
return MOI.Utilities.convert_approx(H, f)
130+
end
131+
132+
function MOI.get(
133+
model::MOI.ModelLike,
134+
::MOI.ConstraintSet,
135+
bridge::DualGeoMeanBridge,
136+
)
137+
set = MOI.get(model, MOI.ConstraintSet(), bridge.geomean_index)
138+
return MOI.DualGeometricMeanCone(MOI.dimension(set))
139+
end
140+
141+
function MOI.get(
142+
model::MOI.ModelLike,
143+
attr::MOI.ConstraintPrimal,
144+
bridge::DualGeoMeanBridge,
145+
)
146+
primal = MOI.get(model, attr, bridge.geomean_index)
147+
primal[1] *= -(length(primal) - 1)
148+
return primal
149+
end
150+
151+
function MOI.get(
152+
model::MOI.ModelLike,
153+
attr::MOI.ConstraintDual,
154+
bridge::DualGeoMeanBridge,
155+
)
156+
dual = MOI.get(model, attr, bridge.geomean_index)
157+
dual[1] /= -(length(dual) - 1)
158+
return dual
159+
end

src/Test/test_basic_constraint.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ _set(::Type{MOI.NormCone}) = MOI.NormCone(4.0, 3)
115115
_set(::Type{MOI.SecondOrderCone}) = MOI.SecondOrderCone(3)
116116
_set(::Type{MOI.RotatedSecondOrderCone}) = MOI.RotatedSecondOrderCone(3)
117117
_set(::Type{MOI.GeometricMeanCone}) = MOI.GeometricMeanCone(3)
118+
_set(::Type{MOI.DualGeometricMeanCone}) = MOI.DualGeometricMeanCone(3)
118119
_set(::Type{MOI.ExponentialCone}) = MOI.ExponentialCone()
119120
_set(::Type{MOI.DualExponentialCone}) = MOI.DualExponentialCone()
120121
_set(::Type{T}, ::Type{MOI.PowerCone}) where {T} = MOI.PowerCone(T(1//2))
@@ -403,6 +404,7 @@ for s in [
403404
:SecondOrderCone,
404405
:RotatedSecondOrderCone,
405406
:GeometricMeanCone,
407+
:DualGeometricMeanCone,
406408
:ExponentialCone,
407409
:DualExponentialCone,
408410
:PowerCone,

src/Test/test_conic.jl

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3216,6 +3216,177 @@ function setup_test(
32163216
return
32173217
end
32183218

3219+
function _test_conic_DualGeometricMeanCone_helper(
3220+
model::MOI.ModelLike,
3221+
config::Config{T},
3222+
use_VectorOfVariables,
3223+
n = 3,
3224+
) where {T<:Real}
3225+
# Problem DualGeoMean1
3226+
# min -3(xyz)^(1/3)
3227+
# s.t.
3228+
# x + y + z ≤ 3
3229+
# in conic form:
3230+
# max t
3231+
# s.t.
3232+
# (t,x,y,z) ∈ DualGeometricMeanCone(4)
3233+
# x+y+z-3 ∈ LessThan(0.)
3234+
# By the arithmetic-geometric mean inequality,
3235+
# (xyz)^(1/3) ≤ (x+y+z)/3 = 1
3236+
# Therefore xyz ≤ 1
3237+
# This can be attained using x = y = z = 1 so it is optimal.
3238+
@requires MOI.supports_incremental_interface(model)
3239+
@requires MOI.supports(
3240+
model,
3241+
MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
3242+
)
3243+
@requires MOI.supports(model, MOI.ObjectiveSense())
3244+
if use_VectorOfVariables
3245+
@requires MOI.supports_constraint(
3246+
model,
3247+
MOI.VectorOfVariables,
3248+
MOI.DualGeometricMeanCone,
3249+
)
3250+
else
3251+
@requires MOI.supports_constraint(
3252+
model,
3253+
MOI.VectorAffineFunction{T},
3254+
MOI.DualGeometricMeanCone,
3255+
)
3256+
end
3257+
@requires MOI.supports_constraint(
3258+
model,
3259+
MOI.ScalarAffineFunction{T},
3260+
MOI.LessThan{T},
3261+
)
3262+
t = MOI.add_variable(model)
3263+
x = MOI.add_variables(model, n)
3264+
vov = MOI.VectorOfVariables([t; x])
3265+
if use_VectorOfVariables
3266+
gmc = MOI.add_constraint(model, vov, MOI.DualGeometricMeanCone(n + 1))
3267+
else
3268+
gmc = MOI.add_constraint(
3269+
model,
3270+
MOI.VectorAffineFunction{T}(vov),
3271+
MOI.DualGeometricMeanCone(n + 1),
3272+
)
3273+
end
3274+
c = MOI.add_constraint(
3275+
model,
3276+
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(T(1), x), T(0)),
3277+
MOI.LessThan(T(n)),
3278+
)
3279+
if _supports(config, MOI.NumberOfConstraints)
3280+
@test MOI.get(
3281+
model,
3282+
MOI.NumberOfConstraints{
3283+
use_VectorOfVariables ? MOI.VectorOfVariables :
3284+
MOI.VectorAffineFunction{T},
3285+
MOI.DualGeometricMeanCone,
3286+
}(),
3287+
) == 1
3288+
@test MOI.get(
3289+
model,
3290+
MOI.NumberOfConstraints{
3291+
MOI.ScalarAffineFunction{T},
3292+
MOI.LessThan{T},
3293+
}(),
3294+
) == 1
3295+
end
3296+
MOI.set(
3297+
model,
3298+
MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
3299+
MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(T(1), t)], T(0)),
3300+
)
3301+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
3302+
if _supports(config, MOI.optimize!)
3303+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED
3304+
MOI.optimize!(model)
3305+
@test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
3306+
@test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
3307+
@test (MOI.get(model, MOI.ObjectiveValue()), -3, config)
3308+
@test (MOI.get(model, MOI.VariablePrimal(), t), -3, config)
3309+
@test (MOI.get(model, MOI.VariablePrimal(), x), ones(T, n), config)
3310+
@test (
3311+
MOI.get(model, MOI.ConstraintPrimal(), gmc),
3312+
T[-3, 1, 1, 1],
3313+
config,
3314+
)
3315+
@test (MOI.get(model, MOI.ConstraintPrimal(), c), n, config)
3316+
if _supports(config, MOI.ConstraintDual)
3317+
@test (
3318+
MOI.get(model, MOI.ConstraintDual(), gmc),
3319+
ones(T, n + 1),
3320+
config,
3321+
)
3322+
@test (MOI.get(model, MOI.ConstraintDual(), c), -T(1), config)
3323+
end
3324+
end
3325+
return
3326+
end
3327+
3328+
function test_conic_DualGeometricMeanCone_VectorOfVariables(
3329+
model::MOI.ModelLike,
3330+
config::Config{T},
3331+
) where {T<:Real}
3332+
_test_conic_DualGeometricMeanCone_helper(model, config, true)
3333+
return
3334+
end
3335+
3336+
function version_added(
3337+
::typeof(test_conic_DualGeometricMeanCone_VectorOfVariables),
3338+
)
3339+
return v"1.53.0"
3340+
end
3341+
3342+
function setup_test(
3343+
::typeof(test_conic_DualGeometricMeanCone_VectorOfVariables),
3344+
model::MOIU.MockOptimizer,
3345+
::Config{T},
3346+
) where {T<:Real}
3347+
MOIU.set_mock_optimize!(
3348+
model,
3349+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(
3350+
mock,
3351+
T[-3, 1, 1, 1]::Vector{T},
3352+
(MOI.ScalarAffineFunction{T}, MOI.LessThan{T}) => [-T(1)],
3353+
),
3354+
)
3355+
return
3356+
end
3357+
3358+
function test_conic_DualGeometricMeanCone_VectorAffineFunction(
3359+
model::MOI.ModelLike,
3360+
config::Config{T},
3361+
) where {T<:Real}
3362+
_test_conic_DualGeometricMeanCone_helper(model, config, false)
3363+
return
3364+
end
3365+
3366+
function version_added(
3367+
::typeof(test_conic_DualGeometricMeanCone_VectorAffineFunction),
3368+
)
3369+
return v"1.53.0"
3370+
end
3371+
3372+
function setup_test(
3373+
::typeof(test_conic_DualGeometricMeanCone_VectorAffineFunction),
3374+
model::MOIU.MockOptimizer,
3375+
::Config{T},
3376+
) where {T<:Real}
3377+
MOIU.set_mock_optimize!(
3378+
model,
3379+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(
3380+
mock,
3381+
T[-3, 1, 1, 1]::Vector{T},
3382+
(MOI.ScalarAffineFunction{T}, MOI.LessThan{T}) => [-T(1)],
3383+
(MOI.VectorAffineFunction{T}, MOI.DualGeometricMeanCone) =>
3384+
[ones(T, 4)],
3385+
),
3386+
)
3387+
return
3388+
end
3389+
32193390
function _test_conic_Exponential_helper(
32203391
model::MOI.ModelLike,
32213392
config::Config{T},

0 commit comments

Comments
 (0)