Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NormCone for representing the epigraph of a p-norm #2119

Merged
merged 7 commits into from
May 3, 2023
Merged
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 @@ -79,6 +79,7 @@ The vector-valued set types implemented in MathOptInterface.jl are:
| [`NormInfinityCone(d)`](@ref MathOptInterface.NormInfinityCone) | ``\{ (t,x) \in \mathbb{R}^{d} : t \ge \max_i \lvert x_i \rvert \}`` |
| [`RelativeEntropyCone(d)`](@ref MathOptInterface.RelativeEntropyCone) | ``\{ (u, v, w) \in \mathbb{R}^{d} : u \ge \sum_i w_i \log (\frac{w_i}{v_i}), v_i \ge 0, w_i \ge 0 \}`` |
| [`HyperRectangle(l, u)`](@ref MathOptInterface.HyperRectangle) | ``\{x \in \bar{\mathbb{R}}^d: x_i \in [l_i, u_i] \forall i=1,\ldots,d\}`` |
| [`NormCone(p, d)`](@ref MathOptInterface.NormCone) | ``\{ (t,x) \in \mathbb{R}^{d} : t \ge \left(\sum\limits_i |x_i|^p\right)^{\frac{1}{p}} \}`` |

## Matrix cones

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 @@ -82,6 +82,7 @@ Nonnegatives
Nonpositives
NormInfinityCone
NormOneCone
NormCone
SecondOrderCone
RotatedSecondOrderCone
GeometricMeanCone
Expand Down
4 changes: 4 additions & 0 deletions docs/src/submodules/Bridges/list_of_bridges.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ Bridges.Constraint.SOCtoPSDBridge
Bridges.Constraint.RSOCtoPSDBridge
Bridges.Constraint.NormInfinityBridge
Bridges.Constraint.NormOneBridge
Bridges.Constraint.NormToPowerBridge
Bridges.Constraint.NormOneConeToNormConeBridge
Bridges.Constraint.SecondOrderConeToNormConeBridge
Bridges.Constraint.NormInfinityConeToNormConeBridge
Bridges.Constraint.GeoMeantoRelEntrBridge
Bridges.Constraint.GeoMeanToPowerBridge
Bridges.Constraint.GeoMeanBridge
Expand Down
6 changes: 6 additions & 0 deletions src/Bridges/Constraint/Constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ include("bridges/interval.jl")
include("bridges/ltgt_to_interval.jl")
include("bridges/norm_infinity.jl")
include("bridges/norm_one.jl")
include("bridges/norm_to_power.jl")
include("bridges/norm_special_case.jl")
include("bridges/norm_spec_nuc_to_psd.jl")
include("bridges/number_conversion.jl")
include("bridges/quad_to_soc.jl")
Expand Down Expand Up @@ -93,6 +95,10 @@ function add_all_bridges(bridged_model, ::Type{T}) where {T}
MOI.Bridges.add_bridge(bridged_model, GeoMeantoRelEntrBridge{T})
MOI.Bridges.add_bridge(bridged_model, GeoMeanBridge{T})
MOI.Bridges.add_bridge(bridged_model, GeoMeanToPowerBridge{T})
MOI.Bridges.add_bridge(bridged_model, NormToPowerBridge{T})
MOI.Bridges.add_bridge(bridged_model, NormOneConeToNormConeBridge{T})
MOI.Bridges.add_bridge(bridged_model, SecondOrderConeToNormConeBridge{T})
MOI.Bridges.add_bridge(bridged_model, NormInfinityConeToNormConeBridge{T})
MOI.Bridges.add_bridge(bridged_model, RelativeEntropyBridge{T})
MOI.Bridges.add_bridge(bridged_model, NormSpectralBridge{T})
MOI.Bridges.add_bridge(bridged_model, NormNuclearBridge{T})
Expand Down
114 changes: 114 additions & 0 deletions src/Bridges/Constraint/bridges/norm_special_case.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# 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.

struct NormSpecialCaseBridge{S,T,F} <: SetMapBridge{T,MOI.NormCone,S,F,F}
constraint::MOI.ConstraintIndex{F,MOI.NormCone}
end

MOI.Bridges.map_function(::Type{<:NormSpecialCaseBridge}, f) = f

MOI.Bridges.inverse_map_function(::Type{<:NormSpecialCaseBridge}, f) = f

MOI.Bridges.adjoint_map_function(::Type{<:NormSpecialCaseBridge}, f) = f

MOI.Bridges.inverse_adjoint_map_function(::Type{<:NormSpecialCaseBridge}, f) = f

function MOI.Bridges.map_set(
::Type{<:NormSpecialCaseBridge{S}},
set::S,
) where {S}
return MOI.NormCone(set)
end

function MOI.Bridges.inverse_map_set(
::Type{<:NormSpecialCaseBridge{S}},
set::MOI.NormCone,
) where {S}
return S(MOI.dimension(set))
end

