Skip to content

Commit e039723

Browse files
authored
Remove get_conic_form and add sparse conversion (#21)
1 parent 2f9b11b commit e039723

File tree

5 files changed

+171
-147
lines changed

5 files changed

+171
-147
lines changed

Project.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@ MathOptInterface = "0.9"
1414
julia = "1"
1515

1616
[extras]
17-
COSMO = "1e616198-aa4e-51ec-90a2-23f7fbd31d8d"
18-
ProxSDP = "65e78d25-6039-50a4-9445-38022e3d2eb3"
19-
SCS = "c946c3f1-0d1f-5ce8-9dea-7daa1f7e2d13"
2017
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2118

2219
[targets]
23-
test = ["Test", "SCS", "ProxSDP", "COSMO"]
20+
test = ["Test"]

src/conic_form.jl

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,6 @@ end
2424

2525
_set_type(::MOI.ConstraintIndex{F,S}) where {F,S} = S
2626

27-
function get_conic_form(::Type{T}, model::M, con_idx) where {T, M <: MOI.AbstractOptimizer}
28-
# extract cones
29-
cones = _set_type.(con_idx)
30-
cones = unique(cones)
31-
32-
conic = GeometricConicForm{T, SparseMatrixCSRtoCSC{T, Int}, Vector{T}}(Tuple(cones))
33-
34-
idxmap = MOI.copy_to(conic, model)
35-
36-
# fix optimization sense
37-
if MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE
38-
conic.c = -conic.c
39-
end
40-
41-
return conic
42-
end
43-
4427
MOI.is_empty(model::GeometricConicForm) = model.A === nothing
4528
function MOI.empty!(model::GeometricConicForm{T}) where T
4629
empty!(model.dimension)
@@ -51,9 +34,9 @@ function MOI.empty!(model::GeometricConicForm{T}) where T
5134
end
5235

5336
function MOI.supports_constraint(
54-
model::GeometricConicForm,
55-
::Type{MOI.VectorAffineFunction{Float64}},
56-
::Type{S}) where S <: MOI.AbstractVectorSet
37+
model::GeometricConicForm{T},
38+
::Type{MOI.VectorAffineFunction{T}},
39+
::Type{S}) where {T, S <: MOI.AbstractVectorSet}
5740
return haskey(model.cone_types_dict, S)
5841
end
5942

