Skip to content

Commit 74387eb

Browse files
blegatodow
andauthored
Add ScaledPositiveSemidefiniteConeTriangle (#2154)
* Add ScaledPositiveSemidefiniteConeTriangle * Fix format * Fix * Update src/sets.jl Co-authored-by: Oscar Dowson <[email protected]> * Update src/sets.jl Co-authored-by: Oscar Dowson <[email protected]> * Fixes * Address review comments * Address review comments * Documentation reference fix * Fixes * Update docstring * Add to standard_form.md * Fix tests * Update src/Test/test_conic.jl * Update symmetric_matrix_scaling.jl * Fix printing * Fix format * Fix format * Use explicit length * Fix bug and add more tests --------- Co-authored-by: Oscar Dowson <[email protected]> Co-authored-by: odow <[email protected]>
1 parent efbeae4 commit 74387eb

File tree

17 files changed

+634
-22
lines changed

17 files changed

+634
-22
lines changed

docs/src/manual/standard_form.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ The matrix-valued set types implemented in MathOptInterface.jl are:
9191
| [`RootDetConeSquare(d)`](@ref MathOptInterface.RootDetConeSquare) | ``\{ (t,X) \in \mathbb{R}^{1+d^2} : t \le \det(X)^{1/d}, X \mbox{ is a PSD matrix} \}`` |
9292
| [`PositiveSemidefiniteConeTriangle(d)`](@ref MathOptInterface.PositiveSemidefiniteConeTriangle) | ``\{ X \in \mathbb{R}^{d(d+1)/2} : X \mbox{ is the upper triangle of a PSD matrix} \}`` |
9393
| [`PositiveSemidefiniteConeSquare(d)`](@ref MathOptInterface.PositiveSemidefiniteConeSquare) | ``\{ X \in \mathbb{R}^{d^2} : X \mbox{ is a PSD matrix} \}`` |
94+
| [`ScaledPositiveSemidefiniteConeTriangle(d)`](@ref MathOptInterface.ScaledPositiveSemidefiniteConeTriangle) | ``\{ X \in \mathbb{R}^{d(d+1)/2} : X \mbox{ is a PSD matrix} \}`` |
9495
| [`LogDetConeTriangle(d)`](@ref MathOptInterface.LogDetConeTriangle) | ``\{ (t,u,X) \in \mathbb{R}^{2+d(1+d)/2} : t \le u\log(\det(X/u)), X \mbox{ is the upper triangle of a PSD matrix}, u > 0 \}`` |
9596
| [`LogDetConeSquare(d)`](@ref MathOptInterface.LogDetConeSquare) | ``\{ (t,u,X) \in \mathbb{R}^{2+d^2} : t \le u \log(\det(X/u)), X \mbox{ is a PSD matrix}, u > 0 \}`` |
9697
| [`NormSpectralCone(r, c)`](@ref MathOptInterface.NormSpectralCone) | ``\{ (t, X) \in \mathbb{R}^{1 + r \times c} : t \ge \sigma_1(X), X \mbox{ is a } r\times c\mbox{ matrix} \}``

docs/src/reference/standard_form.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ List of recognized matrix sets.
146146
PositiveSemidefiniteConeTriangle
147147
PositiveSemidefiniteConeSquare
148148
HermitianPositiveSemidefiniteConeTriangle
149+
ScaledPositiveSemidefiniteConeTriangle
149150
LogDetConeTriangle
150151
LogDetConeSquare
151152
RootDetConeTriangle

docs/src/submodules/Bridges/list_of_bridges.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ Bridges.Constraint.NormSpectralBridge
5353
Bridges.Constraint.NormNuclearBridge
5454
Bridges.Constraint.SquareBridge
5555
Bridges.Constraint.HermitianToSymmetricPSDBridge
56+
Bridges.Constraint.SymmetricMatrixScalingBridge
57+
Bridges.Constraint.SymmetricMatrixInverseScalingBridge
5658
Bridges.Constraint.RootDetBridge
5759
Bridges.Constraint.LogDetBridge
5860
Bridges.Constraint.IndicatorActiveOnFalseBridge

src/Bridges/Constraint/Constraint.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ include("bridges/split_complex_zeros.jl")
5757
include("bridges/split_hyperrectangle.jl")
5858
include("bridges/hermitian.jl")
5959
include("bridges/square.jl")
60+
include("bridges/symmetric_matrix_scaling.jl")
6061
include("bridges/table.jl")
6162
include("bridges/vectorize.jl")
6263
include("bridges/zero_one.jl")
@@ -104,6 +105,11 @@ function add_all_bridges(bridged_model, ::Type{T}) where {T}
104105
MOI.Bridges.add_bridge(bridged_model, NormNuclearBridge{T})
105106
MOI.Bridges.add_bridge(bridged_model, HermitianToSymmetricPSDBridge{T})
106107
MOI.Bridges.add_bridge(bridged_model, SquareBridge{T})
108+
MOI.Bridges.add_bridge(bridged_model, SymmetricMatrixScalingBridge{T})
109+
MOI.Bridges.add_bridge(
110+
bridged_model,
111+
SymmetricMatrixInverseScalingBridge{T},
112+
)
107113
MOI.Bridges.add_bridge(bridged_model, LogDetBridge{T})
108114
MOI.Bridges.add_bridge(bridged_model, RootDetBridge{T})
109115
MOI.Bridges.add_bridge(bridged_model, RSOCtoSOCBridge{T})
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
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+
SymmetricMatrixScalingBridge{T,F,G} <: Bridges.Constraint.AbstractBridge
9+
10+
`SymmetricMatrixScalingBridge` implements the reformulation from constraints
11+
in the `MOI.PositiveSemidefiniteConeTriangle` to constraints
12+
in the `MOI.ScaledPositiveSemidefiniteConeTriangle`.
13+
14+
## Source node
15+
16+
`SymmetricMatrixScalingBridge` supports:
17+
18+
* `G` in [`MOI.PositiveSemidefiniteConeTriangle`](@ref)
19+
20+
## Target node
21+
22+
`SymmetricMatrixScalingBridge` creates:
23+
24+
* `F` in [`MOI.ScaledPositiveSemidefiniteConeTriangle`](@ref)
25+
"""
26+
struct SymmetricMatrixScalingBridge{T,F,G} <: SetMapBridge{
27+
T,
28+
MOI.ScaledPositiveSemidefiniteConeTriangle,
29+
MOI.PositiveSemidefiniteConeTriangle,
30+
F,
31+
G,
32+
}
33+
constraint::MOI.ConstraintIndex{
34+
F,
35+
MOI.ScaledPositiveSemidefiniteConeTriangle,
36+
}
37+
end
38+
39+
const SymmetricMatrixScaling{T,OT<:MOI.ModelLike} =
40+
SingleBridgeOptimizer{SymmetricMatrixScalingBridge{T},OT}
41+
42+
function concrete_bridge_type(
43+
::Type{<:SymmetricMatrixScalingBridge{T}},
44+
G::Type{<:MOI.AbstractVectorFunction},
45+
::Type{MOI.PositiveSemidefiniteConeTriangle},
46+
) where {T}
47+
F = MOI.Utilities.promote_operation(
48+
*,
49+
T,
50+
LinearAlgebra.Diagonal{T,Vector{T}},
51+
G,
52+
)
53+
return SymmetricMatrixScalingBridge{T,F,G}
54+
end
55+
56+
function MOI.Bridges.map_set(
57+
::Type{<:SymmetricMatrixScalingBridge},
58+
set::MOI.PositiveSemidefiniteConeTriangle,
59+
)
60+
return MOI.ScaledPositiveSemidefiniteConeTriangle(set.side_dimension)
61+
end
62+
63+
function MOI.Bridges.inverse_map_set(
64+
::Type{<:SymmetricMatrixScalingBridge},
65+
set::MOI.ScaledPositiveSemidefiniteConeTriangle,
66+
)
67+
return MOI.PositiveSemidefiniteConeTriangle(set.side_dimension)
68+
end
69+
70+
_length(f::MOI.AbstractVectorFunction) = MOI.output_dimension(f)
71+
_length(f::AbstractVector) = length(f)
72+
73+
function MOI.Bridges.map_function(
74+
::Type{<:SymmetricMatrixScalingBridge{T}},
75+
func,
76+
) where {T}
77+
scale = MOI.Utilities.symmetric_matrix_scaling_vector(T, _length(func))
78+
return MOI.Utilities.operate(*, T, LinearAlgebra.Diagonal(scale), func)
79+
end
80+
81+
function MOI.Bridges.inverse_map_function(
82+
::Type{<:SymmetricMatrixScalingBridge{T}},
83+
func,
84+
) where {T}
85+
N = _length(func)
86+
scale = MOI.Utilities.symmetric_matrix_inverse_scaling_vector(T, N)
87+
return MOI.Utilities.operate(*, T, LinearAlgebra.Diagonal(scale), func)
88+
end
89+
90+
# Since the map is a diagonal matrix `D`, it is symmetric so one would initially
91+
# expect `adjoint_map_function` to be the same as `map_function`. However, the
92+
# scalar product for the scaled PSD cone is `<x, y>_2 = x'y` but the scalar
93+
# product for the PSD cone additionally scales the offdiagonal entries by `2`
94+
# hence by `D^2` so `<x, y>_1 = x'D^2y`.
95+
# So `<Dx, y>_2 = <x, D^(-1)y>_1` hence the adjoint of `D` is its inverse!
96+
function MOI.Bridges.adjoint_map_function(
97+
BT::Type{<:SymmetricMatrixScalingBridge},
98+
func,
99+
)
100+
return MOI.Bridges.inverse_map_function(BT, func)
101+
end
102+
103+
function MOI.Bridges.inverse_adjoint_map_function(
104+
BT::Type{<:SymmetricMatrixScalingBridge},
105+
func,
106+
)
107+
return MOI.Bridges.map_function(BT, func)
108+
end
109+
110+
"""
111+
SymmetricMatrixInverseScalingBridge{T,F,G} <: Bridges.Constraint.AbstractBridge
112+
113+
`SymmetricMatrixInverseScalingBridge` implements the reformulation from constraints
114+
in the `MOI.ScaledPositiveSemidefiniteConeTriangle` to constraints
115+
in the `MOI.PositiveSemidefiniteConeTriangle`.
116+
117+
## Source node
118+
119+
`SymmetricMatrixInverseScalingBridge` supports:
120+
121+
* `G` in [`MOI.ScaledPositiveSemidefiniteConeTriangle`](@ref)
122+
123+
## Target node
124+
125+
`SymmetricMatrixInverseScalingBridge` creates:
126+
127+
* `F` in [`MOI.PositiveSemidefiniteConeTriangle`](@ref)
128+
"""
129+
struct SymmetricMatrixInverseScalingBridge{T,F,G} <: SetMapBridge{
130+
T,
131+
MOI.PositiveSemidefiniteConeTriangle,
132+
MOI.ScaledPositiveSemidefiniteConeTriangle,
133+
F,
134+
G,
135+
}
136+
constraint::MOI.ConstraintIndex{F,MOI.PositiveSemidefiniteConeTriangle}
137+
end
138+
139+
const SymmetricMatrixInverseScaling{T,OT<:MOI.ModelLike} =
140+
SingleBridgeOptimizer{SymmetricMatrixInverseScalingBridge{T},OT}
141+
142+
function concrete_bridge_type(
143+
::Type{<:SymmetricMatrixInverseScalingBridge{T}},
144+
G::Type{<:MOI.AbstractVectorFunction},
145+
::Type{MOI.ScaledPositiveSemidefiniteConeTriangle},
146+
) where {T}
147+
F = MOI.Utilities.promote_operation(
148+
*,
149+
T,
150+
LinearAlgebra.Diagonal{T,Vector{T}},
151+
G,
152+
)
153+
return SymmetricMatrixInverseScalingBridge{T,F,G}
154+
end
155+
156+
function MOI.Bridges.map_set(
157+
::Type{<:SymmetricMatrixInverseScalingBridge},
158+
set::MOI.ScaledPositiveSemidefiniteConeTriangle,
159+
)
160+
return MOI.PositiveSemidefiniteConeTriangle(set.side_dimension)
161+
end
162+
163+
function MOI.Bridges.inverse_map_set(
164+
::Type{<:SymmetricMatrixInverseScalingBridge},
165+
set::MOI.PositiveSemidefiniteConeTriangle,
166+
)
167+
return MOI.ScaledPositiveSemidefiniteConeTriangle(set.side_dimension)
168+
end
169+
170+
function MOI.Bridges.map_function(
171+
::Type{<:SymmetricMatrixInverseScalingBridge{T}},
172+
func,
173+
) where {T}
174+
N = _length(func)
175+
scale = MOI.Utilities.symmetric_matrix_inverse_scaling_vector(T, N)
176+
return MOI.Utilities.operate(*, T, LinearAlgebra.Diagonal(scale), func)
177+
end
178+
179+
function MOI.Bridges.inverse_map_function(
180+
::Type{<:SymmetricMatrixInverseScalingBridge{T}},
181+
func,
182+
) where {T}
183+
N = _length(func)
184+
scale = MOI.Utilities.symmetric_matrix_scaling_vector(T, N)
185+
return MOI.Utilities.operate(*, T, LinearAlgebra.Diagonal(scale), func)
186+
end
187+
188+
function MOI.Bridges.adjoint_map_function(
189+
BT::Type{<:SymmetricMatrixInverseScalingBridge},
190+
func,
191+
)
192+
return MOI.Bridges.inverse_map_function(BT, func)
193+
end
194+
195+
function MOI.Bridges.inverse_adjoint_map_function(
196+
BT::Type{<:SymmetricMatrixInverseScalingBridge},
197+
func,
198+
)
199+
return MOI.Bridges.map_function(BT, func)
200+
end

src/Bridges/Objective/bridges/functionize.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,5 @@ function MOI.get(
103103
) where {T}
104104
F = MOI.ScalarAffineFunction{T}
105105
func = MOI.get(model, MOI.ObjectiveFunction{F}())
106-
return convert(MOI.VariableIndex, func)
106+
return MOI.Utilities.convert_approx(MOI.VariableIndex, func)
107107
end

src/Test/test_basic_constraint.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ end
108108
function _set(::Type{MOI.HermitianPositiveSemidefiniteConeTriangle})
109109
return MOI.HermitianPositiveSemidefiniteConeTriangle(3)
110110
end
111+
function _set(::Type{MOI.ScaledPositiveSemidefiniteConeTriangle})
112+
return MOI.ScaledPositiveSemidefiniteConeTriangle(3)
113+
end
111114
_set(::Type{MOI.LogDetConeTriangle}) = MOI.LogDetConeTriangle(3)
112115
_set(::Type{MOI.LogDetConeSquare}) = MOI.LogDetConeSquare(3)
113116
_set(::Type{MOI.RootDetConeTriangle}) = MOI.RootDetConeTriangle(3)
@@ -293,6 +296,7 @@ for s in [
293296
:PositiveSemidefiniteConeSquare,
294297
:PositiveSemidefiniteConeTriangle,
295298
:HermitianPositiveSemidefiniteConeTriangle,
299+
:ScaledPositiveSemidefiniteConeTriangle,
296300
:LogDetConeTriangle,
297301
:LogDetConeSquare,
298302
:RootDetConeTriangle,

src/Test/test_conic.jl

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4753,6 +4753,7 @@ function _test_conic_PositiveSemidefiniteCone_helper(
47534753
atol = config.atol
47544754
rtol = config.rtol
47554755
square = psdcone == MOI.PositiveSemidefiniteConeSquare
4756+
scaled = psdcone == MOI.ScaledPositiveSemidefiniteConeTriangle
47564757
@requires MOI.supports_incremental_interface(model)
47574758
@requires MOI.supports(
47584759
model,
@@ -4778,13 +4779,21 @@ function _test_conic_PositiveSemidefiniteCone_helper(
47784779
@test MOI.get(model, MOI.NumberOfVariables()) == (square ? 4 : 3)
47794780
vov = MOI.VectorOfVariables(X)
47804781
if use_VectorOfVariables
4782+
@assert !scaled
47814783
cX = MOI.add_constraint(model, vov, psdcone(2))
47824784
else
4783-
cX = MOI.add_constraint(
4784-
model,
4785-
MOI.VectorAffineFunction{T}(vov),
4786-
psdcone(2),
4787-
)
4785+
func = MOI.VectorAffineFunction{T}(vov)
4786+
if scaled
4787+
func = MOI.Utilities.operate(
4788+
*,
4789+
T,
4790+
LinearAlgebra.Diagonal(
4791+
MOI.Utilities.symmetric_matrix_scaling_vector(T, 2),
4792+
),
4793+
func,
4794+
)
4795+
end
4796+
cX = MOI.add_constraint(model, func, psdcone(2))
47884797
end
47894798
c = MOI.add_constraint(
47904799
model,
@@ -4833,12 +4842,18 @@ function _test_conic_PositiveSemidefiniteCone_helper(
48334842
Xv = square ? ones(T, 4) : ones(T, 3)
48344843
@test MOI.get(model, MOI.VariablePrimal(), X) Xv atol = atol rtol =
48354844
rtol
4845+
if scaled
4846+
Xv[2] *= sqrt(T(2))
4847+
end
48364848
@test MOI.get(model, MOI.ConstraintPrimal(), cX) Xv atol = atol rtol =
48374849
rtol
48384850
if _supports(config, MOI.ConstraintDual)
48394851
@test MOI.get(model, MOI.ConstraintDual(), c) 2 atol = atol rtol =
48404852
rtol
4841-
cXv = square ? [1, -2, 0, 1] : [1, -1, 1]
4853+
cXv = square ? T[1, -2, 0, 1] : T[1, -1, 1]
4854+
if scaled
4855+
cXv[2] *= sqrt(T(2))
4856+
end
48424857
@test MOI.get(model, MOI.ConstraintDual(), cX) cXv atol = atol rtol =
48434858
rtol
48444859
end
@@ -4967,6 +4982,41 @@ function setup_test(
49674982
return
49684983
end
49694984

4985+
function test_conic_ScaledPositiveSemidefiniteConeTriangle_VectorAffineFunction(
4986+
model::MOI.ModelLike,
4987+
config::Config{T},
4988+
) where {T}
4989+
_test_conic_PositiveSemidefiniteCone_helper(
4990+
model,
4991+
false,
4992+
MOI.ScaledPositiveSemidefiniteConeTriangle,
4993+
config,
4994+
)
4995+
return
4996+
end
4997+
4998+
function setup_test(
4999+
::typeof(
5000+
test_conic_ScaledPositiveSemidefiniteConeTriangle_VectorAffineFunction,
5001+
),
5002+
model::MOIU.MockOptimizer,
5003+
::Config{T},
5004+
) where {T}
5005+
MOIU.set_mock_optimize!(
5006+
model,
5007+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(
5008+
mock,
5009+
ones(T, 3),
5010+
(
5011+
MOI.VectorAffineFunction{T},
5012+
MOI.ScaledPositiveSemidefiniteConeTriangle,
5013+
) => [T[1, -sqrt(T(2)), 1]],
5014+
(MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}) => T[2],
5015+
),
5016+
)
5017+
return
5018+
end
5019+
49705020
"""
49715021
Problem SDP1 - sdo1 from MOSEK docs
49725022
From Mosek.jl/test/mathprogtestextra.jl, under license:

0 commit comments

Comments
 (0)