function concrete_bridge_type(
::Type{<:NormSpecialCaseBridge{S,T}},
F::Type{<:MOI.AbstractVectorFunction},
::Type{S},
) where {T,S<:Union{MOI.NormOneCone,MOI.SecondOrderCone,MOI.NormInfinityCone}}
return NormSpecialCaseBridge{S,T,F}
end

"""
NormOneConeToNormConeBridge{T,F} <: Bridges.Constraint.AbstractBridge

`NormOneConeToNormConeBridge` implements the following reformulations:

* ``(t, x) in NormOneCone(d)`` into ``(t, x) in NormCone(1, d)``

## Source node

`NormOneConeToNormConeBridge` supports:

* `F` in [`MOI.NormOneCone`](@ref)

## Target nodes

`NormOneConeToNormConeBridge` creates:

* `F` in [`MOI.NormCone`](@ref)
"""
const NormOneConeToNormConeBridge{T,F} =
NormSpecialCaseBridge{MOI.NormOneCone,T,F}

const NormOneConeToNormCone{T,OT<:MOI.ModelLike} =
SingleBridgeOptimizer{NormOneConeToNormConeBridge{T},OT}

"""
SecondOrderConeToNormConeBridge{T,F} <: Bridges.Constraint.AbstractBridge

`SecondOrderConeToNormConeBridge` implements the following reformulations:

* ``(t, x) in SecondOrderCone(d)`` into ``(t, x) in NormCone(2, d)``

## Source node

`SecondOrderConeToNormConeBridge` supports:

* `F` in [`MOI.SecondOrderCone`](@ref)

## Target nodes

`SecondOrderConeToNormConeBridge` creates:

* `F` in [`MOI.NormCone`](@ref)
"""
const SecondOrderConeToNormConeBridge{T,F} =
NormSpecialCaseBridge{MOI.SecondOrderCone,T,F}

const SecondOrderConeToNormCone{T,OT<:MOI.ModelLike} =
SingleBridgeOptimizer{SecondOrderConeToNormConeBridge{T},OT}

"""
NormInfinityConeToNormConeBridge{T,F} <: Bridges.Constraint.AbstractBridge

`NormInfinityConeToNormConeBridge` implements the following reformulations:

* ``(t, x) in NormInfinityCone(d)`` into ``(t, x) in NormCone(Inf, d)``

## Source node

`NormInfinityConeToNormConeBridge` supports:

* `F` in [`MOI.NormInfinityCone`](@ref)

## Target nodes

`NormInfinityConeToNormConeBridge` creates:

* `F` in [`MOI.NormCone`](@ref)
"""
const NormInfinityConeToNormConeBridge{T,F} =
NormSpecialCaseBridge{MOI.NormInfinityCone,T,F}

const NormInfinityConeToNormCone{T,OT<:MOI.ModelLike} =
SingleBridgeOptimizer{NormInfinityConeToNormConeBridge{T},OT}
163 changes: 163 additions & 0 deletions src/Bridges/Constraint/bridges/norm_to_power.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# 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.

"""
NormToPowerBridge{T,F} <: Bridges.Constraint.AbstractBridge

`NormToPowerBridge` implements the following reformulation:

* ``(t, x) \\in NormCone(p, 1+d)`` into ``(r_i, t, x_i) \\in PowerCone(1 / p)``
for all ``i``, and ``\\sum\\limits_i r_i == t``.

For details, see Alizadeh, F., and Goldfarb, D. (2001). "Second-order cone
programming." Mathematical Programming, Series B, 95:3-51.

## Source node

`NormToPowerBridge` supports:

* `F` in [`MOI.NormCone`](@ref)

## Target nodes

`NormToPowerBridge` creates:

* `F` in [`MOI.PowerCone{T}`](@ref)
odow marked this conversation as resolved.
Show resolved Hide resolved
* [`MOI.ScalarAffineFunction`](@ref) in [`MOI.EqualTo`](@ref)
"""
struct NormToPowerBridge{T,F} <: AbstractBridge
power::Vector{MOI.ConstraintIndex{F,MOI.PowerCone{T}}}
r::Vector{MOI.VariableIndex}
equal::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}
set::MOI.NormCone
end

const NormToPower{T,OT<:MOI.ModelLike} =
SingleBridgeOptimizer{NormToPowerBridge{T},OT}

function bridge_constraint(
::Type{NormToPowerBridge{T,F}},
model::MOI.ModelLike,
f::F,
s::MOI.NormCone,
) where {T,F}
d = MOI.dimension(s)
fi_s = MOI.Utilities.eachscalar(f)
r = MOI.add_variables(model, d - 1)
power_ci = MOI.ConstraintIndex{F,MOI.PowerCone{T}}[
MOI.add_constraint(
model,
MOI.Utilities.operate(vcat, T, r[i], fi_s[1], fi_s[i+1]),
MOI.PowerCone(T(1 / s.p)),
) for i in 1:length(r)
]
f = zero(MOI.ScalarAffineFunction{T})
for ri in r
f = MOI.Utilities.operate!(+, T, f, ri)
end
MOI.Utilities.operate!(-, T, f, fi_s[1])
equal_ci = MOI.add_constraint(model, f, MOI.EqualTo(zero(T)))
return NormToPowerBridge{T,F}(power_ci, r, equal_ci, s)
end

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

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

