diff --git a/Project.toml b/Project.toml index f415620..f0ee8b0 100644 --- a/Project.toml +++ b/Project.toml @@ -11,16 +11,14 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" MathProgBase = "fdba3010-5040-5b88-9595-932c9decdf73" SemidefiniteModels = "169818f4-1a3d-53bf-95b3-11177825b1e3" -SemidefiniteOptInterface = "f0680fed-b2cd-5302-98f9-f4da282d86b5" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] BinaryProvider = "≥ 0.5.3" CxxWrap = "= 0.8.1" -MathOptInterface = "0.8" +MathOptInterface = "0.9" MathProgBase = "0.7" SemidefiniteModels = "~0.1.1" -SemidefiniteOptInterface = "0.5" julia = "1" [extras] diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 5e0846c..ed6fddc 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -2,67 +2,244 @@ export PARAMETER_DEFAULT, PARAMETER_UNSTABLE_BUT_FAST, PARAMETER_STABLE_BUT_SLOW using MathOptInterface 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} problem::SDPAProblem - options::Dict{Symbol,Any} - function SDOptimizer(; kwargs...) - new(SDPAProblem(), 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[], + SDPAProblem(), NaN, false, Dict{Symbol, Any}()) + for (key, value) in kwargs + MOI.set(optimizer, MOI.RawParameter(key), value) + end + return optimizer end end -Optimizer(; kws...) = SDOI.SDOIOptimizer(SDOptimizer(; kws...)) -MOI.get(::SDOptimizer, ::MOI.SolverName) = "SDPA" +varmap(optimizer::Optimizer, vi::MOI.VariableIndex) = optimizer.varmap[vi.value] -function MOI.empty!(optimizer::SDOptimizer) +function MOI.supports(optimizer::Optimizer, param::MOI.RawParameter) + return param.name in keys(SET_PARAM) +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) = "SDPA" + +# See https://www.researchgate.net/publication/247456489_SDPA_SemiDefinite_Programming_Algorithm_User's_Manual_-_Version_600 +# "SDPA (SemiDefinite Programming Algorithm) User's Manual — Version 6.00" Section 6.2 +const RAW_STATUS = Dict( + noINFO => "The iteration has exceeded the maxIteration and stopped with no informationon the primal feasibility and the dual feasibility.", + pdOPT => "The normal termination yielding both primal and dual approximate optimal solutions.", + pFEAS => "The primal problem got feasible but the iteration has exceeded the maxIteration and stopped.", + dFEAS => "The dual problem got feasible but the iteration has exceeded the maxIteration and stopped.", + pdFEAS => "Both primal problem and the dual problem got feasible, but the iterationhas exceeded the maxIteration and stopped.", + pdINF => "At least one of the primal problem and the dual problem is expected to be infeasible.", + pFEAS_dINF => "The primal problem has become feasible but the dual problem is expected to be infeasible.", + pINF_dFEAS => "The dual problem has become feasible but the primal problem is expected to be infeasible.", + pUNBD => "The primal problem is expected to be unbounded.", + dUNBD => "The dual problem is expected to be unbounded.") + +function MOI.get(optimizer::Optimizer, ::MOI.RawStatusString) + return RAW_STATUS[getPhaseValue(optimizer.problem)] +end +function MOI.get(optimizer::Optimizer, ::MOI.SolveTime) + return optimizer.solve_time +end + +function MOI.is_empty(optimizer::Optimizer) + return iszero(optimizer.objconstant) && + optimizer.objsign == 1 && + isempty(optimizer.blockdims) && + isempty(optimizer.varmap) && + isempty(optimizer.b) +end +function MOI.empty!(optimizer::Optimizer) + optimizer.objconstant = zero(Cdouble) + optimizer.objsign = 1 + empty!(optimizer.blockdims) + empty!(optimizer.varmap) + empty!(optimizer.b) optimizer.problem = SDPAProblem() 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 + inputElement(optimizer.problem, 0, blk, i, j, float(coef), false) +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 - nconstrs = 1 - blkdims = [blkdims; -1] - end - m.problem = SDPAProblem() - setParameterType(m.problem, PARAMETER_DEFAULT) - setparameters!(m.problem, m.options) - inputConstraintNumber(m.problem, nconstrs) - inputBlockNumber(m.problem, length(blkdims)) - for (i, blkdim) in enumerate(blkdims) - inputBlockSize(m.problem, i, blkdim) - inputBlockType(m.problem, i, blkdim < 0 ? LP : SDP) - end - initializeUpperTriangleSpace(m.problem) + optimizer.b = [one(Cdouble)] + optimizer.blockdims = [optimizer.blockdims; -1] + end + optimizer.problem = SDPAProblem() + setParameterType(optimizer.problem, PARAMETER_DEFAULT) + # TODO Take `silent` into account here + setparameters!(optimizer.problem, optimizer.options) + inputConstraintNumber(optimizer.problem, length(optimizer.b)) + inputBlockNumber(optimizer.problem, length(optimizer.blockdims)) + for (i, blkdim) in enumerate(optimizer.blockdims) + inputBlockSize(optimizer.problem, i, blkdim) + inputBlockType(optimizer.problem, i, blkdim < 0 ? LP : SDP) + end + initializeUpperTriangleSpace(optimizer.problem) + for i in eachindex(optimizer.b) + inputCVec(optimizer.problem, i, optimizer.b[i]) + end if dummy - SDOI.setconstraintconstant!(m, 1., 1) - SDOI.setconstraintcoefficient!(m, 1., 1, length(blkdims), 1, 1) + inputElement(optimizer.problem, 1, length(optimizer.blockdims), 1, 1, one(Cdouble), false) end end -function SDOI.setconstraintconstant!(m::SDOptimizer, val, constr::Integer) - @assert constr > 0 - #println("b[$constr] = $val") - inputCVec(m.problem, constr, val) -end -function SDOI.setconstraintcoefficient!(m::SDOptimizer, coef, constr::Integer, blk::Integer, i::Integer, j::Integer) - @assert constr > 0 - #println("A[$constr][$blk][$i, $j] = $coef") - inputElement(m.problem, constr, blk, i, j, float(coef), false) +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") - inputElement(m.problem, 0, blk, i, j, float(coef), false) + +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 + inputElement(m.problem, ci.value, blk, i, j, float(coef), false) + end + end end -function MOI.optimize!(m::SDOptimizer) +function MOI.optimize!(m::Optimizer) + start_time = time() SDPA.initializeUpperTriangle(m.problem, false) SDPA.initializeSolve(m.problem) SDPA.solve(m.problem) + m.solve_time = time() - start_time end -function MOI.get(m::SDOptimizer, ::MOI.TerminationStatus) +function MOI.get(m::Optimizer, ::MOI.TerminationStatus) status = getPhaseValue(m.problem) if status == noINFO return MOI.OPTIMIZE_NOT_CALLED @@ -87,7 +264,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 = getPhaseValue(m.problem) if status == noINFO return MOI.UNKNOWN_RESULT_STATUS @@ -112,7 +289,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 = getPhaseValue(m.problem) if status == noINFO return MOI.UNKNOWN_RESULT_STATUS @@ -137,10 +314,74 @@ function MOI.get(m::SDOptimizer, ::MOI.DualStatus) end end -SDOI.getprimalobjectivevalue(m::SDOptimizer) = getPrimalObj(m.problem) -SDOI.getdualobjectivevalue(m::SDOptimizer) = getDualObj(m.problem) -SDOI.getX(m::SDOptimizer) = PrimalSolution(m.problem) -function SDOI.gety(m::SDOptimizer) - unsafe_wrap(Array, getResultXVec(m.problem), getConstraintNumber(m.problem)) +MOI.get(m::Optimizer, ::MOI.ResultCount) = 1 +function MOI.get(m::Optimizer, ::MOI.ObjectiveValue) + return m.objsign * getPrimalObj(m.problem) + m.objconstant +end +function MOI.get(m::Optimizer, ::MOI.DualObjectiveValue) + return m.objsign * getDualObj(m.problem) + m.objconstant +end +struct PrimalSolutionMatrix <: MOI.AbstractModelAttribute end +MOI.is_set_by_optimize(::PrimalSolutionMatrix) = true +MOI.get(optimizer::Optimizer, ::PrimalSolutionMatrix) = PrimalSolution(optimizer.problem) + +struct DualSolutionVector <: MOI.AbstractModelAttribute end +MOI.is_set_by_optimize(::DualSolutionVector) = true +function MOI.get(optimizer::Optimizer, ::DualSolutionVector) + return unsafe_wrap(Array, getResultXVec(optimizer.problem), getConstraintNumber(optimizer.problem)) +end + +struct DualSlackMatrix <: MOI.AbstractModelAttribute end +MOI.is_set_by_optimize(::DualSlackMatrix) = true +MOI.get(optimizer::Optimizer, ::DualSlackMatrix) = VarDualSolution(optimizer.problem) + +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 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 MOI.get(m::Optimizer, ::MOI.ConstraintPrimal, ci::AFFEQ) + return m.b[ci.value] +end + +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 MOI.get(optimizer::Optimizer, ::MOI.ConstraintDual, ci::AFFEQ) + return -MOI.get(optimizer, DualSolutionVector())[ci.value] end -SDOI.getZ(m::SDOptimizer) = VarDualSolution(m.problem) diff --git a/src/SDPA.jl b/src/SDPA.jl index 7799bc7..b4531e7 100644 --- a/src/SDPA.jl +++ b/src/SDPA.jl @@ -20,9 +20,7 @@ function __init__() @initcxx end -using SemidefiniteOptInterface -SDOI = SemidefiniteOptInterface - +include("blockdiag.jl") include("blockmat.jl") include("options.jl") include("MOI_wrapper.jl") 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.jl b/src/blockmat.jl index b28b257..baf1b83 100644 --- a/src/blockmat.jl +++ b/src/blockmat.jl @@ -1,6 +1,6 @@ using LinearAlgebra -abstract type BlockSolution <: SDOI.AbstractBlockMatrix{Cdouble} end +abstract type BlockSolution <: AbstractBlockMatrix{Cdouble} end struct PrimalSolution <: BlockSolution problem::SDPAProblem end @@ -9,8 +9,8 @@ struct VarDualSolution <: BlockSolution problem::SDPAProblem end getptr(X::VarDualSolution, blk) = getResultXMat(X.problem, blk) -SDOI.nblocks(X::BlockSolution) = getBlockNumber(X.problem) -function SDOI.block(X::BlockSolution, blk::Integer) +nblocks(X::BlockSolution) = getBlockNumber(X.problem) +function block(X::BlockSolution, blk::Integer) if blk <= 0 || blk > getBlockNumber(X.problem) throw(BoundsError(X, blk)) end @@ -23,5 +23,5 @@ function SDOI.block(X::BlockSolution, blk::Integer) end # Needed by MPB_wrapper function Base.getindex(A::BlockSolution, i::Integer) - SDOI.block(A, i) + block(A, i) end diff --git a/src/options.jl b/src/options.jl index 11eb232..9e7aa79 100644 --- a/src/options.jl +++ b/src/options.jl @@ -1,17 +1,17 @@ -const setparam = Dict(:Mode =>setParameterType, - :MaxIteration =>setParameterMaxIteration, - :EpsilonStar =>setParameterEpsilonStar, - :LambdaStar =>setParameterLambdaStar, - :OmegaStar =>setParameterOmegaStar, - :LowerBound =>setParameterLowerBound, - :UpperBound =>setParameterUpperBound, - :BetaStar =>setParameterBetaStar, - :BetaBar =>setParameterBetaBar, - :GammaStar =>setParameterGammaStar, - :EpsilonDash =>setParameterEpsilonDash) +const SET_PARAM = Dict(:Mode => setParameterType, + :MaxIteration => setParameterMaxIteration, + :EpsilonStar => setParameterEpsilonStar, + :LambdaStar => setParameterLambdaStar, + :OmegaStar => setParameterOmegaStar, + :LowerBound => setParameterLowerBound, + :UpperBound => setParameterUpperBound, + :BetaStar => setParameterBetaStar, + :BetaBar => setParameterBetaBar, + :GammaStar => setParameterGammaStar, + :EpsilonDash => setParameterEpsilonDash) function setparameters!(problem, options) for (optname, optval) in options - setparam[optname](problem, optval) + SET_PARAM[optname](problem, optval) end end diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index 19b453d..fc176c1 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -1,3 +1,5 @@ +using Test + using MathOptInterface const MOI = MathOptInterface const MOIT = MOI.Test @@ -6,49 +8,54 @@ const MOIB = MOI.Bridges import SDPA const optimizer = SDPA.Optimizer() +MOI.set(optimizer, MOI.Silent(), true) @testset "SolverName" begin @test MOI.get(optimizer, MOI.SolverName()) == "SDPA" end -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,)) +@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 SDPA -const cache = MOIU.UniversalFallback(SDModelData{Float64}()) +const cache = MOIU.UniversalFallback(MOIU.Model{Float64}()) const cached = MOIU.CachingOptimizer(cache, optimizer) const bridged = MOIB.full_bridge_optimizer(cached, Float64) # test 1e-3 because of rsoc3 test, otherwise, 1e-5 is enough const config = MOIT.TestConfig(atol=1e-3, rtol=1e-3) @testset "Unit" begin - MOIT.unittest(bridged, config, - [# Multiple variable constraints on same variable - "solve_with_lowerbound", "solve_affine_interval", - "solve_with_upperbound", - # 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 "Linear tests" begin - MOIT.contlineartest(bridged, config, - ["linear12", - # https://github.com/JuliaOpt/MathOptInterface.jl/issues/693 - "linear1", - # Multiple variable constraints on same variable - "linear10", "linear10b", "linear14"]) + # 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, [ + # `MOI.UNKNOWN_RESULT_STATUS` instead of `MOI.INFEASIBILITY_CERTIFICATE` + "linear8a", + "linear12" + ]) end @testset "Conic tests" begin - MOIT.contconictest(bridged, config, - ["lin3", "soc3", "rootdets", "logdet", "exp", - # Multiple variable constraints on same variable - "rotatedsoc3"]) + MOIT.contconictest(bridged, config, [ + "lin3", "soc3", + # Missing bridges + "rootdets", + # Does not support power and exponential cone + "pow", "logdet", "exp"]) end diff --git a/test/example2.jl b/test/example2.jl index fbb5b74..ad0a04e 100644 --- a/test/example2.jl +++ b/test/example2.jl @@ -124,33 +124,30 @@ SDPA.solve(p) @test SDPA.getPrimalError(p) < 1e-10 @test SDPA.getDualError(p) < 1e-10 -using SemidefiniteOptInterface -const SDOI = SemidefiniteOptInterface - X = SDPA.VarDualSolution(p) -@test isapprox(SDOI.block(X, 1), [+6.392e-08 -9.638e-09; +@test isapprox(SDPA.block(X, 1), [+6.392e-08 -9.638e-09; -9.638e-09 +4.539e-08], rtol=1e-4) -@test isapprox(SDOI.block(X, 2), [+7.119e+00 +5.025e+00 +1.916e+00; +@test isapprox(SDPA.block(X, 2), [+7.119e+00 +5.025e+00 +1.916e+00; +5.025e+00 +4.415e+00 +2.506e+00; +1.916e+00 +2.506e+00 +2.048e+00], rtol=1e-4) -@test isapprox(SDOI.block(X, 3), Diagonal([+3.432e-01, +4.391e+00]), rtol=1e-4) +@test isapprox(SDPA.block(X, 3), Diagonal([+3.432e-01, +4.391e+00]), rtol=1e-4) Y = SDPA.PrimalSolution(p) @test size(Y) == (7, 7) @test Y[1, 3] == 0 @test_throws BoundsError Y[1, 0] -@test_throws BoundsError SDOI.block(Y, 0) -@test_throws BoundsError SDOI.block(Y, 4) -@test_throws BoundsError SDOI.block(Y, 1)[0, 1] -@test_throws BoundsError SDOI.block(Y, 1)[1, 3] -@test isapprox(SDOI.block(Y, 1), [+2.640e+00 +5.606e-01; +@test_throws BoundsError SDPA.block(Y, 0) +@test_throws BoundsError SDPA.block(Y, 4) +@test_throws BoundsError SDPA.block(Y, 1)[0, 1] +@test_throws BoundsError SDPA.block(Y, 1)[1, 3] +@test isapprox(SDPA.block(Y, 1), [+2.640e+00 +5.606e-01; +5.606e-01 +3.718e+00], rtol=1e-4) @test isapprox(Y[6, 6], +4.087e-07, rtol=1e-4) -@test isapprox(SDOI.block(Y, 2), [+7.616e-01 -1.514e+00 +1.139e+00; +@test isapprox(SDPA.block(Y, 2), [+7.616e-01 -1.514e+00 +1.139e+00; -1.514e+00 +3.008e+00 -2.264e+00; +1.139e+00 -2.264e+00 +1.705e+00], rtol=1e-3) @test isapprox(Y[4, 3], -1.514e+00, rtol=1e-3) -@test isapprox(SDOI.block(Y, 3), Diagonal([+4.087e-07, +3.195e-08]), rtol=1e-4) +@test isapprox(SDPA.block(Y, 3), Diagonal([+4.087e-07, +3.195e-08]), rtol=1e-4) @test isapprox(Y[6, 6], +4.087e-07, rtol=1e-4) @test Y[6, 7] == 0