Skip to content

Commit f43023f

Browse files
committed
Add support for rank-1 constraints
1 parent 324333e commit f43023f

File tree

2 files changed

+89
-25
lines changed

2 files changed

+89
-25
lines changed

src/MOI_wrapper.jl

Lines changed: 85 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
1414
# * `-d` means a diagonal block with diagonal of length `d`
1515
# * `d` means a symmetric `d x d` block
1616
blockdims::Vector{Int}
17+
# MOI variable index -> rank 1 matrix it corresponds to
18+
rank_one::Vector{Union{Nothing,MOI.LowRankMatrix{Cdouble}}}
19+
# To avoid it being free'd
20+
cached_ind::Vector{Vector{Cint}}
1721
varmap::Vector{Tuple{Int, Int, Int}} # Variable Index vi -> blk, i, j
1822
# If `blockdims[i] < 0`, `blk[i]` is the offset in `lpdvars`.
1923
# That is the **sum of length** of diagonal block before
@@ -33,15 +37,18 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
3337

3438
y::Vector{Cdouble}
3539

40+
solve_time::Float64
3641
silent::Bool
3742
options::Dict{Symbol,Any}
3843
function Optimizer()
3944
optimizer = new(
4045
C_NULL, C_NULL, 0.0, 1, Cdouble[], Int[],
46+
Union{Nothing,MOI.LowRankMatrix{Cdouble}}[],
47+
Vector{Cint}[],
4148
Tuple{Int, Int, Int}[], Int[], C_NULL, 0,
4249
Int[], Int[], Cdouble[],
4350
Vector{Int}[], Vector{Cdouble}[],
44-
Cdouble[], false, Dict{Symbol, Any}(),
51+
Cdouble[], NaN, false, Dict{Symbol, Any}(),
4552
)
4653
finalizer(_free, optimizer)
4754
return optimizer
@@ -64,6 +71,8 @@ function MOI.empty!(optimizer::Optimizer)
6471
optimizer.objective_sign = 1
6572
empty!(optimizer.b)
6673
empty!(optimizer.blockdims)
74+
empty!(optimizer.rank_one)
75+
empty!(optimizer.cached_ind)
6776
empty!(optimizer.varmap)
6877
empty!(optimizer.blk)
6978
optimizer.nlpdrows = 0
@@ -73,6 +82,8 @@ function MOI.empty!(optimizer::Optimizer)
7382
empty!(optimizer.sdpdinds)
7483
empty!(optimizer.sdpdcoefs)
7584
empty!(optimizer.y)
85+
optimizer.solve_time = NaN
86+
return
7687
end
7788

