Skip to content

Commit f482237

Browse files
committed
Add support for rank-1 constraints
1 parent 68353c8 commit f482237

File tree

2 files changed

+86
-88
lines changed

2 files changed

+86
-88
lines changed

src/MOI_wrapper.jl

Lines changed: 82 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
1515
# * `-d` means a diagonal block with diagonal of length `d`
1616
# * `d` means a symmetric `d x d` block
1717
blockdims::Vector{Int}
18+
# MOI variable index -> rank 1 matrix it corresponds to
19+
rank_one::Vector{Union{Nothing,MOI.LowRankMatrix{Cdouble}}}
20+
# To avoid it being free'd
21+
cached_ind::Vector{Vector{Cint}}
1822
varmap::Vector{Tuple{Int,Int,Int}} # Variable Index vi -> blk, i, j
1923
# If `blockdims[i] < 0`, `blk[i]` is the offset in `lpdvars`.
2024
# That is the **sum of length** of diagonal block before
@@ -32,6 +36,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
3236
sdpdinds::Vector{Vector{Vector{Cint}}}
3337
sdpdcoefs::Vector{Vector{Vector{Cdouble}}}
3438
y::Vector{Cdouble}
39+
solve_time::Float64
3540
silent::Bool
3641
options::Dict{Symbol,Any}
3742

@@ -43,6 +48,8 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
4348
1,
4449
Cdouble[],
4550
Int[],
51+
Union{Nothing,MOI.LowRankMatrix{Cdouble}}[],
52+
Vector{Cint}[],
4653
Tuple{Int,Int,Int}[],
4754
Int[],
4855
C_NULL,
@@ -53,6 +60,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
5360
Vector{Int}[],
5461
Vector{Cdouble}[],
5562
Cdouble[],
63+
NaN,
5664
false,
5765
Dict{Symbol,Any}(),
5866
)
@@ -80,6 +88,8 @@ function MOI.empty!(optimizer::Optimizer)
8088
optimizer.objective_sign = 1
8189
empty!(optimizer.b)
8290
empty!(optimizer.blockdims)
91+
empty!(optimizer.rank_one)
92+
empty!(optimizer.cached_ind)
8393
empty!(optimizer.varmap)
8494
empty!(optimizer.blk)
8595
optimizer.nlpdrows = 0
@@ -89,6 +99,7 @@ function MOI.empty!(optimizer::Optimizer)
8999
empty!(optimizer.sdpdinds)
90100
empty!(optimizer.sdpdcoefs)
91101
empty!(optimizer.y)
102+
optimizer.solve_time = NaN
92103
return
93104
end
94105

@@ -218,8 +229,16 @@ end
218229

219230
MOI.supports_add_constrained_variables(::Optimizer, ::Type{MOI.Reals}) = false
220231

221-
const SupportedSets =
222-
Union{MOI.Nonnegatives,MOI.PositiveSemidefiniteConeTriangle}
232+
const _SetWithDotProd = MOI.SetWithDotProducts{
233+
MOI.PositiveSemidefiniteConeTriangle,
234+
MOI.TriangleVectorization{Cdouble,MOI.LowRankMatrix{Cdouble}},
235+
}
236+
237+
const SupportedSets = Union{
238+
MOI.Nonnegatives,
239+
MOI.PositiveSemidefiniteConeTriangle,
240+
_SetWithDotProd,
241+
}
223242