@@ -65,15 +48,15 @@ function _allocate_variables(model::GeometricConicForm{T, AT, VT}, vis_src, idxm
6548
return
6649
end
6750

68-
function rows(model::GeometricConicForm, ci::CI{MOI.VectorAffineFunction{Float64}})
51+
function rows(model::GeometricConicForm{T}, ci::CI{MOI.VectorAffineFunction{T}}) where T
6952
return ci.value .+ (1:model.dimension[ci.value])
7053
end
7154

7255
function MOI.set(::GeometricConicForm, ::MOI.VariablePrimalStart,
7356
::MOI.VariableIndex, ::Nothing)
7457
end
75-
function MOI.set(model::GeometricConicForm, ::MOI.VariablePrimalStart,
76-
vi::MOI.VariableIndex, value::Float64)
58+
function MOI.set(model::GeometricConicForm{T}, ::MOI.VariablePrimalStart,
59+
vi::MOI.VariableIndex, value::T) where T
7760
model.primal[vi.value] = value
7861
end
7962
function MOI.set(::GeometricConicForm, ::MOI.ConstraintPrimalStart,
@@ -97,8 +80,8 @@ function MOI.set(model::GeometricConicForm, ::MOI.ObjectiveSense, sense::MOI.Opt
9780
end
9881
variable_index_value(t::MOI.ScalarAffineTerm) = t.variable_index.value
9982
variable_index_value(t::MOI.VectorAffineTerm) = variable_index_value(t.scalar_term)
100-
function MOI.set(model::GeometricConicForm, ::MOI.ObjectiveFunction,
101-
f::MOI.ScalarAffineFunction{Float64})
83+
function MOI.set(model::GeometricConicForm{T}, ::MOI.ObjectiveFunction,
84+
f::MOI.ScalarAffineFunction{T}) where T
10285
c = Vector(sparsevec(variable_index_value.(f.terms), MOI.coefficient.(f.terms),
10386
model.A.n))
10487
model.objective_constant = f.constant
@@ -116,8 +99,8 @@ function _allocate_constraint(model::GeometricConicForm, src, indexmap, cone_id,
11699
return ci, offset, func
117100
end
118101

119-
function _allocate_constraints(model::GeometricConicForm, src, indexmap, cone_id, ::Type{S}) where S
120-
cis = MOI.get(src, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, S}())
102+
function _allocate_constraints(model::GeometricConicForm{T}, src, indexmap, cone_id, ::Type{S}) where {T, S}
103+
cis = MOI.get(src, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{T}, S}())
121104
return map(cis) do ci
122105
_allocate_constraint(model, src, indexmap, cone_id, ci)
123106
end
@@ -142,7 +125,7 @@ function _load_constraints(model::GeometricConicForm, src, indexmap, cone_offset
142125
end
143126
end
144127

145-
function MOI.copy_to(dest::GeometricConicForm, src::MOI.ModelLike; copy_names::Bool=true)
128+
function MOI.copy_to(dest::GeometricConicForm{T}, src::MOI.ModelLike; copy_names::Bool=true) where T
146129
MOI.empty!(dest)
147130

148131
vis_src = MOI.get(src, MOI.ListOfVariableIndices())
@@ -151,7 +134,7 @@ function MOI.copy_to(dest::GeometricConicForm, src::MOI.ModelLike; copy_names::B
151134
has_constraints = BitSet()
152135
for (F, S) in MOI.get(src, MOI.ListOfConstraints())
153136
i = get(dest.cone_types_dict, S, nothing)
154-
if i === nothing || F != MOI.VectorAffineFunction{Float64}
137+
if i === nothing || F != MOI.VectorAffineFunction{T}
155138
throw(MOI.UnsupportedConstraint{F, S}())
156139
end
157140
push!(has_constraints, i)
@@ -180,5 +163,7 @@ function MOI.copy_to(dest::GeometricConicForm, src::MOI.ModelLike; copy_names::B
180163
offset += dest.num_rows[i]
181164
end
182165

166+
final_touch(dest.A)
167+
183168
return idxmap
184169
end

src/sparse_matrix.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,20 @@ end
4242
function load_terms(A::SparseMatrixCSRtoCSC, indexmap, func, offset)
4343
_load_terms(A.colptr, A.rowval, A.nzval, indexmap, func.terms, offset)
4444
end
45+
46+
"""
47+
Base.convert(::Type{SparseMatrixCSC{Tv, Ti}}, A::SparseMatrixCSRtoCSC{Tv, Ti}) where {Tv, Ti}
48+
49+
Converts `A` to a `SparseMatrixCSC`. Note that the field `A.nzval` is **not
50+
copied** so if `A` is modified after the call of this function, it can still
51+
affect the value returned.
52+
"""
53+
function Base.convert(::Type{SparseMatrixCSC{Tv, Ti}}, A::SparseMatrixCSRtoCSC{Tv, Ti}) where {Tv, Ti}
54+
return SparseMatrixCSC{Tv, Ti}(
55+
A.m,
56+
A.n,
57+
A.colptr .+ one(Ti),
58+
A.rowval .+ one(Ti),
59+
A.nzval
60+
)
61+
end

test/conic_form.jl

Lines changed: 139 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,148 @@
1-
CONIC_OPTIMIZERS = [SCS.Optimizer, ProxSDP.Optimizer, COSMO.Optimizer]
2-
3-
@testset "MOI to MatOI conversion 1" begin
4-
# _psd1test: https://github.com/jump-dev/MathOptInterface.jl/blob/master/src/Test/contconic.jl#L2417
5-
6-
for optimizer in CONIC_OPTIMIZERS
7-
model = MOI.instantiate(optimizer, with_bridge_type=Float64)
8-
δ = (1 + (3*√2+2)*√(-116*√2+166) / 14) / 2
9-
ε = ((1 - 2*(2-1)*δ^2) / (2-√2))
10-
y2 = 1 - ε*δ
11-
y1 = 1 - 2*y2
12-
obj = y1 + y2/2
13-
k = -2*δ/ε
14-
x2 = ((3-2obj)*(2+k^2)-4) / (4*(2+k^2)-4*√2)
15-
α = (3-2obj-4x2)/2
16-
β = k*α
17-
18-
X = MOI.add_variables(model, 6)
19-
x = MOI.add_variables(model, 3)
20-
21-
vov = MOI.VectorOfVariables(X)
22-
23-
cX = MOI.add_constraint(
24-
model,
25-
MOI.VectorAffineFunction{Float64}(vov), MOI.PositiveSemidefiniteConeTriangle(3)
26-
)
1+
function _test_matrix_equal(A::SparseMatrixCSC, B::SparseMatrixCSC)
2+
@test A.m == B.m
3+
@test A.n == B.n
4+
@test A.nzval B.nzval atol=ATOL rtol=RTOL
5+
@test A.rowval == B.rowval
6+
@test A.colptr == B.colptr
7+
end
8+
function _test_matrix_equal(A::MatOI.SparseMatrixCSRtoCSC, B::SparseMatrixCSC)
9+
@test A.m == B.m
10+
@test A.n == B.n
11+
@test A.nzval B.nzval atol=ATOL rtol=RTOL
12+
@test A.rowval == B.rowval .- 1
13+
@test A.colptr == B.colptr .- 1
14+
sA = convert(typeof(B), A)
15+
@test typeof(sA) == typeof(B)
16+
_test_matrix_equal(sA, B)
17+
end
2718

28-
cx = MOI.add_constraint(
29-
model,
30-
MOI.VectorAffineFunction{Float64}(MOI.VectorOfVariables(x)), MOI.SecondOrderCone(3)
19+
# _psd1test: https://github.com/jump-dev/MathOptInterface.jl/blob/master/src/Test/contconic.jl#L2417
20+
function psd1(::Type{T}) where T
21+
# We use `MockOptimizer` to have indices xor'ed so that it tests that we don't assumes they are `1:n`.
22+
model = MOIU.MockOptimizer(MOIU.Model{T}())
23+
24+
X = MOI.add_variables(model, 6)
25+
x = MOI.add_variables(model, 3)
26+
27+
vov = MOI.VectorOfVariables(X)
28+
29+
cX = MOI.add_constraint(
30+
model,
31+
MOI.VectorAffineFunction{T}(vov), MOI.PositiveSemidefiniteConeTriangle(3)
32+
)
33+
34+
cx = MOI.add_constraint(
35+
model,
36+
MOI.VectorAffineFunction{T}(MOI.VectorOfVariables(x)), MOI.SecondOrderCone(3)
37+
)
38+
39+
c1 = MOI.add_constraint(
40+
model,
41+
MOI.VectorAffineFunction(
42+
MOI.VectorAffineTerm.(1:1, MOI.ScalarAffineTerm.(ones(T, 4), [X[1], X[3], X[end], x[1]])),
43+
[-one(T)]
44+
),
45+
MOI.Zeros(1)
46+
)
47+
48+
c2 = MOI.add_constraint(
49+
model,
50+
MOI.VectorAffineFunction(
51+
MOI.VectorAffineTerm.(1:1, MOI.ScalarAffineTerm.(T[1, 2, 1, 2, 2, 1, 1, 1], [X; x[2]; x[3]])),
52+
[-inv(T(2))]
53+
),
54+
MOI.Zeros(1)
55+
)
56+
57+
objXidx = [1:3; 5:6]
58+
objXcoefs = 2ones(T, 5)
59+
MOI.set(
60+
model,
61+
MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
62+
MOI.ScalarAffineFunction(
63+
MOI.ScalarAffineTerm.([objXcoefs; one(T)], [X[objXidx]; x[1]]),
64+
zero(T)
3165
)
32-
33-
c1 = MOI.add_constraint(
34-
model,
35-
MOI.VectorAffineFunction(
36-
MOI.VectorAffineTerm.(1:1, MOI.ScalarAffineTerm.([1., 1., 1., 1.], [X[1], X[3], X[end], x[1]])),
37-
[-1.0]
38-
),
39-
MOI.Zeros(1)
66+
)
67+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
68+
69+
conic_form = MatOI.GeometricConicForm{T, MatOI.SparseMatrixCSRtoCSC{T, Int}, Vector{T}}([MOI.PositiveSemidefiniteConeTriangle, MOI.SecondOrderCone, MOI.Zeros])
70+
index_map = MOI.copy_to(conic_form, model)
71+
72+
@test conic_form.c' T[2 2 2 0 2 2 1 0 0]
73+
@test conic_form.b' T[0 0 0 0 0 0 0 0 0 -1 -inv(T(2))]
74+
_test_matrix_equal(
75+
conic_form.A,
76+
SparseMatrixCSC(
77+
11, 9,
78+
[1, 4, 6, 9, 11, 13, 16, 18, 20, 22],
79+
[1, 10, 11, 2, 11, 3, 10, 11, 4, 11, 5, 11, 6, 10, 11, 7, 10, 8, 11, 9, 11],
80+
T[-1, -1, -1, -1, -2, -1, -1, -1, -1, -2, -1, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1],
4081
)
41-
42-
c2 = MOI.add_constraint(
43-
model,
44-
MOI.VectorAffineFunction(
45-
MOI.VectorAffineTerm.(1:1, MOI.ScalarAffineTerm.([1., 2, 1, 2, 2, 1, 1, 1], [X; x[2]; x[3]])),
46-
[-0.5]
47-
),
48-
MOI.Zeros(1)
49-
)
50-
51-
objXidx = [1:3; 5:6]
52-
objXcoefs = 2*ones(5)
53-
MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(),
54-
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([objXcoefs; 1.0], [X[objXidx]; x[1]]), 0.0))
55-
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
56-
57-
MatModel = MatOI.get_conic_form(Float64, model, [cX; cx; c1; c2])
58-
59-
@test MatModel.c' [2. 2. 2. 0. 2. 2. 1. 0. 0.]
60-
@test MatModel.b' [0. 0. 0. 0. 0. 0. 0. 0. 0. -1. -0.5 ]
61-
@test MatModel.A.nzval [-1.0, -1.0, -1.0, -1.0, -2.0, -1.0, -1.0, -1.0, -1.0, -2.0, -1.0, -2.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0] atol=ATOL rtol=RTOL
62-
end
82+
)
6383
end
6484

65-
@testset "MOI to MatOI conversion 2" begin
66-
# find equivalent diffcp program here - https://github.com/AKS1996/jump-gsoc-2020/blob/master/diffcp_sdp_3_py.ipynb
67-
68-
for optimizer in CONIC_OPTIMIZERS
69-
model = MOI.instantiate(optimizer, with_bridge_type=Float64)
70-
71-
x = MOI.add_variables(model, 7)
72-
@test MOI.get(model, MOI.NumberOfVariables()) == 7
73-
74-
η = 10.0
75-
76-
c1 = MOI.add_constraint(
77-
model,
78-
MOI.VectorAffineFunction(
79-
MOI.VectorAffineTerm.(1, MOI.ScalarAffineTerm.(-1.0, x[1:6])),
80-
[η]
81-
),
82-
MOI.Nonnegatives(1)
85+
# Taken from `MOI.Test.psdt2test`.
86+
# find equivalent diffcp program here - https://github.com/AKS1996/jump-gsoc-2020/blob/master/diffcp_sdp_3_py.ipynb
87+
function psd2(::Type{T}, η::T = T(10), α::T = T(4)/T(5), δ::T = T(9)/T(10)) where T
88+
# We use `MockOptimizer` to have indices xor'ed so that it tests that we don't assumes they are `1:n`.
89+
model = MOIU.MockOptimizer(MOIU.Model{T}())
90+
91+
x = MOI.add_variables(model, 7)
92+
93+
c1 = MOI.add_constraint(
94+
model,
95+
MOI.VectorAffineFunction(
96+
MOI.VectorAffineTerm.(1, MOI.ScalarAffineTerm.(-one(T), x[1:6])),
97+
[η]
98+
),
99+
MOI.Nonnegatives(1)
100+
)
101+
c2 = MOI.add_constraint(model, MOI.VectorAffineFunction(MOI.VectorAffineTerm.(1:6, MOI.ScalarAffineTerm.(one(T), x[1:6])), zeros(T, 6)), MOI.Nonnegatives(6))
102+
103+
c3 = MOI.add_constraint(
104+
model,
105+
MOI.VectorAffineFunction(
106+
MOI.VectorAffineTerm.(
107+
[fill(1, 7); fill(2, 5); fill(3, 6)],
108+
MOI.ScalarAffineTerm.(
109+
[ δ/2, α, δ, δ/4, δ/8, 0, -1,
110+
-δ/(2*√2), -δ/4, 0, -δ/(8*√2), 0,
111+
δ/2, δ-α, 0, δ/8, δ/4, -1],
112+
[x[1:7]; x[1:3]; x[5:6]; x[1:3]; x[5:7]])),
113+
zeros(T, 3)
114+
),
115+
MOI.PositiveSemidefiniteConeTriangle(2)
116+
)
117+
c4 = MOI.add_constraint(
118+
model,
119+
MOI.VectorAffineFunction(
120+
MOI.VectorAffineTerm.(1, MOI.ScalarAffineTerm.(zero(T), [x[1:3]; x[5:6]])),
121+
[zero(T)]
122+
),
123+
MOI.Zeros(1)
124+
)
125+
126+
MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(one(T), x[7])], zero(T)))
127+
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
128+
129+
conic_form = MatOI.GeometricConicForm{T, MatOI.SparseMatrixCSRtoCSC{T, Int}, Vector{T}}([MOI.Nonnegatives, MOI.Zeros, MOI.PositiveSemidefiniteConeTriangle])
130+
index_map = MOI.copy_to(conic_form, model)
131+
132+
@test conic_form.c [zeros(T, 6); one(T)]
133+
@test conic_form.b [T(10); zeros(T, 10)]
134+
_test_matrix_equal(
135+
conic_form.A,
136+
SparseMatrixCSC(
137+
11, 7,
138+
[1, 6, 11, 14, 17, 22, 25, 27],
139+
[1, 2, 9, 10, 11, 1, 3, 9, 10, 11, 1, 4, 9, 1, 5, 9, 1, 6, 9, 10, 11, 1, 7, 11, 9, 11],
140+
T[1.0, -1.0, -0.45, 0.318198, -0.45, 1.0, -1.0, -0.8, 0.225, -0.1, 1.0, -1.0, -0.9, 1.0, -1.0, -0.225, 1.0, -1.0, -0.1125, 0.0795495, -0.1125, 1.0, -1.0, -0.225, 1.0, 1.0],
83141
)
84-
c2 = MOI.add_constraint(model, MOI.VectorAffineFunction(MOI.VectorAffineTerm.(1:6, MOI.ScalarAffineTerm.(1.0, x[1:6])), zeros(6)), MOI.Nonnegatives(6))
85-
α = 0.8
86-
δ = 0.9
87-
c3 = MOI.add_constraint(model, MOI.VectorAffineFunction(MOI.VectorAffineTerm.([fill(1, 7); fill(2, 5); fill(3, 6)],
88-
MOI.ScalarAffineTerm.(
89-
[ δ/2, α, δ, δ/4, δ/8, 0.0, -1.0,
90-
-δ/(2*√2), -δ/4, 0, -δ/(8*√2), 0.0,
91-
δ/2, δ-α, 0, δ/8, δ/4, -1.0],
92-
[x[1:7]; x[1:3]; x[5:6]; x[1:3]; x[5:7]])),
93-
zeros(3)), MOI.PositiveSemidefiniteConeTriangle(2))
94-
c4 = MOI.add_constraint(
95-
model,
96-
MOI.VectorAffineFunction(
97-
MOI.VectorAffineTerm.(1, MOI.ScalarAffineTerm.(0.0, [x[1:3]; x[5:6]])),
98-
[0.0]
99-
),
100-
MOI.Zeros(1)
101-
)
102-
103-
MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x[7])], 0.0))
104-
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
105-
106-
MatModel = MatOI.get_conic_form(Float64, model, [c1; c2; c3; c4])
107-
108-
@test MatModel.c [-0.0, -0.0, -0.0, -0.0, -0.0, -0.0, -1.0]
109-
@test MatModel.b [10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
110-
@test MatModel.A.nzval [1.0, -1.0, -0.45, 0.318198, -0.45, 1.0, -1.0, -0.8, 0.225, -0.1, 1.0, -1.0, -0.9, 1.0, -1.0, -0.225, 1.0, -1.0, -0.1125, 0.0795495, -0.1125, 1.0, -1.0, -0.225, 1.0, 1.0] atol=ATOL rtol=RTOL
111-
end
142+
)
112143
end
113144

114-
@testset "Testing minor utilities" begin
115-
model = MOI.instantiate(SCS.Optimizer, with_bridge_type=Float64)
116-
cf = MatOI.get_conic_form(Float64,model,[])
117-
@test MOI.is_empty(cf) == false
118-
MOI.empty!(cf)
119-
@test MOI.is_empty(cf) == true
145+
@testset "PSD $T" for T in [Float64, BigFloat]
146+
psd1(T)
147+
psd2(T)
120148
end

0 commit comments

Comments
 (0)