Skip to content

Commit 089bb06

Browse files
committed
Add NormCone for representing the epigraph of a p-norm
1 parent 5025b50 commit 089bb06

File tree

11 files changed

+333
-0
lines changed

11 files changed

+333
-0
lines changed

docs/src/manual/standard_form.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ The vector-valued set types implemented in MathOptInterface.jl are:
7979
| [`NormInfinityCone(d)`](@ref MathOptInterface.NormInfinityCone) | ``\{ (t,x) \in \mathbb{R}^{d} : t \ge \max_i \lvert x_i \rvert \}`` |
8080
| [`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 \}`` |
8181
| [`HyperRectangle(l, u)`](@ref MathOptInterface.HyperRectangle) | ``\{x \in \bar{\mathbb{R}}^d: x_i \in [l_i, u_i] \forall i=1,\ldots,d\}`` |
82+
| [`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}} \}`` |
8283

8384
## Matrix cones
8485

docs/src/reference/standard_form.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Nonnegatives
8282
Nonpositives
8383
NormInfinityCone
8484
NormOneCone
85+
NormCone
8586
SecondOrderCone
8687
RotatedSecondOrderCone
8788
GeometricMeanCone

docs/src/submodules/Bridges/list_of_bridges.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Bridges.Constraint.SOCtoPSDBridge
4141
Bridges.Constraint.RSOCtoPSDBridge
4242
Bridges.Constraint.NormInfinityBridge
4343
Bridges.Constraint.NormOneBridge
44+
Bridges.Constraint.NormToPowerBridge
4445
Bridges.Constraint.GeoMeantoRelEntrBridge
4546
Bridges.Constraint.GeoMeanToPowerBridge
4647
Bridges.Constraint.GeoMeanBridge

