diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 27ff835..424e9c9 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -278,7 +278,7 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike) return MOI.Utilities.default_copy_to(dest, src) end -function MOI.optimize!(model::Optimizer) +function Base.convert(::Type{Model}, model::Optimizer) CAent = vcat(model.Cent, model.Aent) CArow = vcat(model.Crow, model.Arow) CAcol = vcat(model.Ccol, model.Acol) @@ -299,6 +299,25 @@ function MOI.optimize!(model::Optimizer) push!(CAinfo_entptr, length(CAent)) CAinfo_type = reduce(vcat, vcat([model.Cinfo_type], model.Ainfo_type), init = Cchar[]) + return Model(; + blksz = model.blksz, + blktype = model.blktype, + b = model.b, + CAent, + CArow, + CAcol, + CAinfo_entptr, + CAinfo_type, + ) +end + +function MOI.write_to_file(model::Optimizer, filename::String) + write_sdplr(convert(Model, model), filename) + return +end + +function MOI.optimize!(model::Optimizer) + raw_model = convert(Model, model) params = deepcopy(model.params) if model.silent params.printlevel = 0 @@ -308,7 +327,7 @@ function MOI.optimize!(model::Optimizer) model.params.maxrank, model.blktype, model.blksz, - CAinfo_entptr, + raw_model.CAinfo_entptr, length(model.b), ) model.ranks = copy(model.maxranks) @@ -321,14 +340,7 @@ function MOI.optimize!(model::Optimizer) end end _, model.R, model.lambda, model.ranks, model.pieces = solve( - model.blksz, - model.blktype, - model.b, - CAent, - CArow, - CAcol, - CAinfo_entptr, - CAinfo_type, + raw_model, params = params, maxranks = model.maxranks, ranks = model.ranks, diff --git a/src/SDPLR.jl b/src/SDPLR.jl index ad96885..a92585e 100644 --- a/src/SDPLR.jl +++ b/src/SDPLR.jl @@ -39,6 +39,91 @@ end # See `macros.h` datablockind(data, block, numblock) = data * numblock + block +@kwdef struct Model + blksz::Vector{Cptrdiff_t} + blktype::Vector{Cchar} + b::Vector{Cdouble} + CAent::Vector{Cdouble} + CArow::Vector{Csize_t} + CAcol::Vector{Csize_t} + CAinfo_entptr::Vector{Csize_t} + CAinfo_type::Vector{Cchar} +end + +function checkdims(model::Model) + numblk = length(model.blksz) + @assert length(model.blktype) == numblk + m = length(model.b) + @assert length(model.CAinfo_entptr) == (m + 1) * numblk + 1 + @assert length(model.CAinfo_type) == (m + 1) * numblk + @assert length(model.CAent) == length(model.CArow) == length(model.CAcol) + @assert model.CAinfo_entptr[1] == 0 + @assert model.CAinfo_entptr[end] == length(model.CArow) + k = 0 + for _ in eachindex(model.b) + for blk in eachindex(model.blksz) + k += 1 + @assert model.CAinfo_entptr[k] <= model.CAinfo_entptr[k+1] + for j in ((model.CAinfo_entptr[k]+1):model.CAinfo_entptr[k+1]) + @assert 1 <= model.CArow[j] <= model.blksz[blk] + @assert 1 <= model.CAcol[j] <= model.blksz[blk] + if model.CAinfo_type[k] == Cchar('s') + @assert model.blktype[blk] == Cchar('s') || model.blktype[blk] == Cchar('d') + @assert model.CArow[j] <= model.CAcol[j] + elseif model.CAinfo_type[k] == Cchar('d') + @assert model.blktype[blk] == Cchar('d') + @assert model.CArow[j] == model.CAcol[j] + else + @assert model.CAinfo_type[k] == Cchar('l') + @assert model.blktype[blk] == Cchar('s') + end + end + end + end + return m, numblk +end + +function write_sdplr(model::Model, filename::String) + m, numblk = checkdims(model) + open(filename, "w") do io + println(io, m) + println(io, numblk) + println(io, join(model.blksz .* map(t -> t == 'd' ? -1 : 1, model.blktype), ' ')) + println(io, join(model.b, ' ')) + println(io, -1) # Currently ignored + for constraint in 0:m + for blk in eachindex(model.blksz) + print(io, constraint) + print(io, ' ') + print(io, blk) + print(io, ' ') + cur = numblk * constraint + blk + t = model.CAinfo_type[cur] + range = 1 .+ (model.CAinfo_entptr[cur]:(model.CAinfo_entptr[cur + 1] - 1)) + if t == 'l' + print(io, 'l') + print(io, ' ') + print(io, div(length(range), model.blksz[blk] + 1)) + for i in range + println(io, model.CAent[i]) + end + else + print(io, 's') + print(io, ' ') + print(io, length(range)) + for i in range + print(io, model.CArow[i]) + print(io, ' ') + print(io, model.CAcol[i]) + print(io, ' ') + println(io, model.CAent[i]) + end + end + end + end + end +end + function default_R(blktype::Vector{Cchar}, blksz, maxranks) # See `getstorage` in `main.c` Rsizes = map(eachindex(blktype)) do k @@ -99,69 +184,37 @@ is `Cchar('d')`. The `CA...` arguments specify the `C` and `A_i` matrices. """ function solve( - blksz::Vector{Cptrdiff_t}, - blktype::Vector{Cchar}, - b::Vector{Cdouble}, - CAent::Vector{Cdouble}, - CArow::Vector{Csize_t}, - CAcol::Vector{Csize_t}, - CAinfo_entptr::Vector{Csize_t}, - CAinfo_type::Vector{Cchar}; + model::Model; params::Parameters = Parameters(), maxranks::Vector{Csize_t} = default_maxranks( params.maxrank, - blktype, - blksz, - CAinfo_entptr, - length(b), + model.blktype, + model.blksz, + model.CAinfo_entptr, + length(model.b), ), ranks::Vector{Csize_t} = copy(maxranks), - R::Vector{Cdouble} = default_R(blktype, blksz, maxranks)[2], - lambda::Vector{Cdouble} = zeros(Cdouble, length(b)), - pieces::Vector{Cdouble} = default_pieces(blksz), + R::Vector{Cdouble} = default_R(model.blktype, model.blksz, maxranks)[2], + lambda::Vector{Cdouble} = zeros(Cdouble, length(model.b)), + pieces::Vector{Cdouble} = default_pieces(model.blksz), ) - numblk = length(blksz) - @assert length(blktype) == numblk - m = length(b) - @assert length(CAinfo_entptr) == (m + 1) * numblk + 1 - @assert length(CAinfo_type) == (m + 1) * numblk - @assert length(CAent) == length(CArow) == length(CAcol) + m, numblk = checkdims(model) @assert length(lambda) == m @assert length(maxranks) == numblk @assert length(ranks) == numblk @assert length(pieces) == 8 - @assert CAinfo_entptr[1] == 0 - @assert CAinfo_entptr[end] == length(CArow) - k = 0 - for _ in eachindex(b) - for blk in eachindex(blksz) - k += 1 - @assert CAinfo_entptr[k] <= CAinfo_entptr[k+1] - for j in ((CAinfo_entptr[k]+1):CAinfo_entptr[k+1]) - @assert blktype[blk] == CAinfo_type[k] - @assert 1 <= CArow[j] <= blksz[blk] - @assert 1 <= CAcol[j] <= blksz[blk] - if CAinfo_type[k] == Cchar('s') - @assert CArow[j] <= CAcol[j] - else - @assert CAinfo_type[k] == Cchar('d') - @assert CArow[j] == CAcol[j] - end - end - end - end - GC.@preserve blksz blktype b CAent CArow CAcol CAinfo_entptr CAinfo_type R lambda maxranks ranks pieces begin + GC.@preserve model R lambda maxranks ranks pieces begin ret = @ccall SDPLR_jll.libsdplr.sdplrlib( m::Csize_t, numblk::Csize_t, - blksz::Ptr{Cptrdiff_t}, - blktype::Ptr{Cchar}, - b::Ptr{Cdouble}, - CAent::Ptr{Cdouble}, - CArow::Ptr{Csize_t}, - CAcol::Ptr{Csize_t}, - CAinfo_entptr::Ptr{Csize_t}, - CAinfo_type::Ptr{Cchar}, + model.blksz::Ptr{Cptrdiff_t}, + model.blktype::Ptr{Cchar}, + model.b::Ptr{Cdouble}, + model.CAent::Ptr{Cdouble}, + model.CArow::Ptr{Csize_t}, + model.CAcol::Ptr{Csize_t}, + model.CAinfo_entptr::Ptr{Csize_t}, + model.CAinfo_type::Ptr{Cchar}, params.numbfgsvecs::Csize_t, params.rho_f::Cdouble, params.rho_c::Cdouble, diff --git a/test/runtests.jl b/test/runtests.jl index c6e7a7a..8fee3ab 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -365,6 +365,32 @@ function test_solve_vibra_with_sdplrlib() @test ranks == Csize_t[9, 9, 1] end +# Test of LowRankOpt's test `test_conic_PositiveSemidefinite_RankOne_polynomial` in low-level SDPLR version +function test_solve_conic_PositiveSemidefinite_RankOne_polynomial() + model = SDPLR.Model( + blksz = [2, 2], + blktype = Cchar['d', 's'], + b = Cdouble[-3, 1], + CAent = Cdouble[-1, 1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1], + CArow = UInt64[1, 2, 1, 2, 1, 1, 2, 1, 2, 1, 1, 2], + CAcol = UInt64[1, 2, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1], + CAinfo_entptr = UInt64[0, 2, 2, 4, 7, 9, 12], + CAinfo_type = Cchar['s', 's', 's', 'l', 's', 'l'], + ) + # The `925` seed is taken from SDPLR's `main.c` + Random.seed!(925) + SDPLR.write_sdplr(model, "lowrank_RankOne_poly.sdplr") + ret, R, lambda, ranks, pieces = SDPLR.solve( + model, + params = SDPLR.Parameters(printlevel = 4), + ) + @test iszero(ret) + @show R + @show lambda + @show pieces + @show ranks +end + function runtests() for name in names(@__MODULE__; all = true) if startswith("$(name)", "test_")