Skip to content

Commit d64c93d

Browse files
authored
MOI to MatOI (#7)
* MOI to MatOI * parameterize model; remove ConeData * one more vector template for ConicForm * in light of codecov * update docs * minor fix * fix build * fix build * minor changes * many minor changes * switch to copy_to * remove unused methods * add suggestions * removed unused dependencies * fix dependencies * coverage changes * removed SCS specific scaling
1 parent f42e602 commit d64c93d

File tree

6 files changed

+448
-1
lines changed

6 files changed

+448
-1
lines changed

Project.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ 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"
1720
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1821

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

src/MatrixOptInterface.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ using MathOptInterface
55
const MOI = MathOptInterface
66
const MOIU = MathOptInterface.Utilities
77

8+
const CI = MOI.ConstraintIndex
9+
const VI = MOI.VariableIndex
10+
811
# export MatrixOptimizer
912

1013
@enum ConstraintSense EQUAL_TO GREATER_THAN LESS_THAN INTERVAL
@@ -57,6 +60,8 @@ end
5760

5861
@enum VariableType CONTINUOUS INTEGER BINARY
5962

63+
include("sparse_matrix.jl")
64+
include("conic_form.jl")
6065
include("matrix_input.jl")
6166
include("change_form.jl")
6267

src/conic_form.jl

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
mutable struct ConicForm{T, AT, VT, C} <: MOI.ModelLike
2+
num_rows::Vector{Int}
3+
dimension::Dict{Int, Int}
4+
sense::MOI.OptimizationSense
5+
objective_constant::T # The objective
6+
A::Union{Nothing, AT} # The constraints
7+
b::VT # `b - Ax in cones`
8+
c::VT # `sense c'x + objective_constant`
9+
cone_types::C
10+
11+
function ConicForm{T, AT, VT}(cone_types) where {T, AT, VT}
12+
model = new{T, AT, VT, typeof(cone_types)}()
13+
model.cone_types = cone_types
14+
model.num_rows = zeros(Int, length(cone_types))
15+
model.dimension = Dict{Int, Int}()
16+
model.A = nothing
17+
return model
18+
end
19+
end
20+
21+
# The ordering of CONES matches SCS
22+
const CONES = Dict(
23+
MOI.Zeros => 1,
24+
MOI.Nonnegatives => 2,
25+
MOI.SecondOrderCone => 3,
26+
MOI.PositiveSemidefiniteConeTriangle => 4,
27+
MOI.ExponentialCone => 5,
28+
MOI.DualExponentialCone => 6
29+
)
30+
31+
_set_type(::MOI.ConstraintIndex{F,S}) where {F,S} = S
32+
cons_offset(conic::MOI.Zeros) = conic.dimension
33+
cons_offset(conic::MOI.Nonnegatives) = conic.dimension
34+
cons_offset(conic::MOI.SecondOrderCone) = conic.dimension
35+
cons_offset(conic::MOI.PositiveSemidefiniteConeTriangle) = Int64((conic.side_dimension*(conic.side_dimension+1))/2)
36+
37+
function get_conic_form(::Type{T}, model::M, con_idx) where {T, M <: MOI.AbstractOptimizer}
38+
# reorder constraints
39+
cis = sort(
40+
con_idx,
41+
by = x->CONES[_set_type(x)]
42+
)
43+
44+
# extract cones
45+
cones = _set_type.(cis)
46+
cones = unique(cones)
47+
48+
conic = ConicForm{T, SparseMatrixCSRtoCSC{Int64}, Array{T, 1}}(Tuple(cones))
49+
50+
idxmap = MOI.copy_to(conic, model)
51+
52+
# fix optimization sense
53+
if MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE
54+
conic.c = -conic.c
55+
end
56+
57+
return conic
58+
end
59+
60+
MOI.is_empty(model::ConicForm) = model.A === nothing
61+
function MOI.empty!(model::ConicForm{T}) where T
62+
empty!(model.dimension)
63+
fill!(model.num_rows, 0)
64+
model.A = nothing
65+
model.sense = MOI.FEASIBILITY_SENSE
66+
model.objective_constant = zero(T)
67+
end
68+
69+
function _first(::Type{S}, idx::Int, ::Type{S}, args::Vararg{Type, N}) where {S, N}
70+
return idx
71+
end
72+
function _first(::Type{S}, idx::Int, ::Type, args::Vararg{Type, N}) where {S, N}
73+
return _first(S, idx + 1, args...)
74+
end
75+
_first(::Type, idx::Int) = nothing
76+
77+
_findfirst(::Type{S}, sets::Tuple) where {S} = _first(S, 1, sets...)
78+
79+
function MOI.supports_constraint(
80+
model::ConicForm,
81+
::Type{MOI.VectorAffineFunction{Float64}},
82+
::Type{S}) where S <: MOI.AbstractVectorSet
83+
return _findfirst(S, model.cone_types) !== nothing
84+
end
85+
86+
function _allocate_variables(model::ConicForm{T, AT, VT}, vis_src, idxmap) where {T, AT, VT}
87+
model.A = AT(length(vis_src))
88+
for (i, vi) in enumerate(vis_src)
89+
idxmap[vi] = MOI.VariableIndex(i)
90+
end
91+
return
92+
end
93+
94+
function rows(model::ConicForm, ci::CI{MOI.VectorAffineFunction{Float64}})
95+
return ci.value .+ (1:model.dimension[ci.value])
96+
end
97+
98+
function MOI.set(::ConicForm, ::MOI.VariablePrimalStart,
99+
::MOI.VariableIndex, ::Nothing)
100+
end
101+
function MOI.set(model::ConicForm, ::MOI.VariablePrimalStart,
102+
vi::MOI.VariableIndex, value::Float64)
103+
model.primal[vi.value] = value
104+
end
105+
function MOI.set(::ConicForm, ::MOI.ConstraintPrimalStart,
106+
::MOI.ConstraintIndex, ::Nothing)
107+
end
108+
function MOI.set(model::ConicForm, ::MOI.ConstraintPrimalStart,
109+
ci::MOI.ConstraintIndex, value)
110+
offset = constroffset(model, ci)
111+
model.slack[rows(model, ci)] .= value
112+
end
113+
function MOI.set(::ConicForm, ::MOI.ConstraintDualStart,
114+
::MOI.ConstraintIndex, ::Nothing)
115+
end
116+
function MOI.set(model::ConicForm, ::MOI.ConstraintDualStart,
117+
ci::MOI.ConstraintIndex, value)
118+
offset = constroffset(model, ci)
119+
model.dual[rows(model, ci)] .= value
120+
end
121+
function MOI.set(model::ConicForm, ::MOI.ObjectiveSense, sense::MOI.OptimizationSense)
122+
model.sense = sense
123+
end
124+
variable_index_value(t::MOI.ScalarAffineTerm) = t.variable_index.value
125+
variable_index_value(t::MOI.VectorAffineTerm) = variable_index_value(t.scalar_term)
126+
function MOI.set(model::ConicForm, ::MOI.ObjectiveFunction,
127+
f::MOI.ScalarAffineFunction{Float64})
128+
c = Vector(sparsevec(variable_index_value.(f.terms), MOI.coefficient.(f.terms),
129+
model.A.n))
130+
model.objective_constant = f.constant
131+
model.c = c
132+
return nothing
133+
end
134+
135+
function _allocate_constraint(model::ConicForm, src, indexmap, cone_id, ci)
136+
# TODO use `CanonicalConstraintFunction`
137+
func = MOI.get(src, MOI.ConstraintFunction(), ci)
138+
func = MOIU.is_canonical(func) ? func : MOI.Utilities.canonical(func)
139+
allocate_terms(model.A, indexmap, func)
140+
offset = model.num_rows[cone_id]
141+
model.num_rows[cone_id] = offset + MOI.output_dimension(func)
142+
return ci, offset, func
143+
end
144+
145+
function _allocate_constraints(model::ConicForm, src, indexmap, cone_id, ::Type{S}) where S
146+
cis = MOI.get(src, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, S}())
147+
return map(cis) do ci
148+
_allocate_constraint(model, src, indexmap, cone_id, ci)
149+
end
150+
end
151+
152+
function _load_variables(model::ConicForm, nvars::Integer)
153+
m = sum(model.num_rows)
154+
model.A.m = m
155+
model.b = zeros(m)
156+
model.c = zeros(model.A.n)
157+
allocate_nonzeros(model.A)
158+
end
159+
160+
function _load_constraints(model::ConicForm, src, indexmap, cone_offset, i, cache)
161+
for (ci_src, offset_in_cone, func) in cache
162+
offset = cone_offset + offset_in_cone
163+
set = MOI.get(src, MOI.ConstraintSet(), ci_src)
164+
load_terms(model.A, indexmap, func, offset)
165+
copyto!(model.b, offset + 1, func.constants)
166+
model.dimension[offset] = MOI.output_dimension(func)
167+
indexmap[ci_src] = typeof(ci_src)(offset)
168+
end
169+
end
170+
171+
function MOI.copy_to(dest::ConicForm, src::MOI.ModelLike; copy_names::Bool=true)
172+
MOI.empty!(dest)
173+
174+
vis_src = MOI.get(src, MOI.ListOfVariableIndices())
175+
idxmap = MOIU.IndexMap()
176+
177+
has_constraints = BitSet()
178+
for (F, S) in MOI.get(src, MOI.ListOfConstraints())
179+
i = _findfirst(S, dest.cone_types)
180+
if i === nothing || F != MOI.VectorAffineFunction{Float64}
181+
throw(MOI.UnsupportedConstraint{F, S}())
182+
end
183+
push!(has_constraints, i)
184+
end
185+
186+
_allocate_variables(dest, vis_src, idxmap)
187+
188+
# Allocate constraints
189+
caches = map(collect(has_constraints)) do i
190+
_allocate_constraints(dest, src, idxmap, i, dest.cone_types[i])
191+
end
192+
193+
# Load variables
194+
_load_variables(dest, length(vis_src))
195+
196+
# Set variable attributes
197+
MOIU.pass_attributes(dest, src, copy_names, idxmap, vis_src)
198+
199+
# Set model attributes
200+
MOIU.pass_attributes(dest, src, copy_names, idxmap)
201+
202+
# Load constraints
203+
offset = 0
204+
for (i, cache) in zip(has_constraints, caches)
205+
_load_constraints(dest, src, idxmap, offset, i, cache)
206+
offset += dest.num_rows[i]
207+
end
208+
209+
return idxmap
210+
end