function MOI.Bridges.added_constraint_types(
::Type{<:NormToPowerBridge{T,F}},
) where {T,F}
return Tuple{Type,Type}[
(F, MOI.PowerCone{T}),
(MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}),
]
end

function concrete_bridge_type(
::Type{<:NormToPowerBridge{T}},
F::Type{<:MOI.AbstractVectorFunction},
::Type{MOI.NormCone},
) where {T}
return NormToPowerBridge{T,F}
end

function MOI.get(bridge::NormToPowerBridge, ::MOI.NumberOfVariables)::Int64
return length(bridge.r)
end

function MOI.get(bridge::NormToPowerBridge, ::MOI.ListOfVariableIndices)
return copy(bridge.r)
end

function MOI.get(
bridge::NormToPowerBridge{T,F},
::MOI.NumberOfConstraints{F,MOI.PowerCone{T}},
)::Int64 where {T,F}
return length(bridge.power)
end

function MOI.get(
bridge::NormToPowerBridge{T,F},
::MOI.ListOfConstraintIndices{F,MOI.PowerCone{T}},
) where {T,F}
return copy(bridge.power)
end

function MOI.get(
bridge::NormToPowerBridge{T},
::MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}},
)::Int64 where {T}
return 1
end

function MOI.get(
bridge::NormToPowerBridge{T},
::MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}},
) where {T}
return [bridge.equal]
end

function MOI.delete(model::MOI.ModelLike, bridge::NormToPowerBridge)
MOI.delete(model, bridge.power)
MOI.delete(model, bridge.equal)
MOI.delete(model, bridge.r)
return
end

function MOI.get(
model::MOI.ModelLike,
::MOI.ConstraintFunction,
bridge::NormToPowerBridge{T,F},
) where {T,F}
elements = MOI.Utilities.scalar_type(F)[]
for ci in bridge.power
f = MOI.get(model, MOI.ConstraintFunction(), ci)
fi_s = MOI.Utilities.eachscalar(f)
if isempty(elements)
push!(elements, fi_s[2])
end
push!(elements, fi_s[3])
end
return MOI.Utilities.operate(vcat, T, elements...)
end

function MOI.get(
::MOI.ModelLike,
::MOI.ConstraintSet,
bridge::NormToPowerBridge,
)
return bridge.set
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 @@ -88,6 +88,7 @@ _set(::Type{MOI.Nonpositives}) = MOI.Nonpositives(2)
_set(::Type{MOI.Nonnegatives}) = MOI.Nonnegatives(2)
_set(::Type{MOI.NormInfinityCone}) = MOI.NormInfinityCone(3)
_set(::Type{MOI.NormOneCone}) = MOI.NormOneCone(3)
_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)
Expand Down Expand Up @@ -275,6 +276,7 @@ for s in [
:Nonnegatives,
:NormInfinityCone,
:NormOneCone,
:NormCone,
:SecondOrderCone,
:RotatedSecondOrderCone,
:GeometricMeanCone,
Expand Down
42 changes: 42 additions & 0 deletions src/Test/test_conic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7023,3 +7023,45 @@ function version_added(
)
return v"1.7.0"
end

"""
test_conic_NormCone(model::MOI.ModelLike, config::Config)

Test [`MOI.NormCone`](@ref).
"""
function test_conic_NormCone(model::MOI.ModelLike, config::Config{T}) where {T}
@requires _supports(config, MOI.optimize!)
@requires MOI.supports_constraint(
model,
MOI.VectorOfVariables,
MOI.NormCone,
)
t = MOI.add_variable(model)
x = MOI.add_variables(model, 4)
x0 = T[1, 2, 3, 4]
MOI.add_constraint.(model, x, MOI.EqualTo.(x0))
f = MOI.VectorOfVariables([t; x])
MOI.add_constraint(model, f, MOI.NormCone(4, 5))
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
MOI.set(model, MOI.ObjectiveFunction{typeof(t)}(), t)
MOI.optimize!(model)
target = LinearAlgebra.norm(x0, 4)
@test ≈(MOI.get(model, MOI.VariablePrimal(), t), target, config)
return
end

function setup_test(
::typeof(test_conic_NormCone),
model::MOIU.MockOptimizer,
::Config{T},
) where {T}
x0 = T[1, 2, 3, 4]
MOIU.set_mock_optimize!(
model,
(mock::MOIU.MockOptimizer) ->
MOIU.mock_optimize!(mock, T[LinearAlgebra.norm(x0, 4); x0]),
)
return
end

version_added(::typeof(test_conic_NormCone)) = v"1.14.0"
1 change: 1 addition & 0 deletions src/Utilities/model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@ const LessThanIndicatorZero{T} =
MOI.Complements,
MOI.NormInfinityCone,
MOI.NormOneCone,
MOI.NormCone,
MOI.SecondOrderCone,
MOI.RotatedSecondOrderCone,
MOI.GeometricMeanCone,
Expand Down
Loading