From 797bad886d4e319f8ac3ea417c0c9d9f2a3ae257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 30 Jul 2024 10:30:37 +0200 Subject: [PATCH 1/7] Add support for rank-1 constraints --- src/MOI_wrapper.jl | 126 +++++++++++++++++++++++++++++---------------- src/sdpcone.jl | 48 ++--------------- 2 files changed, 86 insertions(+), 88 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index c895e8e..c0ace1c 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -15,6 +15,10 @@ mutable struct Optimizer <: MOI.AbstractOptimizer # * `-d` means a diagonal block with diagonal of length `d` # * `d` means a symmetric `d x d` block blockdims::Vector{Int} + # MOI variable index -> rank 1 matrix it corresponds to + rank_one::Vector{Union{Nothing,MOI.LowRankMatrix{Cdouble}}} + # To avoid it being free'd + cached_ind::Vector{Vector{Cint}} varmap::Vector{Tuple{Int,Int,Int}} # Variable Index vi -> blk, i, j # If `blockdims[i] < 0`, `blk[i]` is the offset in `lpdvars`. # That is the **sum of length** of diagonal block before @@ -32,6 +36,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer sdpdinds::Vector{Vector{Vector{Cint}}} sdpdcoefs::Vector{Vector{Vector{Cdouble}}} y::Vector{Cdouble} + solve_time::Float64 silent::Bool options::Dict{Symbol,Any} @@ -43,6 +48,8 @@ mutable struct Optimizer <: MOI.AbstractOptimizer 1, Cdouble[], Int[], + Union{Nothing,MOI.LowRankMatrix{Cdouble}}[], + Vector{Cint}[], Tuple{Int,Int,Int}[], Int[], C_NULL, @@ -53,6 +60,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer Vector{Int}[], Vector{Cdouble}[], Cdouble[], + NaN, false, Dict{Symbol,Any}(), ) @@ -80,6 +88,8 @@ function MOI.empty!(optimizer::Optimizer) optimizer.objective_sign = 1 empty!(optimizer.b) empty!(optimizer.blockdims) + empty!(optimizer.rank_one) + empty!(optimizer.cached_ind) empty!(optimizer.varmap) empty!(optimizer.blk) optimizer.nlpdrows = 0 @@ -89,6 +99,7 @@ function MOI.empty!(optimizer::Optimizer) empty!(optimizer.sdpdinds) empty!(optimizer.sdpdcoefs) empty!(optimizer.y) + optimizer.solve_time = NaN return end @@ -218,8 +229,16 @@ end MOI.supports_add_constrained_variables(::Optimizer, ::Type{MOI.Reals}) = false -const SupportedSets = - Union{MOI.Nonnegatives,MOI.PositiveSemidefiniteConeTriangle} +const _SetWithDotProd = MOI.SetWithDotProducts{ + MOI.PositiveSemidefiniteConeTriangle, + MOI.TriangleVectorization{Cdouble,MOI.LowRankMatrix{Cdouble}}, +} + +const SupportedSets = Union{ + MOI.Nonnegatives, + MOI.PositiveSemidefiniteConeTriangle, + _SetWithDotProd, +} function MOI.supports_add_constrained_variables( ::Optimizer, @@ -241,6 +260,7 @@ function new_block(optimizer::Optimizer, set::MOI.Nonnegatives) blk = length(optimizer.blockdims) for i in 1:MOI.dimension(set) push!(optimizer.varmap, (blk, i, i)) + push!(optimizer.rank_one, nothing) end return end @@ -254,14 +274,27 @@ function new_block( for j in 1:set.side_dimension for i in 1:j push!(optimizer.varmap, (blk, i, j)) + push!(optimizer.rank_one, nothing) end end return end +function new_block(model::Optimizer, set::_SetWithDotProd) + println("______________________Low-Rank") + blk = length(model.blockdims) + 1 + for i in eachindex(set.vectors) + push!(model.varmap, (blk, 0, 0)) + push!(model.rank_one, set.vectors[i].matrix) + end + new_block(model, set.set) +end + function _add_constrained_variables(optimizer::Optimizer, set::SupportedSets) offset = length(optimizer.varmap) new_block(optimizer, set) + @assert length(optimizer.varmap) == offset + MOI.dimension(set) + @assert length(optimizer.rank_one) == offset + MOI.dimension(set) ci = MOI.ConstraintIndex{MOI.VectorOfVariables,typeof(set)}(offset + 1) return [MOI.VariableIndex(i) for i in offset .+ (1:MOI.dimension(set))], ci end @@ -306,57 +339,54 @@ function constrain_variables_on_creation( return end -function _setcoefficient!( - m::Optimizer, - coef, - constr::Integer, - blk::Integer, - i::Integer, - j::Integer, -) - if m.blockdims[blk] < 0 - @assert i == j - push!(m.lpdvars, constr + 1) - push!(m.lpdrows, m.blk[blk] + i - 1) # -1 because indexing starts at 0 in DSDP - push!(m.lpcoefs, coef) - else - sdp = m.blk[blk] - push!(m.sdpdinds[end][sdp], i + (j - 1) * m.blockdims[blk] - 1) - if i != j - coef /= 2 +function _setcoefficient!(dest::Optimizer, coef, constr::Integer, vi::MOI.VariableIndex) + blk, i, j = varmap(dest, vi) + rank_one = dest.rank_one[vi.value] + if isnothing(rank_one) + if dest.blockdims[blk] < 0 + @assert i == j + push!(dest.lpdvars, constr + 1) + push!(dest.lpdrows, dest.blk[blk] + i - 1) # -1 because indexing starts at 0 in DSDP + push!(dest.lpcoefs, coef) + else + sdp = dest.blk[blk] + push!(dest.sdpdinds[end][sdp], i + (j - 1) * dest.blockdims[blk] - 1) + if i != j + coef /= 2 + end + push!(dest.sdpdcoefs[end][sdp], coef) end - push!(m.sdpdcoefs[end][sdp], coef) + else + d = Cint(dest.blockdims[blk]) + push!(dest.cached_ind, collect(Cint(0):(d - 1))) + # 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 + DSDP.SDPCone.SetARankOneMat( + dest.sdpcone, + dest.blk[blk] - 1, + constr, + d, + coef * rank_one.diagonal[], + 0, + last(dest.cached_ind), + collect(eachcol(rank_one.factor)[]), + d, + ) end return end # Loads objective coefficient α * vi -function load_objective_term!( - optimizer::Optimizer, - index_map, - α, - vi::MOI.VariableIndex, -) - blk, i, j = varmap(optimizer, vi) +function load_objective_term!(optimizer::Optimizer, α, vi::MOI.VariableIndex) coef = optimizer.objective_sign * α - _setcoefficient!(optimizer, coef, 0, blk, i, j) + _setcoefficient!(optimizer, coef, 0, vi) return end function _set_A_matrices(m::Optimizer, i) for (blk, blkdim) in zip(m.blk, m.blockdims) - if blkdim > 0 - SDPCone.SetASparseVecMat( - m.sdpcone, - blk - 1, - i, - blkdim, - 1.0, - 0, - m.sdpdinds[end][blk], - m.sdpdcoefs[end][blk], - length(m.sdpdcoefs[end][blk]), - ) + if blkdim > 0 && !isempty(m.sdpdcoefs[end][blk]) + @show (blk - 1, i, blkdim, 1.0, 0, m.sdpdinds[end][blk], m.sdpdcoefs[end][blk], length(m.sdpdcoefs[end][blk])) + SDPCone.SetASparseVecMat(m.sdpcone, blk - 1, i, blkdim, 1.0, 0, m.sdpdinds[end][blk], m.sdpdcoefs[end][blk], length(m.sdpdcoefs[end][blk])) end end return @@ -386,6 +416,12 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike) index_map, MOI.PositiveSemidefiniteConeTriangle, ) + constrain_variables_on_creation( + dest, + src, + index_map, + _SetWithDotProd, + ) vis_src = MOI.get(src, MOI.ListOfVariableIndices()) if length(vis_src) < length(index_map.var_map) _error( @@ -464,8 +500,7 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike) _new_A_matrix(dest) for t in func.terms if !iszero(t.coefficient) - blk, i, j = varmap(dest, index_map[t.variable]) - _setcoefficient!(dest, t.coefficient, k, blk, i, j) + _setcoefficient!(dest, t.coefficient, k, index_map[t.variable]) end end _set_A_matrices(dest, k) @@ -508,7 +543,6 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike) if !iszero(term.coefficient) load_objective_term!( dest, - index_map, term.coefficient, index_map[term.variable], ) @@ -533,7 +567,9 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike) end function MOI.optimize!(m::Optimizer) + start_time = time() Solve(m.dsdp) + m.solve_time = time() - start_time # Calling `ComputeX` not right after `Solve` seems to sometime cause segfaults or weird Heisenbug's # let's call it directly what `DSDP/examples/readsdpa.c` does ComputeX(m.dsdp) @@ -543,6 +579,8 @@ function MOI.optimize!(m::Optimizer) return end +MOI.get(optimizer::Optimizer, ::MOI.SolveTimeSec) = optimizer.solve_time + function MOI.get(m::Optimizer, ::MOI.RawStatusString) if m.dsdp == C_NULL return "`optimize!` not called" diff --git a/src/sdpcone.jl b/src/sdpcone.jl index 8500452..e60d059 100644 --- a/src/sdpcone.jl +++ b/src/sdpcone.jl @@ -95,28 +95,8 @@ function SetADenseVecMat( ) sdpcone arg2 arg3 arg4 arg5 arg6 arg7 end -function SetARankOneMat( - sdpcone::SDPConeT, - arg2::Integer, - arg3::Integer, - arg4::Integer, - arg5::Cdouble, - arg6::Integer, - arg7::Vector{Cint}, - arg8::Vector{Cdouble}, - arg9::Integer, -) - @dsdp_ccall SDPConeSetARankOneMat ( - SDPConeT, - Cint, - Cint, - Cint, - Cdouble, - Cint, - Ptr{Cint}, - Ptr{Cdouble}, - Cint, - ) sdpcone arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 +function SetARankOneMat(sdpcone::SDPConeT, blockj::Integer, vari::Integer, n::Integer, alpha::Cdouble, ishift::Integer, ind::Vector{Cint}, val::Vector{Cdouble}, nnz::Integer) + @dsdp_ccall SDPConeSetARankOneMat (SDPConeT, Cint, Cint, Cint, Cdouble, Cint, Ptr{Cint}, Ptr{Cdouble}, Cint) sdpcone blockj vari n alpha ishift ind val nnz end function SetConstantMat( @@ -220,28 +200,8 @@ function AddIdentity( @dsdp_ccall SDPConeAddIdentity (SDPConeT, Cint, Cint, Cint, Cdouble) sdpcone arg2 arg3 arg4 arg5 end -function AddARankOneMat( - sdpcone::SDPConeT, - arg2::Integer, - arg3::Integer, - arg4::Integer, - arg5::Cdouble, - arg6::Integer, - arg7::Vector{Cint}, - arg8::Vector{Cdouble}, - arg9::Integer, -) - @dsdp_ccall SDPConeAddARankOneMat ( - SDPConeT, - Cint, - Cint, - Cint, - Cdouble, - Cint, - Ptr{Cint}, - Ptr{Cdouble}, - Cint, - ) sdpcone arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 +function AddARankOneMat(sdpcone::SDPConeT, blockj::Integer, vari::Integer, n::Integer, alpha::Cdouble, ishift::Integer, ind::Vector{Cint}, val::Vector{Cdouble}, nnz::Integer) + @dsdp_ccall SDPConeAddARankOneMat (SDPConeT, Cint, Cint, Cint, Cdouble, Cint, Ptr{Cint}, Ptr{Cdouble}, Cint) sdpcone blockj vari n alpha ishift ind val nnz end function AddSparseVecMat( From e1c7725521b4cd9abe322232299e7c69781e8f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 15 Apr 2025 10:57:28 +0200 Subject: [PATCH 2/7] Checkout LRO --- .github/workflows/ci.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f4c9c2..3f4b12e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,13 @@ jobs: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - uses: julia-actions/cache@v2 + - name: LRO + shell: julia --project=@. {0} + run: | + using Pkg + Pkg.add([ + PackageSpec(url="https://github.com/blegat/LowRankOpt.jl/"), + ]) - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 From f4d385147dc6a59df54b854a66e3cc7cd22134d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 15 Apr 2025 10:58:59 +0200 Subject: [PATCH 3/7] Fix format --- src/DSDP.jl | 2 +- src/MOI_wrapper.jl | 53 +++++++++++++++++++++++++++++++--------------- src/sdpcone.jl | 48 +++++++++++++++++++++++++++++++++++++---- test/maxcut.jl | 2 +- 4 files changed, 82 insertions(+), 23 deletions(-) diff --git a/src/DSDP.jl b/src/DSDP.jl index 41399b9..94728d7 100644 --- a/src/DSDP.jl +++ b/src/DSDP.jl @@ -10,7 +10,7 @@ import DSDP_jll using LinearAlgebra macro dsdp_ccall(f, args...) - quote + return quote # QuoteNode prevents the interpretion of the symbol # and leave it as a symbol info = diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index c0ace1c..a2e6a26 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -234,11 +234,8 @@ const _SetWithDotProd = MOI.SetWithDotProducts{ MOI.TriangleVectorization{Cdouble,MOI.LowRankMatrix{Cdouble}}, } -const SupportedSets = Union{ - MOI.Nonnegatives, - MOI.PositiveSemidefiniteConeTriangle, - _SetWithDotProd, -} +const SupportedSets = + Union{MOI.Nonnegatives,MOI.PositiveSemidefiniteConeTriangle,_SetWithDotProd} function MOI.supports_add_constrained_variables( ::Optimizer, @@ -287,7 +284,7 @@ function new_block(model::Optimizer, set::_SetWithDotProd) push!(model.varmap, (blk, 0, 0)) push!(model.rank_one, set.vectors[i].matrix) end - new_block(model, set.set) + return new_block(model, set.set) end function _add_constrained_variables(optimizer::Optimizer, set::SupportedSets) @@ -339,7 +336,12 @@ function constrain_variables_on_creation( return end -function _setcoefficient!(dest::Optimizer, coef, constr::Integer, vi::MOI.VariableIndex) +function _setcoefficient!( + dest::Optimizer, + coef, + constr::Integer, + vi::MOI.VariableIndex, +) blk, i, j = varmap(dest, vi) rank_one = dest.rank_one[vi.value] if isnothing(rank_one) @@ -350,7 +352,10 @@ function _setcoefficient!(dest::Optimizer, coef, constr::Integer, vi::MOI.Variab push!(dest.lpcoefs, coef) else sdp = dest.blk[blk] - push!(dest.sdpdinds[end][sdp], i + (j - 1) * dest.blockdims[blk] - 1) + push!( + dest.sdpdinds[end][sdp], + i + (j - 1) * dest.blockdims[blk] - 1, + ) if i != j coef /= 2 end @@ -358,7 +363,7 @@ function _setcoefficient!(dest::Optimizer, coef, constr::Integer, vi::MOI.Variab end else d = Cint(dest.blockdims[blk]) - push!(dest.cached_ind, collect(Cint(0):(d - 1))) + push!(dest.cached_ind, collect(Cint(0):(d-1))) # 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 DSDP.SDPCone.SetARankOneMat( dest.sdpcone, @@ -385,8 +390,27 @@ end function _set_A_matrices(m::Optimizer, i) for (blk, blkdim) in zip(m.blk, m.blockdims) if blkdim > 0 && !isempty(m.sdpdcoefs[end][blk]) - @show (blk - 1, i, blkdim, 1.0, 0, m.sdpdinds[end][blk], m.sdpdcoefs[end][blk], length(m.sdpdcoefs[end][blk])) - SDPCone.SetASparseVecMat(m.sdpcone, blk - 1, i, blkdim, 1.0, 0, m.sdpdinds[end][blk], m.sdpdcoefs[end][blk], length(m.sdpdcoefs[end][blk])) + @show ( + blk - 1, + i, + blkdim, + 1.0, + 0, + m.sdpdinds[end][blk], + m.sdpdcoefs[end][blk], + length(m.sdpdcoefs[end][blk]), + ) + SDPCone.SetASparseVecMat( + m.sdpcone, + blk - 1, + i, + blkdim, + 1.0, + 0, + m.sdpdinds[end][blk], + m.sdpdcoefs[end][blk], + length(m.sdpdcoefs[end][blk]), + ) end end return @@ -416,12 +440,7 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike) index_map, MOI.PositiveSemidefiniteConeTriangle, ) - constrain_variables_on_creation( - dest, - src, - index_map, - _SetWithDotProd, - ) + constrain_variables_on_creation(dest, src, index_map, _SetWithDotProd) vis_src = MOI.get(src, MOI.ListOfVariableIndices()) if length(vis_src) < length(index_map.var_map) _error( diff --git a/src/sdpcone.jl b/src/sdpcone.jl index e60d059..da18462 100644 --- a/src/sdpcone.jl +++ b/src/sdpcone.jl @@ -95,8 +95,28 @@ function SetADenseVecMat( ) sdpcone arg2 arg3 arg4 arg5 arg6 arg7 end -function SetARankOneMat(sdpcone::SDPConeT, blockj::Integer, vari::Integer, n::Integer, alpha::Cdouble, ishift::Integer, ind::Vector{Cint}, val::Vector{Cdouble}, nnz::Integer) - @dsdp_ccall SDPConeSetARankOneMat (SDPConeT, Cint, Cint, Cint, Cdouble, Cint, Ptr{Cint}, Ptr{Cdouble}, Cint) sdpcone blockj vari n alpha ishift ind val nnz +function SetARankOneMat( + sdpcone::SDPConeT, + blockj::Integer, + vari::Integer, + n::Integer, + alpha::Cdouble, + ishift::Integer, + ind::Vector{Cint}, + val::Vector{Cdouble}, + nnz::Integer, +) + @dsdp_ccall SDPConeSetARankOneMat ( + SDPConeT, + Cint, + Cint, + Cint, + Cdouble, + Cint, + Ptr{Cint}, + Ptr{Cdouble}, + Cint, + ) sdpcone blockj vari n alpha ishift ind val nnz end function SetConstantMat( @@ -200,8 +220,28 @@ function AddIdentity( @dsdp_ccall SDPConeAddIdentity (SDPConeT, Cint, Cint, Cint, Cdouble) sdpcone arg2 arg3 arg4 arg5 end -function AddARankOneMat(sdpcone::SDPConeT, blockj::Integer, vari::Integer, n::Integer, alpha::Cdouble, ishift::Integer, ind::Vector{Cint}, val::Vector{Cdouble}, nnz::Integer) - @dsdp_ccall SDPConeAddARankOneMat (SDPConeT, Cint, Cint, Cint, Cdouble, Cint, Ptr{Cint}, Ptr{Cdouble}, Cint) sdpcone blockj vari n alpha ishift ind val nnz +function AddARankOneMat( + sdpcone::SDPConeT, + blockj::Integer, + vari::Integer, + n::Integer, + alpha::Cdouble, + ishift::Integer, + ind::Vector{Cint}, + val::Vector{Cdouble}, + nnz::Integer, +) + @dsdp_ccall SDPConeAddARankOneMat ( + SDPConeT, + Cint, + Cint, + Cint, + Cdouble, + Cint, + Ptr{Cint}, + Ptr{Cdouble}, + Cint, + ) sdpcone blockj vari n alpha ishift ind val nnz end function AddSparseVecMat( diff --git a/test/maxcut.jl b/test/maxcut.jl index cd496a2..aa56785 100644 --- a/test/maxcut.jl +++ b/test/maxcut.jl @@ -70,7 +70,7 @@ function maxcut(nnodes, edges) yy = zeros(nnodes) indd = zeros(Cint, nnodes + nedges) val = zeros(nnodes + nedges) - indd[nedges.+(1:nnodes)] = iptr + indd[nedges .+ (1:nnodes)] = iptr tval = 0.0 for (i, (u, v, w)) in enumerate(edges) indd[i] = di(u, v) From a4a1d3e8b27e6011ae2376ca8e6e67979c10101a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 16 Apr 2025 11:38:40 +0200 Subject: [PATCH 4/7] Fix format --- test/maxcut.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/maxcut.jl b/test/maxcut.jl index aa56785..cd496a2 100644 --- a/test/maxcut.jl +++ b/test/maxcut.jl @@ -70,7 +70,7 @@ function maxcut(nnodes, edges) yy = zeros(nnodes) indd = zeros(Cint, nnodes + nedges) val = zeros(nnodes + nedges) - indd[nedges .+ (1:nnodes)] = iptr + indd[nedges.+(1:nnodes)] = iptr tval = 0.0 for (i, (u, v, w)) in enumerate(edges) indd[i] = di(u, v) From 8a82d6de7a30b27c8f02b9240970dce1bc7f7c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 16 Apr 2025 11:51:19 +0200 Subject: [PATCH 5/7] Fixes --- Project.toml | 1 + src/MOI_wrapper.jl | 37 ++++++++++++++++--------------------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/Project.toml b/Project.toml index 2111bfc..b5c129a 100644 --- a/Project.toml +++ b/Project.toml @@ -6,6 +6,7 @@ version = "0.3.0" [deps] DSDP_jll = "1065e140-e56c-5613-be8b-7480bf7138df" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +LowRankOpt = "607ca3ad-272e-43c8-bcbe-fc71b56c935c" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" [compat] diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index a2e6a26..6d6cca8 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -4,6 +4,17 @@ # in the LICENSE.md file or at https://opensource.org/licenses/MIT. import MathOptInterface as MOI +import LowRankOpt as LRO + +const _RankOneMatrix{F<:AbstractVector{Cdouble},D<:AbstractArray{Cdouble,0}} = + LRO.Factorization{Cdouble,F,D} + +const _SetDotProd{F<:AbstractMatrix{Cdouble},D<:AbstractVector{Cdouble}} = + LRO.SetDotProducts{ + LRO.WITH_SET, + MOI.PositiveSemidefiniteConeTriangle, + LRO.TriangleVectorization{Cdouble,_RankOneMatrix{F,D}}, + } mutable struct Optimizer <: MOI.AbstractOptimizer dsdp::DSDPT @@ -16,7 +27,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer # * `d` means a symmetric `d x d` block blockdims::Vector{Int} # MOI variable index -> rank 1 matrix it corresponds to - rank_one::Vector{Union{Nothing,MOI.LowRankMatrix{Cdouble}}} + rank_one::Vector{Union{Nothing,_RankOneMatrix}} # To avoid it being free'd cached_ind::Vector{Vector{Cint}} varmap::Vector{Tuple{Int,Int,Int}} # Variable Index vi -> blk, i, j @@ -48,7 +59,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer 1, Cdouble[], Int[], - Union{Nothing,MOI.LowRankMatrix{Cdouble}}[], + Union{Nothing,_RankOneMatrix}[], Vector{Cint}[], Tuple{Int,Int,Int}[], Int[], @@ -229,13 +240,8 @@ end MOI.supports_add_constrained_variables(::Optimizer, ::Type{MOI.Reals}) = false -const _SetWithDotProd = MOI.SetWithDotProducts{ - MOI.PositiveSemidefiniteConeTriangle, - MOI.TriangleVectorization{Cdouble,MOI.LowRankMatrix{Cdouble}}, -} - const SupportedSets = - Union{MOI.Nonnegatives,MOI.PositiveSemidefiniteConeTriangle,_SetWithDotProd} + Union{MOI.Nonnegatives,MOI.PositiveSemidefiniteConeTriangle,_SetDotProd} function MOI.supports_add_constrained_variables( ::Optimizer, @@ -277,8 +283,7 @@ function new_block( return end -function new_block(model::Optimizer, set::_SetWithDotProd) - println("______________________Low-Rank") +function new_block(model::Optimizer, set::_SetDotProd) blk = length(model.blockdims) + 1 for i in eachindex(set.vectors) push!(model.varmap, (blk, 0, 0)) @@ -390,16 +395,6 @@ end function _set_A_matrices(m::Optimizer, i) for (blk, blkdim) in zip(m.blk, m.blockdims) if blkdim > 0 && !isempty(m.sdpdcoefs[end][blk]) - @show ( - blk - 1, - i, - blkdim, - 1.0, - 0, - m.sdpdinds[end][blk], - m.sdpdcoefs[end][blk], - length(m.sdpdcoefs[end][blk]), - ) SDPCone.SetASparseVecMat( m.sdpcone, blk - 1, @@ -440,7 +435,7 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike) index_map, MOI.PositiveSemidefiniteConeTriangle, ) - constrain_variables_on_creation(dest, src, index_map, _SetWithDotProd) + constrain_variables_on_creation(dest, src, index_map, _SetDotProd) vis_src = MOI.get(src, MOI.ListOfVariableIndices()) if length(vis_src) < length(index_map.var_map) _error( From 492852d1b1c71dcfabe8979e76a927792954d534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 16 Apr 2025 11:51:31 +0200 Subject: [PATCH 6/7] Add test/Project.toml --- test/Project.toml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 test/Project.toml diff --git a/test/Project.toml b/test/Project.toml new file mode 100644 index 0000000..27b18d3 --- /dev/null +++ b/test/Project.toml @@ -0,0 +1,4 @@ +[deps] +DSDP = "2714ae6b-e930-5b4e-9c21-d0bacf577842" +MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" From 66e3e609ea6cb2f8f728ac346495a25d2ffe7d38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 16 Apr 2025 21:05:42 +0200 Subject: [PATCH 7/7] Add test --- test/MOI_wrapper.jl | 19 +++++++++++++++++++ test/Project.toml | 1 + 2 files changed, 20 insertions(+) diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index 5006afd..b8d0cbb 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -7,6 +7,7 @@ module TestDSDP using Test import MathOptInterface as MOI +import LowRankOpt as LRO import DSDP function runtests() @@ -170,6 +171,24 @@ function test_runtests() return end +function test_LRO_runtests() + T = Float64 + model = MOI.instantiate( + DSDP.Optimizer, + with_bridge_type = T, + with_cache_type = T, + ) + LRO.Bridges.add_all_bridges(model, T) + MOI.set(model, MOI.Silent(), true) + config = MOI.Test.Config( + rtol = 1e-2, + atol = 1e-2, + ) + MOI.Test.runtests(model, config, test_module = LRO.Test) + return +end + + end # module TestDSDP.runtests() diff --git a/test/Project.toml b/test/Project.toml index 27b18d3..729396d 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,4 +1,5 @@ [deps] DSDP = "2714ae6b-e930-5b4e-9c21-d0bacf577842" +LowRankOpt = "607ca3ad-272e-43c8-bcbe-fc71b56c935c" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"