src/sparse_matrix.jl

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
mutable struct SparseMatrixCSRtoCSC{T}
2+
m::Int # Number of rows
3+
n::Int # Number of columns
4+
colptr::Vector{T}
5+
rowval::Vector{T}
6+
nzval::Vector{Float64}
7+
function SparseMatrixCSRtoCSC{T}(n) where T
8+
A = new()
9+
A.n = n
10+
A.colptr = zeros(T, n + 1)
11+
return A
12+
end
13+
end
14+
function allocate_nonzeros(A::SparseMatrixCSRtoCSC{T}) where T
15+
for i in 3:length(A.colptr)
16+
A.colptr[i] += A.colptr[i - 1]
17+
end
18+
A.rowval = Vector{T}(undef, A.colptr[end])
19+
A.nzval = Vector{Float64}(undef, A.colptr[end])
20+
end
21+
function final_touch(A::SparseMatrixCSRtoCSC)
22+
for i in length(A.colptr):-1:2
23+
A.colptr[i] = A.colptr[i - 1]
24+
end
25+
A.colptr[1] = 0
26+
end
27+
function _allocate_terms(colptr, indexmap, terms)
28+
for term in terms
29+
colptr[indexmap[term.scalar_term.variable_index].value + 1] += 1
30+
end
31+
end
32+
function allocate_terms(A::SparseMatrixCSRtoCSC, indexmap, func)
33+
_allocate_terms(A.colptr, indexmap, func.terms)
34+
end
35+
function _load_terms(colptr, rowval, nzval, indexmap, terms, offset)
36+
for term in terms
37+
ptr = colptr[indexmap[term.scalar_term.variable_index].value] += 1
38+
rowval[ptr] = offset + term.output_index - 1
39+
nzval[ptr] = -term.scalar_term.coefficient
40+
end
41+
end
42+
function load_terms(A::SparseMatrixCSRtoCSC, indexmap, func, offset)
43+
_load_terms(A.colptr, A.rowval, A.nzval, indexmap, func.terms, offset)
44+
end

0 commit comments

Comments
 (0)