7889
function MOI.is_empty(optimizer::Optimizer)
@@ -193,8 +204,20 @@ function MOI.supports(
193204
end
194205

195206
MOI.supports_add_constrained_variables(::Optimizer, ::Type{MOI.Reals}) = false
196-
const SupportedSets = Union{MOI.Nonnegatives, MOI.PositiveSemidefiniteConeTriangle}
207+
208+
const _SetWithDotProd = MOI.SetWithDotProducts{
209+
MOI.PositiveSemidefiniteConeTriangle,
210+
MOI.TriangleVectorization{Cdouble,MOI.LowRankMatrix{Cdouble}},
211+
}
212+
213+
const SupportedSets = Union{
214+
MOI.Nonnegatives,
215+
MOI.PositiveSemidefiniteConeTriangle,
216+
_SetWithDotProd,
217+
}
218+
197219
MOI.supports_add_constrained_variables(::Optimizer, ::Type{<:SupportedSets}) = true
220+
198221
function MOI.supports_constraint(
199222
::Optimizer, ::Type{MOI.ScalarAffineFunction{Cdouble}},
200223
::Type{MOI.EqualTo{Cdouble}})
@@ -206,6 +229,7 @@ function new_block(optimizer::Optimizer, set::MOI.Nonnegatives)
206229
blk = length(optimizer.blockdims)
207230
for i in 1:MOI.dimension(set)
208231
push!(optimizer.varmap, (blk, i, i))
232+
push!(optimizer.rank_one, nothing)
209233
end
210234
end
211235

@@ -215,13 +239,26 @@ function new_block(optimizer::Optimizer, set::MOI.PositiveSemidefiniteConeTriang
215239
for j in 1:set.side_dimension
216240
for i in 1:j
217241
push!(optimizer.varmap, (blk, i, j))
242+
push!(optimizer.rank_one, nothing)
218243
end
219244
end
220245
end
221246

247+
function new_block(model::Optimizer, set::_SetWithDotProd)
248+
println("______________________Low-Rank")
249+
blk = length(model.blockdims) + 1
250+
for i in eachindex(set.vectors)
251+
push!(model.varmap, (blk, 0, 0))
252+
push!(model.rank_one, set.vectors[i].matrix)
253+
end
254+
new_block(model, set.set)
255+
end
256+
222257
function _add_constrained_variables(optimizer::Optimizer, set::SupportedSets)
223258
offset = length(optimizer.varmap)
224259
new_block(optimizer, set)
260+
@assert length(optimizer.varmap) == offset + MOI.dimension(set)
261+
@assert length(optimizer.rank_one) == offset + MOI.dimension(set)
225262
ci = MOI.ConstraintIndex{MOI.VectorOfVariables, typeof(set)}(offset + 1)
226263
return [MOI.VariableIndex(i) for i in offset .+ (1:MOI.dimension(set))], ci
227264
end
@@ -265,32 +302,51 @@ function constrain_variables_on_creation(
265302
end
266303
end
267304

268-
function _setcoefficient!(m::Optimizer, coef, constr::Integer, blk::Integer, i::Integer, j::Integer)
269-
if m.blockdims[blk] < 0
270-
@assert i == j
271-
push!(m.lpdvars, constr + 1)
272-
push!(m.lpdrows, m.blk[blk] + i - 1) # -1 because indexing starts at 0 in DSDP
273-
push!(m.lpcoefs, coef)
274-
else
275-
sdp = m.blk[blk]
276-
push!(m.sdpdinds[end][sdp], i + (j - 1) * m.blockdims[blk] - 1)
277-
if i != j
278-
coef /= 2
305+
function _setcoefficient!(dest::Optimizer, coef, constr::Integer, vi::MOI.VariableIndex)
306+
blk, i, j = varmap(dest, vi)
307+
rank_one = dest.rank_one[vi.value]
308+
if isnothing(rank_one)
309+
if dest.blockdims[blk] < 0
310+
@assert i == j
311+
push!(dest.lpdvars, constr + 1)
312+
push!(dest.lpdrows, dest.blk[blk] + i - 1) # -1 because indexing starts at 0 in DSDP
313+
push!(dest.lpcoefs, coef)
314+
else
315+
sdp = dest.blk[blk]
316+
push!(dest.sdpdinds[end][sdp], i + (j - 1) * dest.blockdims[blk] - 1)
317+
if i != j
318+
coef /= 2
319+
end
320+
push!(dest.sdpdcoefs[end][sdp], coef)
279321
end
280-
push!(m.sdpdcoefs[end][sdp], coef)
322+
else
323+
d = Cint(dest.blockdims[blk])
324+
push!(dest.cached_ind, collect(Cint(0):(d - 1)))
325+
# We use `Add` and not `Set` because I think (if I interpret the name correctly) that would allow mixing with sparse matrices for the same block and constraint
326+
DSDP.SDPCone.SetARankOneMat(
327+
dest.sdpcone,
328+
dest.blk[blk] - 1,
329+
constr,
330+
d,
331+
coef * rank_one.diagonal[],
332+
0,
333+
last(dest.cached_ind),
334+
collect(eachcol(rank_one.factor)[]),
335+
d,
336+
)
281337
end
282338
end
283339

284340
# Loads objective coefficient α * vi
285-
function load_objective_term!(optimizer::Optimizer, index_map, α, vi::MOI.VariableIndex)
286-
blk, i, j = varmap(optimizer, vi)
341+
function load_objective_term!(optimizer::Optimizer, α, vi::MOI.VariableIndex)
287342
coef = optimizer.objective_sign * α
288-
_setcoefficient!(optimizer, coef, 0, blk, i, j)
343+
_setcoefficient!(optimizer, coef, 0, vi)
289344
end
290345

291346
function _set_A_matrices(m::Optimizer, i)
292347
for (blk, blkdim) in zip(m.blk, m.blockdims)
293-
if blkdim > 0
348+
if blkdim > 0 && !isempty(m.sdpdcoefs[end][blk])
349+
@show (blk - 1, i, blkdim, 1.0, 0, m.sdpdinds[end][blk], m.sdpdcoefs[end][blk], length(m.sdpdcoefs[end][blk]))
294350
SDPCone.SetASparseVecMat(m.sdpcone, blk - 1, i, blkdim, 1.0, 0, m.sdpdinds[end][blk], m.sdpdcoefs[end][blk], length(m.sdpdcoefs[end][blk]))
295351
end
296352
end
@@ -321,6 +377,12 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)
321377
index_map,
322378
MOI.PositiveSemidefiniteConeTriangle,
323379
)
380+
constrain_variables_on_creation(
381+
dest,
382+
src,
383+
index_map,
384+
_SetWithDotProd,
385+
)
324386
vis_src = MOI.get(src, MOI.ListOfVariableIndices())
325387
if length(vis_src) < length(index_map.var_map)
326388
_error(
@@ -392,8 +454,7 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)
392454
_new_A_matrix(dest)
393455
for t in func.terms
394456
if !iszero(t.coefficient)
395-
blk, i, j = varmap(dest, index_map[t.variable])
396-
_setcoefficient!(dest, t.coefficient, k, blk, i, j)
457+
_setcoefficient!(dest, t.coefficient, k, index_map[t.variable])
397458
end
398459
end
399460
_set_A_matrices(dest, k)
@@ -428,7 +489,6 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)
428489
if !iszero(term.coefficient)
429490
load_objective_term!(
430491
dest,
431-
index_map,
432492
term.coefficient,
433493
index_map[term.variable],
434494
)
@@ -449,7 +509,9 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)
449509
end
450510

