Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/src/manual/standard_form.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ The vector-valued set types implemented in MathOptInterface.jl are:
| [`ExponentialCone()`](@ref) | ``\{ (x,y,z) \in \mathbb{R}^3 : y \exp (x/y) \le z, y > 0 \}`` |
| [`DualExponentialCone()`](@ref) | ``\{ (u,v,w) \in \mathbb{R}^3 : -u \exp (v/u) \le \exp(1) w, u < 0 \}`` |
| [`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`` |
| [`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`` |
| [`PowerCone(α)`](@ref) | ``\{ (x,y,z) \in \mathbb{R}^3 : x^{\alpha} y^{1-\alpha} \ge \|z\|, x \ge 0,y \ge 0 \}`` |
| [`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 \}`` |
| [`NormOneCone(d)`](@ref) | ``\{ (t,x) \in \mathbb{R}^{d} : t \ge \sum_i \lvert x_i \rvert \}`` |
Expand Down
1 change: 1 addition & 0 deletions docs/src/reference/standard_form.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ NormCone
SecondOrderCone
RotatedSecondOrderCone
GeometricMeanCone
DualGeometricMeanCone
ExponentialCone
DualExponentialCone
PowerCone
Expand Down
1 change: 1 addition & 0 deletions src/Bridges/Constraint/Constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ function add_all_bridges(model, ::Type{T}) where {T}
MOI.Bridges.add_bridge(model, CountBelongsToMILPBridge{T})
MOI.Bridges.add_bridge(model, CountDistinctToMILPBridge{T})
MOI.Bridges.add_bridge(model, CountGreaterThanToMILPBridge{T})
MOI.Bridges.add_bridge(model, DualGeoMeanBridge{T})
# * ExponentialConeToScalarNonlinearFunctionBridge{T}
# This bridge is not added by default because it starts with a convex
# conic constraint and adds a nonlinear constraint that local NLP
Expand Down
160 changes: 160 additions & 0 deletions src/Bridges/Constraint/bridges/DualGeoMeanBridge.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# Copyright (c) 2017: Miles Lubin and contributors
# Copyright (c) 2017: Google Inc.
#
# Use of this source code is governed by an MIT-style license that can be found
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.

"""
DualGeoMeanBridge{T,G,H} <: Bridges.Constraint.AbstractBridge

`DualGeoMeanBridge` implements the following reformulation:

* ``(u, v) \\in DualGeometricMeanCone`` into
``(-u/length(v), v) \\in GeometricMeanCone`` and ``u \\le 0``

## Source node

`DualGeoMeanBridge` supports:

* `H` in [`MOI.DualGeometricMeanCone`](@ref)

## Target nodes

`DualGeoMeanBridge` creates:

* `G` in [`MOI.GeometricMeanCone`](@ref)
* `G` in [`MOI.Nonnegatives`](@ref)
"""
struct DualGeoMeanBridge{T,G,H} <: AbstractBridge
nn_index::MOI.ConstraintIndex{G,MOI.Nonnegatives}
geomean_index::MOI.ConstraintIndex{G,MOI.GeometricMeanCone}
end

const DualGeoMean{T,OT<:MOI.ModelLike} =
SingleBridgeOptimizer{DualGeoMeanBridge{T},OT}

function bridge_constraint(
::Type{DualGeoMeanBridge{T,G,H}},
model::MOI.ModelLike,
f::H,
s::MOI.DualGeometricMeanCone,
) where {T,G,H}
f_scalars = MOI.Utilities.eachscalar(f)
nn_index = MOI.add_constraint(
model,
MOI.Utilities.vectorize([MOI.Utilities.operate(-, T, f_scalars[1])]),
MOI.Nonnegatives(1),
)
geomean_func = MOI.Utilities.operate(
vcat,
T,
MOI.Utilities.operate(/, T, f_scalars[1], -T(MOI.dimension(s) - 1)),
f_scalars[2:end],
)
geomean_index = MOI.add_constraint(
model,
geomean_func,
MOI.GeometricMeanCone(MOI.dimension(s)),
)
return DualGeoMeanBridge{T,G,H}(nn_index, geomean_index)
end

function MOI.supports_constraint(
::Type{<:DualGeoMeanBridge{T}},
::Type{<:MOI.AbstractVectorFunction},
::Type{MOI.DualGeometricMeanCone},
) where {T}
return true
end

function MOI.Bridges.added_constrained_variable_types(
::Type{<:DualGeoMeanBridge},
)
return Tuple{Type}[]
end

function MOI.Bridges.added_constraint_types(
::Type{<:DualGeoMeanBridge{T,G}},
) where {T,G}
return Tuple{Type,Type,Type}[(G, MOI.Nonnegatives, MOI.GeometricMeanCone)]
end

function concrete_bridge_type(
::Type{<:DualGeoMeanBridge{T}},
H::Type{<:MOI.AbstractVectorFunction},
::Type{MOI.DualGeometricMeanCone},
) where {T}
S = MOI.Utilities.scalar_type(H)
G = MOI.Utilities.promote_operation(
vcat,
T,
T,
S,
MOI.Utilities.promote_operation(+, T, S, MOI.VariableIndex),
)
return DualGeoMeanBridge{T,G,H}
end

MOI.get(::DualGeoMeanBridge, ::MOI.NumberOfVariables)::Int64 = 0

function MOI.get(
::DualGeoMeanBridge{T,G},
::MOI.NumberOfConstraints{G,MOI.Nonnegatives},
)::Int64 where {T,G}
return 1
end

function MOI.get(
::DualGeoMeanBridge{T,G},
::MOI.NumberOfConstraints{G,MOI.GeometricMeanCone},
)::Int64 where {T,G}
return 1
end

function MOI.get(
bridge::DualGeoMeanBridge{T,G},
::MOI.ListOfConstraintIndices{G,MOI.Nonnegatives},
) where {T,G}
return [bridge.nn_index]
end

function MOI.get(
bridge::DualGeoMeanBridge{T,G},
::MOI.ListOfConstraintIndices{G,MOI.GeometricMeanCone},
) where {T,G}
return [bridge.geomean_index]
end

function MOI.delete(model::MOI.ModelLike, bridge::DualGeoMeanBridge)
MOI.delete(model, bridge.geomean_index)
MOI.delete(model, bridge.nn_index)
return
end

function MOI.get(
model::MOI.ModelLike,
::MOI.ConstraintFunction,
bridge::DualGeoMeanBridge{T,G,H},
) where {T,G,H}
geomean_func = MOI.Utilities.eachscalar(
MOI.get(model, MOI.ConstraintFunction(), bridge.geomean_index),
)
d = length(geomean_func) - 1
u = MOI.Utilities.operate(*, T, geomean_func[1], T(-d))
return MOI.Utilities.convert_approx(
H,
MOI.Utilities.operate(vcat, T, u, geomean_func[2:end]),
)
end

function MOI.get(
model::MOI.ModelLike,
::MOI.ConstraintSet,
bridge::DualGeoMeanBridge,
)
return MOI.DualGeometricMeanCone(
MOI.dimension(
MOI.get(model, MOI.ConstraintSet(), bridge.geomean_index),
),
)
end
2 changes: 2 additions & 0 deletions src/Test/test_basic_constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ _set(::Type{MOI.NormCone}) = MOI.NormCone(4.0, 3)
_set(::Type{MOI.SecondOrderCone}) = MOI.SecondOrderCone(3)
_set(::Type{MOI.RotatedSecondOrderCone}) = MOI.RotatedSecondOrderCone(3)
_set(::Type{MOI.GeometricMeanCone}) = MOI.GeometricMeanCone(3)
_set(::Type{MOI.DualGeometricMeanCone}) = MOI.DualGeometricMeanCone(3)
_set(::Type{MOI.ExponentialCone}) = MOI.ExponentialCone()
_set(::Type{MOI.DualExponentialCone}) = MOI.DualExponentialCone()
_set(::Type{T}, ::Type{MOI.PowerCone}) where {T} = MOI.PowerCone(T(1//2))
Expand Down Expand Up @@ -403,6 +404,7 @@ for s in [
:SecondOrderCone,
:RotatedSecondOrderCone,
:GeometricMeanCone,
:DualGeometricMeanCone,
:ExponentialCone,
:DualExponentialCone,
:PowerCone,
Expand Down
159 changes: 159 additions & 0 deletions src/Test/test_conic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3216,6 +3216,165 @@ function setup_test(
return
end

function _test_conic_DualGeometricMeanCone_helper(
model::MOI.ModelLike,
config::Config{T},
use_VectorOfVariables,
n = 3,
) where {T<:Real}
# Problem DualGeoMean1
# min -3(xyz)^(1/3)
# s.t.
# x + y + z ≤ 3
# in conic form:
# max t
# s.t.
# (t,x,y,z) ∈ DualGeometricMeanCone(4)
# x+y+z-3 ∈ LessThan(0.)
# By the arithmetic-geometric mean inequality,
# (xyz)^(1/3) ≤ (x+y+z)/3 = 1
# Therefore xyz ≤ 1
# This can be attained using x = y = z = 1 so it is optimal.
@requires MOI.supports_incremental_interface(model)
@requires MOI.supports(
model,
MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
)
@requires MOI.supports(model, MOI.ObjectiveSense())
if use_VectorOfVariables
@requires MOI.supports_constraint(
model,
MOI.VectorOfVariables,
MOI.DualGeometricMeanCone,
)
else
@requires MOI.supports_constraint(
model,
MOI.VectorAffineFunction{T},
MOI.DualGeometricMeanCone,
)
end
@requires MOI.supports_constraint(
model,
MOI.ScalarAffineFunction{T},
MOI.LessThan{T},
)
t = MOI.add_variable(model)
x = MOI.add_variables(model, n)
vov = MOI.VectorOfVariables([t; x])
if use_VectorOfVariables
gmc = MOI.add_constraint(model, vov, MOI.DualGeometricMeanCone(n + 1))
else
gmc = MOI.add_constraint(
model,
MOI.VectorAffineFunction{T}(vov),
MOI.DualGeometricMeanCone(n + 1),
)
end
c = MOI.add_constraint(
model,
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(T(1), x), T(0)),
MOI.LessThan(T(n)),
)
if _supports(config, MOI.NumberOfConstraints)
@test MOI.get(
model,
MOI.NumberOfConstraints{
use_VectorOfVariables ? MOI.VectorOfVariables :
MOI.VectorAffineFunction{T},
MOI.DualGeometricMeanCone,
}(),
) == 1
@test MOI.get(
model,
MOI.NumberOfConstraints{
MOI.ScalarAffineFunction{T},
MOI.LessThan{T},
}(),
) == 1
end
MOI.set(
model,
MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(T(1), t)], T(0)),
)
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
if _supports(config, MOI.optimize!)
@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED
MOI.optimize!(model)
@test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
@test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
@test ≈(MOI.get(model, MOI.ObjectiveValue()), -3, config)
@test ≈(MOI.get(model, MOI.VariablePrimal(), t), -3, config)
@test ≈(MOI.get(model, MOI.VariablePrimal(), x), ones(T, n), config)
@test ≈(
MOI.get(model, MOI.ConstraintPrimal(), gmc),
T[-3, 1, 1, 1],
config,
)
@test ≈(MOI.get(model, MOI.ConstraintPrimal(), c), n, config)
if _supports(config, MOI.ConstraintDual)
@test ≈(
MOI.get(model, MOI.ConstraintDual(), gmc),
ones(T, n + 1),
config,
)
@test ≈(MOI.get(model, MOI.ConstraintDual(), c), -T(1), config)
end
end
return
end

function test_conic_DualGeometricMeanCone_VectorOfVariables(
model::MOI.ModelLike,
config::Config{T},
) where {T<:Real}
_test_conic_DualGeometricMeanCone_helper(model, config, true)
return
end

function setup_test(
::typeof(test_conic_DualGeometricMeanCone_VectorOfVariables),
model::MOIU.MockOptimizer,
::Config{T},
) where {T<:Real}
MOIU.set_mock_optimize!(
model,
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(
mock,
T[-3, 1, 1, 1]::Vector{T},
(MOI.ScalarAffineFunction{T}, MOI.LessThan{T}) => [-T(1)],
),
)
return
end

function test_conic_DualGeometricMeanCone_VectorAffineFunction(
model::MOI.ModelLike,
config::Config{T},
) where {T<:Real}
_test_conic_DualGeometricMeanCone_helper(model, config, false)
return
end

function setup_test(
::typeof(test_conic_DualGeometricMeanCone_VectorAffineFunction),
model::MOIU.MockOptimizer,
::Config{T},
) where {T<:Real}
MOIU.set_mock_optimize!(
model,
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(
mock,
T[-3, 1, 1, 1]::Vector{T},
(MOI.ScalarAffineFunction{T}, MOI.LessThan{T}) => [-T(1)],
(MOI.VectorAffineFunction{T}, MOI.DualGeometricMeanCone) =>
[ones(T, 4)],
),
)
return
end

function _test_conic_Exponential_helper(
model::MOI.ModelLike,
config::Config{T},
Expand Down
Loading
Loading