224243
function MOI.supports_add_constrained_variables(
225244
::Optimizer,
@@ -241,6 +260,7 @@ function new_block(optimizer::Optimizer, set::MOI.Nonnegatives)
241260
blk = length(optimizer.blockdims)
242261
for i in 1:MOI.dimension(set)
243262
push!(optimizer.varmap, (blk, i, i))
263+
push!(optimizer.rank_one, nothing)
244264
end
245265
return
246266
end
@@ -254,14 +274,27 @@ function new_block(
254274
for j in 1:set.side_dimension
255275
for i in 1:j
256276
push!(optimizer.varmap, (blk, i, j))
277+
push!(optimizer.rank_one, nothing)
257278
end
258279
end
259280
return
260281
end
261282

283+
function new_block(model::Optimizer, set::_SetWithDotProd)
284+
println("______________________Low-Rank")
285+
blk = length(model.blockdims) + 1
286+
for i in eachindex(set.vectors)
287+
push!(model.varmap, (blk, 0, 0))
288+
push!(model.rank_one, set.vectors[i].matrix)
289+
end
290+
new_block(model, set.set)
291+
end
292+
262293
function _add_constrained_variables(optimizer::Optimizer, set::SupportedSets)
263294
offset = length(optimizer.varmap)
264295
new_block(optimizer, set)
296+
@assert length(optimizer.varmap) == offset + MOI.dimension(set)
297+
@assert length(optimizer.rank_one) == offset + MOI.dimension(set)
265298
ci = MOI.ConstraintIndex{MOI.VectorOfVariables,typeof(set)}(offset + 1)
266299
return [MOI.VariableIndex(i) for i in offset .+ (1:MOI.dimension(set))], ci
267300
end
@@ -306,57 +339,54 @@ function constrain_variables_on_creation(
306339
return
307340
end
308341

309-
function _setcoefficient!(
310-
m::Optimizer,
311-
coef,
312-
constr::Integer,
313-
blk::Integer,
314-
i::Integer,
315-
j::Integer,
316-
)
317-
if m.blockdims[blk] < 0
318-
@assert i == j
319-
push!(m.lpdvars, constr + 1)
320-
push!(m.lpdrows, m.blk[blk] + i - 1) # -1 because indexing starts at 0 in DSDP
321-
push!(m.lpcoefs, coef)
322-
else
323-
sdp = m.blk[blk]
324-
push!(m.sdpdinds[end][sdp], i + (j - 1) * m.blockdims[blk] - 1)
325-
if i != j
326-
coef /= 2
342+
function _setcoefficient!(dest::Optimizer, coef, constr::Integer, vi::MOI.VariableIndex)
343+
blk, i, j = varmap(dest, vi)
344+
rank_one = dest.rank_one[vi.value]
345+
if isnothing(rank_one)
346+
if dest.blockdims[blk] < 0
347+
@assert i == j
348+
push!(dest.lpdvars, constr + 1)
349+
push!(dest.lpdrows, dest.blk[blk] + i - 1) # -1 because indexing starts at 0 in DSDP
350+
push!(dest.lpcoefs, coef)
351+
else
352+
sdp = dest.blk[blk]
353+
push!(dest.sdpdinds[end][sdp], i + (j - 1) * dest.blockdims[blk] - 1)
354+
if i != j
355+
coef /= 2
356+
end
357+
push!(dest.sdpdcoefs[end][sdp], coef)
327358
end
328-
push!(m.sdpdcoefs[end][sdp], coef)
359+
else
360+
d = Cint(dest.blockdims[blk])
361+
push!(dest.cached_ind, collect(Cint(0):(d - 1)))
362+
# 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
363+
DSDP.SDPCone.SetARankOneMat(
364+
dest.sdpcone,
365+
dest.blk[blk] - 1,
366+
constr,
367+
d,
368+
coef * rank_one.diagonal[],
369+
0,
370+
last(dest.cached_ind),
371+
collect(eachcol(rank_one.factor)[]),
372+
d,
373+
)
329374
end
330375
return
331376
end
332377

333378
# Loads objective coefficient α * vi
334-
function load_objective_term!(
335-
optimizer::Optimizer,
336-
index_map,
337-
α,
338-
vi::MOI.VariableIndex,
339-
)
340-
blk, i, j = varmap(optimizer, vi)
379+
function load_objective_term!(optimizer::Optimizer, α, vi::MOI.VariableIndex)
341380
coef = optimizer.objective_sign * α
342-
_setcoefficient!(optimizer, coef, 0, blk, i, j)
381+
_setcoefficient!(optimizer, coef, 0, vi)
343382
return
344383
end
345384

346385
function _set_A_matrices(m::Optimizer, i)
347386
for (blk, blkdim) in zip(m.blk, m.blockdims)
348-
if blkdim > 0
349-
SDPCone.SetASparseVecMat(
350-
m.sdpcone,
351-
blk - 1,
352-
i,
353-
blkdim,
354-
1.0,
355-
0,
356-
m.sdpdinds[end][blk],
357-
m.sdpdcoefs[end][blk],
358-
length(m.sdpdcoefs[end][blk]),
359-
)
387+
if blkdim > 0 && !isempty(m.sdpdcoefs[end][blk])
388+
@show (blk - 1, i, blkdim, 1.0, 0, m.sdpdinds[end][blk], m.sdpdcoefs[end][blk], length(m.sdpdcoefs[end][blk]))
389+
SDPCone.SetASparseVecMat(m.sdpcone, blk - 1, i, blkdim, 1.0, 0, m.sdpdinds[end][blk], m.sdpdcoefs[end][blk], length(m.sdpdcoefs[end][blk]))
360390
end
361391
end
362392
return
@@ -386,6 +416,12 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)
386416
index_map,
387417
MOI.PositiveSemidefiniteConeTriangle,
388418
)
419+
constrain_variables_on_creation(
420+
dest,
421+
src,
422+
index_map,
423+
_SetWithDotProd,
424+
)
389425
vis_src = MOI.get(src, MOI.ListOfVariableIndices())
390426
if length(vis_src) < length(index_map.var_map)
391427
_error(
@@ -464,8 +500,7 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)
464500
_new_A_matrix(dest)
465501
for t in func.terms
466502
if !iszero(t.coefficient)
467-
blk, i, j = varmap(dest, index_map[t.variable])
468-
_setcoefficient!(dest, t.coefficient, k, blk, i, j)
503+
_setcoefficient!(dest, t.coefficient, k, index_map[t.variable])
469504
end
470505
end
471506
_set_A_matrices(dest, k)
@@ -508,7 +543,6 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)
508543
if !iszero(term.coefficient)
509544
load_objective_term!(
510545
dest,
511-
index_map,
512546
term.coefficient,
513547
index_map[term.variable],
514548
)
@@ -533,7 +567,9 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)
533567
end
534568

