diff --git a/.gitignore b/.gitignore index 65c8175..427ce98 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +Manifest.toml *.jl.cov *.jl.*.cov *.jl.mem diff --git a/.travis.yml b/.travis.yml index a340f4b..76696cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,8 @@ os: - linux - osx julia: - - 0.6 - - 0.7 - 1.0 + - 1.1 matrix: allow_failures: @@ -21,7 +20,4 @@ addons: notifications: email: false after_success: - # push coverage results to Coveralls - - julia -e 'cd(Pkg.dir("CSDP")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(process_folder())' - # push coverage results to Codecov - - julia -e 'cd(Pkg.dir("CSDP")); Pkg.add("Coverage"); using Coverage; Codecov.submit(Codecov.process_folder())' + - julia -e 'import Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder()); Coveralls.submit(process_folder())' diff --git a/Project.toml b/Project.toml new file mode 100644 index 0000000..485fa22 --- /dev/null +++ b/Project.toml @@ -0,0 +1,26 @@ +name = "CSDP" +uuid = "0a46da34-8e4b-519e-b418-48813639ff34" +repo = "https://github.com/JuliaOpt/CSDP.jl.git" +version = "0.5.0" + +[deps] +BinDeps = "9e28174c-4ba2-5203-b857-d8d62c4213ee" +Glob = "c27321d9-0574-5035-807b-f59d2c89b15c" +Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" +MathProgBase = "fdba3010-5040-5b88-9595-932c9decdf73" +SemidefiniteModels = "169818f4-1a3d-53bf-95b3-11177825b1e3" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + +[compat] +MathOptInterface = "0.9" +MathProgBase = "~0.7.0" +SemidefiniteModels = "~0.1.1" +julia = "1" + +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Test"] diff --git a/REQUIRE b/REQUIRE deleted file mode 100644 index effa3b9..0000000 --- a/REQUIRE +++ /dev/null @@ -1,8 +0,0 @@ -julia 0.6 -BinDeps -Glob -MathOptInterface 0.8 0.9 -SemidefiniteOptInterface 0.5 0.6 -MathProgBase 0.7 0.8 -SemidefiniteModels 0.1.1 0.2 -Compat 1.0 diff --git a/appveyor.yml b/appveyor.yml index ecbb923..342697c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,8 +1,7 @@ environment: matrix: - - julia_version: 0.6 - - julia_version: 0.7 - julia_version: 1 + - julia_version: 1.1 platform: - x86 # 32-bit diff --git a/src/CSDP.jl b/src/CSDP.jl index 4bb78d3..c11ebe4 100644 --- a/src/CSDP.jl +++ b/src/CSDP.jl @@ -1,8 +1,7 @@ module CSDP -using Compat -using Compat.LinearAlgebra # For Diagonal -using Compat.SparseArrays # For SparseMatrixCSC +using LinearAlgebra # For Diagonal +using SparseArrays # For SparseMatrixCSC # Try to load the binary dependency if isfile(joinpath(dirname(@__FILE__),"..","deps","deps.jl")) @@ -14,6 +13,7 @@ end export Blockmatrix include("blockmat.h.jl") +include("blockdiag.jl") include("blockmat.jl") include("declarations.h.jl") include("declarations.jl") diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 60cb75a..d1efbaa 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -1,12 +1,15 @@ -using SemidefiniteOptInterface -SDOI = SemidefiniteOptInterface - using MathOptInterface -MOI = MathOptInterface +const MOI = MathOptInterface +const MOIU = MOI.Utilities +const AFFEQ = MOI.ConstraintIndex{MOI.ScalarAffineFunction{Cdouble}, MOI.EqualTo{Cdouble}} -mutable struct SDOptimizer <: SDOI.AbstractSDOptimizer +mutable struct Optimizer <: MOI.AbstractOptimizer + objconstant::Cdouble + objsign::Int + blockdims::Vector{Int} + varmap::Vector{Tuple{Int, Int, Int}} # Variable Index vi -> blk, i, j + b::Vector{Cdouble} C::Union{Nothing, BlockMatrix} - b::Union{Nothing, Vector{Cdouble}} As::Union{Nothing, Vector{ConstraintMatrix}} X::Union{Nothing, BlockMatrix} y::Union{Nothing, Vector{Cdouble}} @@ -14,19 +17,82 @@ mutable struct SDOptimizer <: SDOI.AbstractSDOptimizer status::Cint pobj::Cdouble dobj::Cdouble - options::Dict{Symbol,Any} - function SDOptimizer(; kwargs...) - new(nothing, nothing, nothing, nothing, nothing, nothing, - -1, 0.0, 0.0, checkoptions(Dict{Symbol, Any}(kwargs))) + solve_time::Float64 + silent::Bool + options::Dict{Symbol, Any} + function Optimizer(; kwargs...) + optimizer = new( + zero(Cdouble), 1, Int[], Tuple{Int, Int, Int}[], Cdouble[], + nothing, nothing, nothing, nothing, nothing, + -1, NaN, NaN, NaN, false, Dict{Symbol, Any}()) + for (key, value) in kwargs + MOI.set(optimizer, MOI.RawParameter(key), value) + end + return optimizer + end +end + +varmap(optimizer::Optimizer, vi::MOI.VariableIndex) = optimizer.varmap[vi.value] + +function MOI.supports(optimizer::Optimizer, param::MOI.RawParameter) + return param.name in ALLOWED_OPTIONS +end +function MOI.set(optimizer::Optimizer, param::MOI.RawParameter, value) + if !MOI.supports(optimizer, param) + throw(MOI.UnsupportedAttribute(param)) end + optimizer.options[param.name] = value +end +function MOI.get(optimizer::Optimizer, param::MOI.RawParameter) + # TODO: This gives a poor error message if the name of the parameter is invalid. + return optimizer.options[param.name] +end + +MOI.supports(::Optimizer, ::MOI.Silent) = true +function MOI.set(optimizer::Optimizer, ::MOI.Silent, value::Bool) + optimizer.silent = value +end +MOI.get(optimizer::Optimizer, ::MOI.Silent) = optimizer.silent + +MOI.get(::Optimizer, ::MOI.SolverName) = "CSDP" +# See table "Return codes for easy_sdp() and CSDP" in `doc/csdpuser.pdf`. +const RAW_STATUS = [ + "Problem solved to optimality.", + "Problem is primal infeasible.", + "Problem is dual infeasible.", + "Problem solved to near optimality.", + "Maximum iterations reached.", + "Stuck at edge of primal feasibility.", + "Stuck at edge of dual feasibility.", + "Lack of progress.", + "X, Z, or O is singular.", + "NaN or Inf values encountered.", + "Program stopped by signal (SIXCPU, SIGTERM, etc.)"] + +function MOI.get(optimizer::Optimizer, ::MOI.RawStatusString) + return RAW_STATUS[optimizer.status + 1] +end +function MOI.get(optimizer::Optimizer, ::MOI.SolveTime) + return optimizer.solve_time end -Optimizer(; kws...) = SDOI.SDOIOptimizer(SDOptimizer(; kws...)) -MOI.get(::SDOptimizer, ::MOI.SolverName) = "CSDP" +function MOI.is_empty(optimizer::Optimizer) + return iszero(optimizer.objconstant) && + optimizer.objsign == 1 && + isempty(optimizer.blockdims) && + isempty(optimizer.varmap) && + isempty(optimizer.b) && + optimizer.C === nothing && + optimizer.As === nothing +end -function MOI.empty!(optimizer::SDOptimizer) +function MOI.empty!(optimizer::Optimizer) + optimizer.objconstant = zero(Cdouble) + optimizer.objsign = 1 + empty!(optimizer.blockdims) + empty!(optimizer.varmap) + empty!(optimizer.b) optimizer.C = nothing - optimizer.b = nothing optimizer.As = nothing optimizer.X = nothing optimizer.y = nothing @@ -36,49 +102,163 @@ function MOI.empty!(optimizer::SDOptimizer) optimizer.dobj = 0.0 end -function SDOI.init!(m::SDOptimizer, blkdims::Vector{Int}, nconstrs::Int) - @assert nconstrs >= 0 - dummy = nconstrs == 0 +function MOI.supports( + optimizer::Optimizer, + ::Union{MOI.ObjectiveSense, + MOI.ObjectiveFunction{<:Union{MOI.SingleVariable, + MOI.ScalarAffineFunction{Cdouble}}}}) + return true +end + +function MOI.supports_constraint( + ::Optimizer, ::Type{MOI.VectorOfVariables}, ::Type{MOI.Reals}) + return false +end +const SupportedSets = Union{MOI.Nonnegatives, MOI.PositiveSemidefiniteConeTriangle} +function MOI.supports_constraint( + ::Optimizer, ::Type{MOI.VectorOfVariables}, + ::Type{<:SupportedSets}) + return true +end +function MOI.supports_constraint( + ::Optimizer, ::Type{MOI.ScalarAffineFunction{Cdouble}}, + ::Type{MOI.EqualTo{Cdouble}}) + return true +end + +function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike; kws...) + return MOIU.automatic_copy_to(dest, src; kws...) +end +MOIU.supports_allocate_load(::Optimizer, copy_names::Bool) = !copy_names + +function MOIU.allocate(optimizer::Optimizer, ::MOI.ObjectiveSense, sense::MOI.OptimizationSense) + # To be sure that it is done before load(optimizer, ::ObjectiveFunction, ...), we do it in allocate + optimizer.objsign = sense == MOI.MIN_SENSE ? -1 : 1 +end +function MOIU.allocate(::Optimizer, ::MOI.ObjectiveFunction, ::Union{MOI.SingleVariable, MOI.ScalarAffineFunction}) end + +function MOIU.load(::Optimizer, ::MOI.ObjectiveSense, ::MOI.OptimizationSense) end +# Loads objective coefficient α * vi +function load_objective_term!(optimizer::Optimizer, α, vi::MOI.VariableIndex) + blk, i, j = varmap(optimizer, vi) + coef = optimizer.objsign * α + if i != j + coef /= 2 + end + # in SDP format, it is max and in MPB Conic format it is min + block(optimizer.C, blk)[i, j] = coef +end +function MOIU.load(optimizer::Optimizer, ::MOI.ObjectiveFunction, f::MOI.ScalarAffineFunction) + obj = MOIU.canonical(f) + optimizer.objconstant = f.constant + for t in obj.terms + if !iszero(t.coefficient) + load_objective_term!(optimizer, t.coefficient, t.variable_index) + end + end +end +function MOIU.load(optimizer::Optimizer, ::MOI.ObjectiveFunction, f::MOI.SingleVariable) + load_objective_term!(optimizer, one(Cdouble), f.variable) +end + +function new_block(optimizer::Optimizer, set::MOI.Nonnegatives) + push!(optimizer.blockdims, -MOI.dimension(set)) + blk = length(optimizer.blockdims) + for i in 1:MOI.dimension(set) + push!(optimizer.varmap, (blk, i, i)) + end +end + +function new_block(optimizer::Optimizer, set::MOI.PositiveSemidefiniteConeTriangle) + push!(optimizer.blockdims, set.side_dimension) + blk = length(optimizer.blockdims) + for i in 1:set.side_dimension + for j in 1:i + push!(optimizer.varmap, (blk, i, j)) + end + end +end + +function MOIU.allocate_constrained_variables(optimizer::Optimizer, + set::SupportedSets) + offset = length(optimizer.varmap) + new_block(optimizer, set) + ci = MOI.ConstraintIndex{MOI.VectorOfVariables, typeof(set)}(offset + 1) + return [MOI.VariableIndex(i) for i in offset .+ (1:MOI.dimension(set))], ci +end + +function MOIU.load_constrained_variables( + optimizer::Optimizer, vis::Vector{MOI.VariableIndex}, + ci::MOI.ConstraintIndex{MOI.VectorOfVariables}, + set::SupportedSets) +end + +function MOIU.load_variables(optimizer::Optimizer, nvars) + @assert nvars == length(optimizer.varmap) + dummy = isempty(optimizer.b) if dummy # See https://github.com/coin-or/Csdp/issues/2 - nconstrs = 1 - blkdims = [blkdims; -1] + optimizer.b = [one(Cdouble)] + optimizer.blockdims = [optimizer.blockdims; -1] end - m.C = blockmatzeros(blkdims) - m.b = zeros(Cdouble, nconstrs) - m.As = [constrmatzeros(i, blkdims) for i in 1:nconstrs] + optimizer.C = blockmatzeros(optimizer.blockdims) + optimizer.As = [constrmatzeros(i, optimizer.blockdims) for i in eachindex(optimizer.b)] if dummy # See https://github.com/coin-or/Csdp/issues/2 - m.b[1] = 1 - SDOI.block(m.As[1], length(blkdims))[1,1] = 1 + block(optimizer.As[1], length(optimizer.blockdims))[1, 1] = 1 end -end -function SDOI.setconstraintconstant!(m::SDOptimizer, val, constr::Integer) - #println("b[$constr] = $val") - m.b[constr] = val end -function SDOI.setconstraintcoefficient!(m::SDOptimizer, coef, constr::Integer, blk::Integer, i::Integer, j::Integer) - #println("A[$constr][$blk][$i, $j] = $coef") - SDOI.block(m.As[constr], blk)[i, j] = coef + +function MOIU.allocate_constraint(optimizer::Optimizer, + func::MOI.ScalarAffineFunction{Cdouble}, + set::MOI.EqualTo{Cdouble}) + push!(optimizer.b, MOI.constant(set)) + return AFFEQ(length(optimizer.b)) end -function SDOI.setobjectivecoefficient!(m::SDOptimizer, coef, blk::Integer, i::Integer, j::Integer) - #println("C[$blk][$i, $j] = $coef") - SDOI.block(m.C, blk)[i, j] = coef + +function MOIU.load_constraint(m::Optimizer, ci::AFFEQ, + f::MOI.ScalarAffineFunction, s::MOI.EqualTo) + if !iszero(MOI.constant(f)) + throw(MOI.ScalarFunctionConstantNotZero{ + Cdouble, MOI.ScalarAffineFunction{Cdouble}, MOI.EqualTo{Cdouble}}( + MOI.constant(f))) + end + f = MOIU.canonical(f) # sum terms with same variables and same outputindex + for t in f.terms + if !iszero(t.coefficient) + blk, i, j = varmap(m, t.variable_index) + coef = t.coefficient + if i != j + coef /= 2 + end + block(m.As[ci.value], blk)[i, j] = coef + end + end end -function MOI.optimize!(m::SDOptimizer) - As = map(A->A.csdp, m.As) - write_prob(m) +function MOI.optimize!(optimizer::Optimizer) + As = map(A->A.csdp, optimizer.As) + + write_prob(optimizer) + + start_time = time() + optimizer.X, optimizer.y, optimizer.Z = initsoln(optimizer.C, optimizer.b, As) + + options = optimizer.options + if optimizer.silent + options = copy(options) + options[:printlevel] = 0 + end - m.X, m.y, m.Z = initsoln(m.C, m.b, As) - #verbose = get(m.options, :verbose, true) - #m.status, m.pobj, m.dobj = easy_sdp(m.C, m.b, As, m.X, m.y, m.Z, verbose) - m.status, m.pobj, m.dobj = sdp(m.C, m.b, m.As, m.X, m.y, m.Z, m.options) + optimizer.status, optimizer.pobj, optimizer.dobj = sdp( + optimizer.C, optimizer.b, optimizer.As, optimizer.X, optimizer.y, + optimizer.Z, options) + optimizer.solve_time = time() - start_time end -function MOI.get(m::SDOptimizer, ::MOI.TerminationStatus) +function MOI.get(m::Optimizer, ::MOI.TerminationStatus) status = m.status if status == -1 return MOI.OPTIMIZE_NOT_CALLED @@ -101,7 +281,7 @@ function MOI.get(m::SDOptimizer, ::MOI.TerminationStatus) end end -function MOI.get(m::SDOptimizer, ::MOI.PrimalStatus) +function MOI.get(m::Optimizer, ::MOI.PrimalStatus) status = m.status if status == 0 return MOI.FEASIBLE_POINT @@ -118,7 +298,7 @@ function MOI.get(m::SDOptimizer, ::MOI.PrimalStatus) end end -function MOI.get(m::SDOptimizer, ::MOI.DualStatus) +function MOI.get(m::Optimizer, ::MOI.DualStatus) status = m.status if status == 0 return MOI.FEASIBLE_POINT @@ -135,18 +315,72 @@ function MOI.get(m::SDOptimizer, ::MOI.DualStatus) end end -function SDOI.getprimalobjectivevalue(m::SDOptimizer) - m.pobj +MOI.get(m::Optimizer, ::MOI.ResultCount) = 1 +function MOI.get(m::Optimizer, ::MOI.ObjectiveValue) + return m.objsign * m.pobj + m.objconstant +end +function MOI.get(m::Optimizer, ::MOI.DualObjectiveValue) + return m.objsign * m.dobj + m.objconstant +end +struct PrimalSolutionMatrix <: MOI.AbstractModelAttribute end +MOI.is_set_by_optimize(::PrimalSolutionMatrix) = true +MOI.get(optimizer::Optimizer, ::PrimalSolutionMatrix) = optimizer.X + +struct DualSolutionVector <: MOI.AbstractModelAttribute end +MOI.is_set_by_optimize(::DualSolutionVector) = true +MOI.get(optimizer::Optimizer, ::DualSolutionVector) = optimizer.y + +struct DualSlackMatrix <: MOI.AbstractModelAttribute end +MOI.is_set_by_optimize(::DualSlackMatrix) = true +MOI.get(optimizer::Optimizer, ::DualSlackMatrix) = optimizer.Z + +function block(optimizer::Optimizer, ci::MOI.ConstraintIndex{MOI.VectorOfVariables}) + return optimizer.varmap[ci.value][1] +end +function dimension(optimizer::Optimizer, ci::MOI.ConstraintIndex{MOI.VectorOfVariables}) + blockdim = optimizer.blockdims[block(optimizer, ci)] + if blockdim < 0 + return -blockdim + else + return MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(blockdim)) + end +end +function vectorize_block(M, blk::Integer, s::Type{MOI.Nonnegatives}) + return diag(block(M, blk)) +end +function vectorize_block(M::AbstractMatrix{Cdouble}, blk::Integer, s::Type{MOI.PositiveSemidefiniteConeTriangle}) where T + B = block(M, blk) + d = LinearAlgebra.checksquare(B) + n = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(d)) + v = Vector{Cdouble}(undef, n) + k = 0 + for j in 1:d + for i in 1:j + k += 1 + v[k] = B[i, j] + end + end + @assert k == n + return v +end + +function MOI.get(optimizer::Optimizer, ::MOI.VariablePrimal, vi::MOI.VariableIndex) + blk, i, j = varmap(optimizer, vi) + return block(MOI.get(optimizer, PrimalSolutionMatrix()), blk)[i, j] end -function SDOI.getdualobjectivevalue(m::SDOptimizer) - m.dobj + +function MOI.get(optimizer::Optimizer, ::MOI.ConstraintPrimal, + ci::MOI.ConstraintIndex{MOI.VectorOfVariables, S}) where S<:SupportedSets + return vectorize_block(MOI.get(optimizer, PrimalSolutionMatrix()), block(optimizer, ci), S) end -function SDOI.getX(m::SDOptimizer) - m.X +function MOI.get(m::Optimizer, ::MOI.ConstraintPrimal, ci::AFFEQ) + return m.b[ci.value] end -function SDOI.gety(m::SDOptimizer) - m.y + +function MOI.get(optimizer::Optimizer, ::MOI.ConstraintDual, + ci::MOI.ConstraintIndex{MOI.VectorOfVariables, S}) where S<:SupportedSets + return vectorize_block(MOI.get(optimizer, DualSlackMatrix()), block(optimizer, ci), S) end -function SDOI.getZ(m::SDOptimizer) - m.Z +function MOI.get(optimizer::Optimizer, ::MOI.ConstraintDual, ci::AFFEQ) + return -MOI.get(optimizer, DualSolutionVector())[ci.value] end diff --git a/src/MPB_wrapper.jl b/src/MPB_wrapper.jl index b0012a2..64caa6a 100644 --- a/src/MPB_wrapper.jl +++ b/src/MPB_wrapper.jl @@ -5,6 +5,15 @@ const SDM = SemidefiniteModels export CSDPMathProgModel, CSDPSolver +function checkoptions(d::Dict{Symbol, Any}) + for key in keys(d) + if !(key in ALLOWED_OPTIONS) + error("Option $key is not not a valid CSDP option. The valid options are $ALLOWED_OPTIONS.") + end + end + return d +end + struct CSDPSolver <: MPB.AbstractMathProgSolver options::Dict{Symbol,Any} end @@ -56,10 +65,10 @@ function SDM.setconstrB!(m::CSDPMathProgModel, val, constr::Integer) m.b[constr] = val end function SDM.setconstrentry!(m::CSDPMathProgModel, coef, constr::Integer, blk::Integer, i::Integer, j::Integer) - SDOI.block(m.As[constr], blk)[i, j] = coef + block(m.As[constr], blk)[i, j] = coef end function SDM.setobjentry!(m::CSDPMathProgModel, coef, blk::Integer, i::Integer, j::Integer) - SDOI.block(m.C, blk)[i, j] = coef + block(m.C, blk)[i, j] = coef end function MPB.optimize!(m::CSDPMathProgModel) diff --git a/src/blockdiag.jl b/src/blockdiag.jl new file mode 100644 index 0000000..95c8cfc --- /dev/null +++ b/src/blockdiag.jl @@ -0,0 +1,28 @@ +abstract type AbstractBlockMatrix{T} <: AbstractMatrix{T} end + +function nblocks end +function block end + +function Base.size(bm::AbstractBlockMatrix) + n = mapreduce(blk -> LinearAlgebra.checksquare(block(bm, blk)), + +, 1:nblocks(bm), init=0) + return (n, n) +end +function Base.getindex(bm::AbstractBlockMatrix, i::Integer, j::Integer) + (i < 0 || j < 0) && throw(BoundsError(i, j)) + for k in 1:nblocks(bm) + blk = block(bm, k) + n = size(blk, 1) + if i <= n && j <= n + return blk[i, j] + elseif i <= n || j <= n + return 0 + else + i -= n + j -= n + end + end + i, j = (i, j) .+ size(bm) + throw(BoundsError(i, j)) +end +Base.getindex(A::AbstractBlockMatrix, I::Tuple) = getindex(A, I...) diff --git a/src/blockmat.h.jl b/src/blockmat.h.jl index c0799e8..415640b 100644 --- a/src/blockmat.h.jl +++ b/src/blockmat.h.jl @@ -3,7 +3,7 @@ # TODO detect size and use bitstype because if the DLL changes and gets # compiled with NOSHORTS we are screwed with the following code... -@static if Compat.Sys.iswindows() +@static if Sys.iswindows() const csdpshort = Cushort else const csdpshort = Cint diff --git a/src/blockmat.jl b/src/blockmat.jl index 19ea202..e9b4ce2 100644 --- a/src/blockmat.jl +++ b/src/blockmat.jl @@ -15,16 +15,12 @@ end export fptr, ptr function mywrap(X::blockmatrix) - @compat finalizer(free_blockmatrix, X) + finalizer(free_blockmatrix, X) BlockMatrix(X) end function _unsafe_wrap(A, x, n, own::Bool) - @static if VERSION >= v"0.7-" - Base.unsafe_wrap(A, x, n, own=own) - else - Base.unsafe_wrap(A, x, n, own) - end + Base.unsafe_wrap(A, x, n, own=own) end function mywrap(x::Ptr{T}, len) where T @@ -32,7 +28,7 @@ function mywrap(x::Ptr{T}, len) where T # because the pointer it has has an offset y = _unsafe_wrap(Array, x + sizeof(T), len, false) # fptr takes care of this offset - @compat finalizer(s -> Libc.free(fptr(s)), y) + finalizer(s -> Libc.free(fptr(s)), y) y end @@ -53,9 +49,6 @@ end # * blockrec contains the blockdatarec, the category (dense or diagonal), and the block size # * blockmatrix contains the number of blocks and a vector containing the blocks (blockrec) (1-indexed) -using SemidefiniteOptInterface -const SDOI = SemidefiniteOptInterface - # blockrec mutable struct BlockRec <: AbstractMatrix{Cdouble} _blockdatarec::Vector{Cdouble} @@ -72,7 +65,7 @@ function BlockRec(a::Vector{Cdouble}, n::Int) end BlockRec(a::Vector, n::Int) = BlockRec(Vector{Cdouble}(a), n) function BlockRec(A::Matrix) - return BlockRec(reshape(A, length(A)), Compat.LinearAlgebra.checksquare(A)) + return BlockRec(reshape(A, length(A)), LinearAlgebra.checksquare(A)) end function BlockRec(A::Diagonal) a = Vector{Cdouble}(diag(A)) @@ -122,7 +115,7 @@ end # blockmatrix -mutable struct BlockMatrix <: SDOI.AbstractBlockMatrix{Cdouble} +mutable struct BlockMatrix <: AbstractBlockMatrix{Cdouble} jblocks::Vector{BlockRec} blocks::Vector{blockrec} csdp::blockmatrix @@ -205,7 +198,7 @@ end SparseBlock(A::SparseBlock) = A function SparseBlock(A::SparseMatrixCSC{Cdouble}) - n = Compat.LinearAlgebra.checksquare(A) + n = LinearAlgebra.checksquare(A) nn = nnz(A) I = csdpshort[] J = csdpshort[] @@ -272,7 +265,7 @@ function Base.setindex!(A::SparseBlock, v, i, j) end -mutable struct ConstraintMatrix <: SDOI.AbstractBlockMatrix{Cdouble} +mutable struct ConstraintMatrix <: AbstractBlockMatrix{Cdouble} jblocks::Vector{SparseBlock} csdp::constraintmatrix end @@ -313,13 +306,13 @@ end # Needed by MPB_wrapper function Base.getindex(A::Union{BlockMatrix, ConstraintMatrix}, i::Integer) - SDOI.block(A, i) + block(A, i) end -function SDOI.block(A::Union{BlockMatrix, ConstraintMatrix}, i::Integer) +function block(A::Union{BlockMatrix, ConstraintMatrix}, i::Integer) A.jblocks[i] end -SDOI.nblocks(A::Union{BlockMatrix, ConstraintMatrix}) = length(A.jblocks) +nblocks(A::Union{BlockMatrix, ConstraintMatrix}) = length(A.jblocks) function free_blockmatrix(m::blockmatrix) ccall((:free_mat, CSDP.csdp), Nothing, (blockmatrix,), m) diff --git a/src/options.jl b/src/options.jl index d411b0f..a20d454 100644 --- a/src/options.jl +++ b/src/options.jl @@ -1,4 +1,4 @@ -const allowed_options = [:printlevel, :axtol, :atytol, :objtol, :pinftol, :dinftol, :maxiter, :minstepfrac, :maxstepfrac, :minstepp, :minstepd, :usexzgap, :tweakgap, :affine, :perturbobj, :fastmode, :write_prob] +const ALLOWED_OPTIONS = [:printlevel, :axtol, :atytol, :objtol, :pinftol, :dinftol, :maxiter, :minstepfrac, :maxstepfrac, :minstepp, :minstepd, :usexzgap, :tweakgap, :affine, :perturbobj, :fastmode, :write_prob] # The :write_prob option is for the following function function write_prob(m) @@ -10,19 +10,10 @@ function write_prob(m) wrtf = "$wrt.$k" k += 1 end - Compat.@info "Writing problem to $(pwd())/$(wrtf)" + @info "Writing problem to $(pwd())/$(wrtf)" write_prob(wrtf, m.C, m.b, map(A->A.csdp, m.As)) end end end -function checkoptions(d::Dict{Symbol, Any}) - for key in keys(d) - if !(key in allowed_options) - error("Option $key is not not a valid CSDP option. The valid options are $allowed_options.") - end - end - d -end - options(params::Dict{Symbol}) = get(params, :printlevel, 1), paramstruc(params) diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index 6e605af..3220b70 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -1,37 +1,68 @@ +using Test + using MathOptInterface const MOI = MathOptInterface const MOIT = MOI.Test -const MOIB = MOI.Bridges - const MOIU = MOI.Utilities -MOIU.@model(SDModelData, - (), - (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan), - (MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives, - MOI.PositiveSemidefiniteConeTriangle), - (), - (MOI.SingleVariable,), - (MOI.ScalarAffineFunction,), - (MOI.VectorOfVariables,), - (MOI.VectorAffineFunction,)) +const MOIB = MOI.Bridges -const optimizer = MOIU.CachingOptimizer(SDModelData{Float64}(), CSDP.Optimizer(printlevel=0)) -const config = MOIT.TestConfig(atol=1e-4, rtol=1e-4) +import CSDP +const optimizer = CSDP.Optimizer() +MOI.set(optimizer, MOI.Silent(), true) @testset "SolverName" begin @test MOI.get(optimizer, MOI.SolverName()) == "CSDP" end +@testset "supports_default_copy_to" begin + @test MOIU.supports_allocate_load(optimizer, false) + @test !MOIU.supports_allocate_load(optimizer, true) +end + +# UniversalFallback is needed for starting values, even if they are ignored by CSDP +const cache = MOIU.UniversalFallback(MOIU.Model{Float64}()) +const cached = MOIU.CachingOptimizer(cache, optimizer) +const bridged = MOIB.full_bridge_optimizer(cached, Float64) +const config = MOIT.TestConfig(atol=1e-4, rtol=1e-4) + +@testset "Options" begin + param = MOI.RawParameter(:bad_option) + err = MOI.UnsupportedAttribute(param) + @test_throws err CSDP.Optimizer(bad_option = 1) +end + @testset "Unit" begin - MOIT.unittest(MOIB.SplitInterval{Float64}(optimizer), config, - [# Quadratic functions are not supported - "solve_qcp_edge_cases", "solve_qp_edge_cases", - # Integer and ZeroOne sets are not supported - "solve_integer_edge_cases", "solve_objbound_edge_cases"]) + MOIT.unittest(bridged, config, [ + # `TimeLimitSec` not supported. + "time_limit_sec", + # SingleVariable objective of bridged variables, will be solved by objective bridges + "solve_time", "raw_status_string", "solve_singlevariable_obj", + # Quadratic functions are not supported + "solve_qcp_edge_cases", "solve_qp_edge_cases", + # Integer and ZeroOne sets are not supported + "solve_integer_edge_cases", "solve_objbound_edge_cases", + "solve_zero_one_with_bounds_1", + "solve_zero_one_with_bounds_2", + "solve_zero_one_with_bounds_3"]) end @testset "Continuous Linear" begin - MOIT.contlineartest(MOIB.SplitInterval{Float64}(optimizer), config) + # See explanation in `MOI/test/Bridges/lazy_bridge_optimizer.jl`. + # This is to avoid `Variable.VectorizeBridge` which does not support + # `ConstraintSet` modification. + MOIB.remove_bridge(bridged, MOIB.Constraint.ScalarSlackBridge{Float64}) + MOIT.contlineartest(bridged, config, [ + # Finds `MOI.ALMOST_OPTIMAL` instead of `MOI.OPTIMAL` + "linear10b" + ]) end @testset "Continuous Conic" begin - MOIT.contconictest(MOIB.RootDet{Float64}(MOIB.GeoMean{Float64}(MOIB.RSOCtoPSD{Float64}(MOIB.SOCtoPSD{Float64}(optimizer)))), config, ["psds", "rootdets", "logdet", "exp"]) + MOIT.contconictest(bridged, config, [ + # Finds `MOI.OPTIMAL` instead of `MOI.INFEASIBLE`. + "soc3", + # See https://github.com/coin-or/Csdp/issues/11 + "rotatedsoc1v", + # Missing bridges + "rootdets", + # Does not support power and exponential cone + "pow", "logdet", "exp"]) end diff --git a/test/MPB_wrapper.jl b/test/MPB_wrapper.jl index 06ad148..d1249f4 100644 --- a/test/MPB_wrapper.jl +++ b/test/MPB_wrapper.jl @@ -1,11 +1,7 @@ const solver = CSDP.CSDPSolver(printlevel=0) import MathProgBase -@static if VERSION >= v"0.7-" - const MPB_test_path = joinpath(dirname(pathof(MathProgBase)), "..", "test") -else - const MPB_test_path = joinpath(Pkg.dir("MathProgBase"), "test") -end +const MPB_test_path = joinpath(dirname(pathof(MathProgBase)), "..", "test") @testset "Linear tests" begin include(joinpath(MPB_test_path, "linproginterface.jl")) @@ -16,7 +12,7 @@ end include(joinpath(MPB_test_path, "conicinterface.jl")) # FIXME fails on Windows 32 bits... Maybe I should put linear vars/cons # in a diagonal matrix in SemidefiniteModels.jl instead of many 1x1 blocks - @static if !Compat.Sys.iswindows() || Sys.WORD_SIZE != 32 + @static if !Sys.iswindows() || Sys.WORD_SIZE != 32 @testset "Conic linear tests" begin coniclineartest(solver, duals=true, tol=1e-6) end diff --git a/test/check_blas.jl b/test/check_blas.jl index a94d527..27f69f5 100644 --- a/test/check_blas.jl +++ b/test/check_blas.jl @@ -13,13 +13,13 @@ const openblas = "/usr/lib/libopenblas.so.0" A = [1.0 0.0; 0.0 -1.0] lda = max(1,stride(A,2)) -infot = Ref{Compat.LinearAlgebra.BlasInt}() +infot = Ref{LinearAlgebra.BlasInt}() uplo = 'U' -ccall((:dpotrf_64_, Compat.LinearAlgebra.BLAS.libblas), Void, - (Ptr{UInt8}, Ptr{Compat.LinearAlgebra.BlasInt}, Ptr{eltype(A)}, Ptr{Compat.LinearAlgebra.BlasInt}, Ptr{Compat.LinearAlgebra.BlasInt}), +ccall((:dpotrf_64_, LinearAlgebra.BLAS.libblas), Void, + (Ptr{UInt8}, Ptr{LinearAlgebra.BlasInt}, Ptr{eltype(A)}, Ptr{LinearAlgebra.BlasInt}, Ptr{LinearAlgebra.BlasInt}), &uplo, &size(A,1), A, &lda, info) ccall((:dpotrf_, openblas), Void, - (Ptr{UInt8}, Ptr{Compat.LinearAlgebra.BlasInt}, Ptr{eltype(A)}, Ptr{Compat.LinearAlgebra.BlasInt}, Ptr{Compat.LinearAlgebra.BlasInt}), + (Ptr{UInt8}, Ptr{LinearAlgebra.BlasInt}, Ptr{eltype(A)}, Ptr{LinearAlgebra.BlasInt}, Ptr{LinearAlgebra.BlasInt}), &uplo, &size(A,1), A, &lda, infot) diff --git a/test/runtests.jl b/test/runtests.jl index f91d136..a05daef 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,17 +1,16 @@ using CSDP -using Compat -using Compat.Test -using Compat.LinearAlgebra # For Diagonal +using Test +using LinearAlgebra # For Diagonal @testset "Interact with BLAS" begin vec = Cdouble[1.0, 2.0, 0.0, -1.0] l = length(vec) inc = 1 - n1 = ccall((BLAS.@blasfunc(dasum_), Compat.LinearAlgebra.BLAS.libblas), + n1 = ccall((BLAS.@blasfunc(dasum_), LinearAlgebra.BLAS.libblas), Cdouble, - (Ref{Compat.LinearAlgebra.BlasInt}, Ptr{Cdouble}, - Ref{Compat.LinearAlgebra.BlasInt}), + (Ref{LinearAlgebra.BlasInt}, Ptr{Cdouble}, + Ref{LinearAlgebra.BlasInt}), l, vec, inc) @test abs(n1 - 4) < 1e-15 end @@ -31,7 +30,7 @@ end end @testset "Example" begin - cd("../examples/") do + cd(joinpath(dirname(dirname(pathof(CSDP))), "examples")) do include(joinpath(pwd(), "example.jl")) @test size(X) == (7, 7) @test length(y) == 2 @@ -58,7 +57,6 @@ end end @testset "Options" begin - @test_throws ErrorException CSDP.Optimizer(bad_option = 1) @test CSDP.paramstruc(Dict(:axtol => 1e-7)).axtol == 1e-7 end