src/Bridges/Constraint/Constraint.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ include("bridges/interval.jl")
3939
include("bridges/ltgt_to_interval.jl")
4040
include("bridges/norm_infinity.jl")
4141
include("bridges/norm_one.jl")
42+
include("bridges/norm_to_power.jl")
4243
include("bridges/norm_spec_nuc_to_psd.jl")
4344
include("bridges/number_conversion.jl")
4445
include("bridges/quad_to_soc.jl")
@@ -93,6 +94,7 @@ function add_all_bridges(bridged_model, ::Type{T}) where {T}
9394
MOI.Bridges.add_bridge(bridged_model, GeoMeantoRelEntrBridge{T})
9495
MOI.Bridges.add_bridge(bridged_model, GeoMeanBridge{T})
9596
MOI.Bridges.add_bridge(bridged_model, GeoMeanToPowerBridge{T})
97+
MOI.Bridges.add_bridge(bridged_model, NormToPowerBridge{T})
9698
MOI.Bridges.add_bridge(bridged_model, RelativeEntropyBridge{T})
9799
MOI.Bridges.add_bridge(bridged_model, NormSpectralBridge{T})
98100
MOI.Bridges.add_bridge(bridged_model, NormNuclearBridge{T})
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
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+
NormToPowerBridge{T,F} <: Bridges.Constraint.AbstractBridge
9+
10+
`NormToPowerBridge` implements the following reformulation:
11+
12+
* ``(t, x...) \\in NormCone(p, 1+d)`` into ``(r_i, t, x_i) \\in PowerCone(1 / p)``
13+
for all ``i``, and ``\\sum\\limits_i r_i == t``.
14+
15+
## Source node
16+
17+
`NormToPowerBridge` supports:
18+
19+
* `F` in [`MOI.NormCone`](@ref)
20+
21+
## Target nodes
22+
23+
`NormToPowerBridge` creates:
24+
25+
* `F` in [`MOI.PowerCone{T}`](@ref)
26+
* [`MOI.ScalarAffineFunction`](@ref) in [`MOI.EqualTo`](@ref)
27+
"""
28+
struct NormToPowerBridge{T,F} <: AbstractBridge
29+
power::Vector{MOI.ConstraintIndex{F,MOI.PowerCone{T}}}
30+
r::Vector{MOI.VariableIndex}
31+
equal::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}
32+
set::MOI.NormCone
33+
end
34+
35+
const NormToPower{T,OT<:MOI.ModelLike} =
36+
SingleBridgeOptimizer{NormToPowerBridge{T},OT}
37+
38+
function bridge_constraint(
39+
::Type{NormToPowerBridge{T,F}},
40+
model::MOI.ModelLike,
41+
f::F,
42+
s::MOI.NormCone,
43+
) where {T,F}
44+
d = MOI.dimension(s)
45+
fi_s = MOI.Utilities.eachscalar(f)
46+
r = MOI.add_variables(model, d - 1)
47+
power_ci = MOI.ConstraintIndex{F,MOI.PowerCone{T}}[
48+
MOI.add_constraint(
49+
model,
50+
MOI.Utilities.operate(vcat, T, r[i], fi_s[1], fi_s[i+1]),
51+
MOI.PowerCone(T(1 / s.p)),
52+
) for i in 1:length(r)
53+
]
54+
f = zero(MOI.ScalarAffineFunction{T})
55+
for ri in r
56+
f = MOI.Utilities.operate!(+, T, f, ri)
57+
end
58+
MOI.Utilities.operate!(-, T, f, fi_s[1])
59+
equal_ci = MOI.add_constraint(model, f, MOI.EqualTo(zero(T)))
60+
return NormToPowerBridge{T,F}(power_ci, r, equal_ci, s)
61+
end
62+
63+
function MOI.supports_constraint(
64+
::Type{<:NormToPowerBridge{T}},
65+
::Type{<:MOI.AbstractVectorFunction},
66+
::Type{MOI.NormCone},
67+
) where {T}
68+
return true
69+
end
70+
71+
function MOI.Bridges.added_constrained_variable_types(
72+
::Type{<:NormToPowerBridge},
73+
)
74+
return Tuple{Type}[]
75+
end
76+
77+
function MOI.Bridges.added_constraint_types(
78+
::Type{<:NormToPowerBridge{T,F}},
79+
) where {T,F}
80+
return Tuple{Type,Type}[
81+
(F, MOI.PowerCone{T}),
82+
(MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}),
83+
]
84+
end
85+
86+
function concrete_bridge_type(
87+
::Type{<:NormToPowerBridge{T}},
88+
F::Type{<:MOI.AbstractVectorFunction},
89+
::Type{MOI.NormCone},
90+
) where {T}
91+
return NormToPowerBridge{T,F}
92+
end
93+
94+
function MOI.get(bridge::NormToPowerBridge, ::MOI.NumberOfVariables)::Int64
95+
return length(bridge.r)
96+
end
97+
98+
function MOI.get(bridge::NormToPowerBridge, ::MOI.ListOfVariableIndices)
99+
return copy(bridge.r)
100+
end
101+
102+
function MOI.get(
103+
bridge::NormToPowerBridge{T,F},
104+
::MOI.NumberOfConstraints{F,MOI.PowerCone{T}},
105+
)::Int64 where {T,F}
106+
return length(bridge.power)
107+
end
108+
109+
function MOI.get(
110+
bridge::NormToPowerBridge{T,F},
111+
::MOI.ListOfConstraintIndices{F,MOI.PowerCone{T}},
112+
) where {T,F}
113+
return copy(bridge.power)
114+
end
115+
116+
function MOI.get(
117+
bridge::NormToPowerBridge{T},
118+
::MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}},
119+
)::Int64 where {T}
120+
return 1
121+
end
122+
123+
function MOI.get(
124+
bridge::NormToPowerBridge{T},
125+
::MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}},
126+
) where {T}
127+
return [bridge.equal]
128+
end
129+
130+
function MOI.delete(model::MOI.ModelLike, bridge::NormToPowerBridge)
131+
MOI.delete(model, bridge.power)
132+
MOI.delete(model, bridge.equal)
133+
MOI.delete(model, bridge.r)
134+
return
135+
end
136+
137+
function MOI.get(
138+
model::MOI.ModelLike,
139+
::MOI.ConstraintFunction,
140+
bridge::NormToPowerBridge{T,F},
141+
) where {T,F}
142+
elements = MOI.Utilities.scalar_type(F)[]
143+
for ci in bridge.power
144+
f = MOI.get(model, MOI.ConstraintFunction(), ci)
145+
fi_s = MOI.Utilities.eachscalar(f)
146+
if isempty(elements)
147+
push!(elements, fi_s[2])
148+
end
149+
push!(elements, fi_s[3])
150+
end
151+
return MOI.Utilities.operate(vcat, T, elements...)
152+
end
153+
154+
function MOI.get(
155+
::MOI.ModelLike,
156+
::MOI.ConstraintSet,
157+
bridge::NormToPowerBridge,
158+
)
159+
return bridge.set
160+
end

src/Test/test_basic_constraint.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ _set(::Type{MOI.Nonpositives}) = MOI.Nonpositives(2)
8888
_set(::Type{MOI.Nonnegatives}) = MOI.Nonnegatives(2)
8989
_set(::Type{MOI.NormInfinityCone}) = MOI.NormInfinityCone(3)
9090
_set(::Type{MOI.NormOneCone}) = MOI.NormOneCone(3)
91+
_set(::Type{MOI.NormCone}) = MOI.NormCone(4.0, 3)
9192
_set(::Type{MOI.SecondOrderCone}) = MOI.SecondOrderCone(3)
9293
_set(::Type{MOI.RotatedSecondOrderCone}) = MOI.RotatedSecondOrderCone(3)
9394
_set(::Type{MOI.GeometricMeanCone}) = MOI.GeometricMeanCone(3)
@@ -275,6 +276,7 @@ for s in [
275276
:Nonnegatives,
276277
:NormInfinityCone,
277278
:NormOneCone,
279+
:NormCone,
278280
:SecondOrderCone,
279281
:RotatedSecondOrderCone,
280282
:GeometricMeanCone,

src/Test/test_conic.jl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7023,3 +7023,45 @@ function version_added(
70237023
)
70247024
return v"1.7.0"
70257025
end
7026+
7027+
"""
7028+
test_conic_NormCone(model::MOI.ModelLike, config::Config)
7029+
7030+
Test [`MOI.NormCone`](@ref).
7031+
"""
7032+
function test_conic_NormCone(model::MOI.ModelLike, config::Config{T}) where {T}
7033+
@requires _supports(config, MOI.optimize!)
7034+
@requires MOI.supports_constraint(
7035+
model,
7036+
MOI.VectorOfVariables,
7037+
MOI.NormCone,
7038+
)
7039+
t = MOI.add_variable(model)
7040+
x = MOI.add_variables(model, 4)
7041+
x0 = T[1, 2, 3, 4]
7042+
MOI.add_constraint.(model, x, MOI.EqualTo.(x0))
7043+
f = MOI.VectorOfVariables([t; x])
7044+
MOI.add_constraint(model, f, MOI.NormCone(4, 5))
7045+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
7046+
MOI.set(model, MOI.ObjectiveFunction{typeof(t)}(), t)
7047+
MOI.optimize!(model)
7048+
target = LinearAlgebra.norm(x0, 4)
7049+
@test (MOI.get(model, MOI.VariablePrimal(), t), target, config)
7050+
return
7051+
end
7052+
7053+
function setup_test(
7054+
::typeof(test_conic_NormCone),
7055+
model::MOIU.MockOptimizer,
7056+
::Config{T},
7057+
) where {T}
7058+
x0 = T[1, 2, 3, 4]
7059+
MOIU.set_mock_optimize!(
7060+
model,
7061+
(mock::MOIU.MockOptimizer) ->
7062+
MOIU.mock_optimize!(mock, T[LinearAlgebra.norm(x0, 4); x0]),
7063+
)
7064+
return
7065+
end
7066+
7067+
version_added(::typeof(test_conic_NormCone)) = v"1.14.0"

src/Utilities/model.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,7 @@ const LessThanIndicatorZero{T} =
780780
MOI.Complements,
781781
MOI.NormInfinityCone,
782782
MOI.NormOneCone,
783+
MOI.NormCone,
783784
MOI.SecondOrderCone,
784785
MOI.RotatedSecondOrderCone,
785786
MOI.GeometricMeanCone,

src/sets.jl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,48 @@ end
668668
dual_set(s::NormOneCone) = NormInfinityCone(dimension(s))
669669
dual_set_type(::Type{NormOneCone}) = NormInfinityCone
670670

671+
"""
672+
NormCone(p::Float64, dimension::Int)
673+
674+
The ``\\ell_p``-norm cone ``\\{ (t,x) \\in \\mathbb{R}^{dimension} : t \\ge \\left(\\sum\\limits_i |x_i|^p\\right)^{\\frac{1}{p}} \\}``
675+
of dimension `dimension`.
676+
677+
The `dimension` must be at least `1`.
678+
679+
## Example
680+
681+
```jldoctest
682+
julia> import MathOptInterface as MOI
683+
684+
julia> model = MOI.Utilities.Model{Float64}()
685+
MOIU.Model{Float64}
686+
687+
julia> t = MOI.add_variable(model)
688+
MOI.VariableIndex(1)
689+
690+
julia> x = MOI.add_variables(model, 3);
691+
692+
julia> MOI.add_constraint(model, MOI.VectorOfVariables([t; x]), MOI.NormCone(3, 4))
693+
MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.NormCone}(1)
694+
```
695+
"""
696+
struct NormCone <: AbstractVectorSet
697+
p::Float64
698+
dimension::Int
699+
700+
function NormCone(p::Real, dimension::Base.Integer)
701+
if !(dimension >= 1)
702+
throw(
703+
DimensionMismatch(
704+
"Dimension of NormCone must be >= 1, not " *
705+
"$(dimension).",
706+
),
707+
)
708+
end
709+
return new(convert(Float64, p), dimension)
710+
end
711+
end
712+
671713
"""
672714
SecondOrderCone(dimension::Int)
673715
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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+
module TestConstraintNormToPower
8+
9+
using Test
10+
11+
import MathOptInterface as MOI
12+
13+
function runtests()
14+
for name in names(@__MODULE__; all = true)
15+
if startswith("$(name)", "test_")
16+
@testset "$(name)" begin
17+
getfield(@__MODULE__, name)()
18+
end
19+
end
20+
end
21+
return
22+
end
23+
24+
function test_runtests_dimension_5()
25+
MOI.Bridges.runtests(
26+
MOI.Bridges.Constraint.NormToPowerBridge,
27+
"""
28+
variables: t, x1, x2, x3, x4
29+
[t, x1, x2, x3, x4] in NormCone(4, 5)
30+
""",
31+
"""
32+
variables: t, x1, x2, x3, x4, r1, r2, r3, r4
33+
[r1, t, x1] in PowerCone(0.25)
34+
[r2, t, x2] in PowerCone(0.25)
35+
[r3, t, x3] in PowerCone(0.25)
36+
[r4, t, x4] in PowerCone(0.25)
37+
r1 + r2 + r3 + r4 + -1.0 * t == 0.0
38+
""",
39+
)
40+
return
41+
end
42+
43+
function test_runtests_dimension_4()
44+
MOI.Bridges.runtests(
45+
MOI.Bridges.Constraint.NormToPowerBridge,
46+
"""
47+
variables: t, x1, x2, x3
48+
[t, 1.0 * x1, 2.0 * x2, x3] in NormCone(4, 4)
49+
""",
50+
"""
51+
variables: t, x1, x2, x3, r1, r2, r3
52+
[r1, t, 1.0 * x1] in PowerCone(0.25)
53+
[r2, t, 2.0 * x2] in PowerCone(0.25)
54+
[r3, t, 1.0 * x3] in PowerCone(0.25)
55+
r1 + r2 + r3 + -1.0 * t == 0.0
56+
""",
57+
)
58+
return
59+
end
60+
61+
function test_runtests_dimension_2()
62+
MOI.Bridges.runtests(
63+
MOI.Bridges.Constraint.NormToPowerBridge,
64+
"""
65+
variables: t, x1
66+
[t, x1] in NormCone(2, 2)
67+
""",
68+
"""
69+
variables: t, x1, r1
70+
[r1, t, x1] in PowerCone(0.5)
71+
r1 + -1.0 * t == 0.0
72+
""",
73+
)
74+
return
75+
end
76+
77+
end # module
78+
79+
TestConstraintNormToPower.runtests()

0 commit comments

Comments
 (0)