535569
function MOI.optimize!(m::Optimizer)
570+
start_time = time()
536571
Solve(m.dsdp)
572+
m.solve_time = time() - start_time
537573
# Calling `ComputeX` not right after `Solve` seems to sometime cause segfaults or weird Heisenbug's
538574
# let's call it directly what `DSDP/examples/readsdpa.c` does
539575
ComputeX(m.dsdp)
@@ -543,6 +579,8 @@ function MOI.optimize!(m::Optimizer)
543579
return
544580
end
545581

582+
MOI.get(optimizer::Optimizer, ::MOI.SolveTimeSec) = optimizer.solve_time
583+
546584
function MOI.get(m::Optimizer, ::MOI.RawStatusString)
547585
if m.dsdp == C_NULL
548586
return "`optimize!` not called"

src/sdpcone.jl

Lines changed: 4 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -95,28 +95,8 @@ function SetADenseVecMat(
9595
) sdpcone arg2 arg3 arg4 arg5 arg6 arg7
9696
end
9797

98-
function SetARankOneMat(
99-
sdpcone::SDPConeT,
100-
arg2::Integer,
101-
arg3::Integer,
102-
arg4::Integer,
103-
arg5::Cdouble,
104-
arg6::Integer,
105-
arg7::Vector{Cint},
106-
arg8::Vector{Cdouble},
107-
arg9::Integer,
108-
)
109-
@dsdp_ccall SDPConeSetARankOneMat (
110-
SDPConeT,
111-
Cint,
112-
Cint,
113-
Cint,
114-
Cdouble,
115-
Cint,
116-
Ptr{Cint},
117-
Ptr{Cdouble},
118-
Cint,
119-
) sdpcone arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9
98+
function SetARankOneMat(sdpcone::SDPConeT, blockj::Integer, vari::Integer, n::Integer, alpha::Cdouble, ishift::Integer, ind::Vector{Cint}, val::Vector{Cdouble}, nnz::Integer)
99+
@dsdp_ccall SDPConeSetARankOneMat (SDPConeT, Cint, Cint, Cint, Cdouble, Cint, Ptr{Cint}, Ptr{Cdouble}, Cint) sdpcone blockj vari n alpha ishift ind val nnz
120100
end
121101

122102
function SetConstantMat(
@@ -220,28 +200,8 @@ function AddIdentity(
220200
@dsdp_ccall SDPConeAddIdentity (SDPConeT, Cint, Cint, Cint, Cdouble) sdpcone arg2 arg3 arg4 arg5
221201
end
222202

223-
function AddARankOneMat(
224-
sdpcone::SDPConeT,
225-
arg2::Integer,
226-
arg3::Integer,
227-
arg4::Integer,
228-
arg5::Cdouble,
229-
arg6::Integer,
230-
arg7::Vector{Cint},
231-
arg8::Vector{Cdouble},
232-
arg9::Integer,
233-
)
234-
@dsdp_ccall SDPConeAddARankOneMat (
235-
SDPConeT,
236-
Cint,
237-
Cint,
238-
Cint,
239-
Cdouble,
240-
Cint,
241-
Ptr{Cint},
242-
Ptr{Cdouble},
243-
Cint,
244-
) sdpcone arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9
203+
function AddARankOneMat(sdpcone::SDPConeT, blockj::Integer, vari::Integer, n::Integer, alpha::Cdouble, ishift::Integer, ind::Vector{Cint}, val::Vector{Cdouble}, nnz::Integer)
204+
@dsdp_ccall SDPConeAddARankOneMat (SDPConeT, Cint, Cint, Cint, Cdouble, Cint, Ptr{Cint}, Ptr{Cdouble}, Cint) sdpcone blockj vari n alpha ishift ind val nnz
245205
end
246206

247207
function AddSparseVecMat(

0 commit comments

Comments
 (0)