451511
function MOI.optimize!(m::Optimizer)
512+
start_time = time()
452513
Solve(m.dsdp)
514+
m.solve_time = time() - start_time
453515
# Calling `ComputeX` not right after `Solve` seems to sometime cause segfaults or weird Heisenbug's
454516
# let's call it directly what `DSDP/examples/readsdpa.c` does
455517
ComputeX(m.dsdp)
@@ -459,6 +521,8 @@ function MOI.optimize!(m::Optimizer)
459521
return
460522
end
461523

524+
MOI.get(optimizer::Optimizer, ::MOI.SolveTimeSec) = optimizer.solve_time
525+
462526
function MOI.get(m::Optimizer, ::MOI.RawStatusString)
463527
if m.dsdp == C_NULL
464528
return "`optimize!` not called"

src/sdpcone.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ function SetADenseVecMat(sdpcone::SDPConeT, arg2::Integer, arg3::Integer, arg4::
5353
@dsdp_ccall SDPConeSetADenseVecMat (SDPConeT, Cint, Cint, Cint, Cdouble, Ptr{Cdouble}, Cint) sdpcone arg2 arg3 arg4 arg5 arg6 arg7
5454
end
5555

56-
function SetARankOneMat(sdpcone::SDPConeT, arg2::Integer, arg3::Integer, arg4::Integer, arg5::Cdouble, arg6::Integer, arg7::Vector{Cint}, arg8::Vector{Cdouble}, arg9::Integer)
57-
@dsdp_ccall SDPConeSetARankOneMat (SDPConeT, Cint, Cint, Cint, Cdouble, Cint, Ptr{Cint}, Ptr{Cdouble}, Cint) sdpcone arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9
56+
function SetARankOneMat(sdpcone::SDPConeT, blockj::Integer, vari::Integer, n::Integer, alpha::Cdouble, ishift::Integer, ind::Vector{Cint}, val::Vector{Cdouble}, nnz::Integer)
57+
@dsdp_ccall SDPConeSetARankOneMat (SDPConeT, Cint, Cint, Cint, Cdouble, Cint, Ptr{Cint}, Ptr{Cdouble}, Cint) sdpcone blockj vari n alpha ishift ind val nnz
5858
end
5959

6060
function SetConstantMat(sdpcone::SDPConeT, arg2::Integer, arg3::Integer, arg4::Integer, arg5::Cdouble)
@@ -93,8 +93,8 @@ function AddIdentity(sdpcone::SDPConeT, arg2::Integer, arg3::Integer, arg4::Inte
9393
@dsdp_ccall SDPConeAddIdentity (SDPConeT, Cint, Cint, Cint, Cdouble) sdpcone arg2 arg3 arg4 arg5
9494
end
9595

96-
function AddARankOneMat(sdpcone::SDPConeT, arg2::Integer, arg3::Integer, arg4::Integer, arg5::Cdouble, arg6::Integer, arg7::Vector{Cint}, arg8::Vector{Cdouble}, arg9::Integer)
97-
@dsdp_ccall SDPConeAddARankOneMat (SDPConeT, Cint, Cint, Cint, Cdouble, Cint, Ptr{Cint}, Ptr{Cdouble}, Cint) sdpcone arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9
96+
function AddARankOneMat(sdpcone::SDPConeT, blockj::Integer, vari::Integer, n::Integer, alpha::Cdouble, ishift::Integer, ind::Vector{Cint}, val::Vector{Cdouble}, nnz::Integer)
97+
@dsdp_ccall SDPConeAddARankOneMat (SDPConeT, Cint, Cint, Cint, Cdouble, Cint, Ptr{Cint}, Ptr{Cdouble}, Cint) sdpcone blockj vari n alpha ishift ind val nnz
9898
end
9999

100100
function AddSparseVecMat(sdpcone::SDPConeT, arg2::Integer, arg3::Integer, arg4::Integer, arg5::Integer, arg6::Vector{Cint}, arg7::Vector{Cdouble}, arg8::Integer)

0 commit comments

Comments
 (0)