diff --git a/Project.toml b/Project.toml index abe5a72..e74560b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DSDP" uuid = "2714ae6b-e930-5b4e-9c21-d0bacf577842" -repo = "https://github.com/joehuchette/DSDP.jl.git" -version = "0.0.1" +repo = "https://github.com/jump-dev/DSDP.jl.git" +version = "0.1.0" [deps] BinDeps = "9e28174c-4ba2-5203-b857-d8d62c4213ee" @@ -10,7 +10,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" [compat] -MathOptInterface = "0.10" +MathOptInterface = "1" julia = "1.6" [extras] diff --git a/README.md b/README.md index 1c4b86c..cef9771 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ | [![Build Status][build-img]][build-url] | | [![Codecov branch][codecov-img]][codecov-url] | -**Important note**: this is still a work on progress. The use of semidefinite matrices in linear equality constraints has not been implemented yet so only linear programs can be solved at the moment with DSDP. +**Important note**: this is still a work on progress. The use of positive semidefinite matrices in linear equality constraints has not been implemented yet so only linear programs can be solved at the moment with DSDP. Julia wrapper for the [DSDP](http://www.mcs.anl.gov/hs/software/DSDP/) semidefinite programming solver. diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 15f0304..15fa2e8 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -188,7 +188,9 @@ end MOI.supports_add_constrained_variables(::Optimizer, ::Type{MOI.Reals}) = false const SupportedSets = Union{MOI.Nonnegatives, MOI.PositiveSemidefiniteConeTriangle} -MOI.supports_add_constrained_variables(::Optimizer, ::Type{<:SupportedSets}) = true +# TODO positive semidefinite matrix variables not supported yet in linear equality constraints +#MOI.supports_add_constrained_variables(::Optimizer, ::Type{<:SupportedSets}) = true +MOI.supports_add_constrained_variables(::Optimizer, ::Type{<:MOI.Nonnegatives}) = true function MOI.supports_constraint( ::Optimizer, ::Type{MOI.ScalarAffineFunction{Cdouble}}, ::Type{MOI.EqualTo{Cdouble}}) @@ -266,12 +268,12 @@ function _setcoefficient!(m::Optimizer, coef, constr::Integer, blk::Integer, i:: push!(m.lpdrows, m.blk[blk] + i - 1) # -1 because indexing starts at 0 in DSDP push!(m.lpcoefs, coef) else - error("Semidefinite matrix variables are not supported yet so only linear programs are supported at the moment.") + error("Positive semidefinite matrix variables are not supported yet so only linear programs are supported at the moment.") end end # Loads objective coefficient α * vi -function load_objective_term!(optimizer::Optimizer, α, vi::MOI.VariableIndex) +function load_objective_term!(optimizer::Optimizer, index_map, α, vi::MOI.VariableIndex) blk, i, j = varmap(optimizer, vi) coef = optimizer.objective_sign * α if i != j @@ -301,6 +303,9 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike) ) end cis_src = MOI.get(src, MOI.ListOfConstraintIndices{AFF,EQ}()) + if isempty(cis_src) + throw(ArgumentError("DSDP does not support problems with no constraint.")) + end dest.b = Vector{Cdouble}(undef, length(cis_src)) _free(dest) @@ -352,7 +357,7 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike) SetDualObjective(dest.dsdp, k, MOI.constant(set)) for t in func.terms if !iszero(t.coefficient) - blk, i, j = varmap(dest, t.variable) + blk, i, j = varmap(dest, index_map[t.variable]) coef = t.coefficient if i != j coef /= 2 @@ -390,6 +395,7 @@ 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], ) diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index 0d3daeb..3384728 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -1,71 +1,131 @@ +module TestDSDP + using Test using MathOptInterface +import DSDP const MOI = MathOptInterface -const MOIT = MOI.DeprecatedTest -const MOIU = MOI.Utilities -const MOIB = MOI.Bridges - -import DSDP -const optimizer = DSDP.Optimizer() -@testset "SolverName" begin - @test MOI.get(optimizer, MOI.SolverName()) == "DSDP" +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end + end + return end -const cache = MOIU.UniversalFallback(MOIU.Model{Float64}()) -const cached = MOIU.CachingOptimizer(cache, optimizer) -const bridged = MOIB.full_bridge_optimizer(cached, Float64) -const config = MOIT.Config(atol=1e-2, rtol=1e-2) +function test_solver_name() + @test MOI.get(DSDP.Optimizer(), MOI.SolverName()) == "DSDP" +end -@testset "Unit" begin - MOIT.unittest(bridged, config, [ - # To investigate... - "solve_duplicate_terms_obj", "solve_with_lowerbound", - "solve_blank_obj", "solve_affine_interval", - "solve_singlevariable_obj", "solve_constant_obj", - "solve_affine_deletion_edge_cases", - "solve_with_upperbound", - # Expression: MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE - # Evaluated: MathOptInterface.OPTIMAL == MathOptInterface.INFEASIBLE - "solve_farkas_equalto_lower", - "solve_farkas_equalto_upper", - "solve_farkas_variable_lessthan", - "solve_farkas_variable_lessthan_max", - "solve_farkas_greaterthan", - "solve_farkas_interval_upper", - "solve_farkas_lessthan", - "solve_farkas_interval_lower", - # TODO should work when SDP is complete - "solve_qp_zero_offdiag", - "solve_start_soc", - # `NumberOfThreads` not supported. - "number_threads", - # `TimeLimitSec` not supported. - "time_limit_sec", - # `SolveTime` not supported. - "solve_time", - # 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" - ]) +function test_options() + param = MOI.RawOptimizerAttribute("bad_option") + err = MOI.UnsupportedAttribute(param) + @test_throws err MOI.set( + DSDP.Optimizer(), + MOI.RawOptimizerAttribute("bad_option"), + 0, + ) end -@testset "Continuous Linear" begin - # 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, [ - # To investigate... - "linear1", "linear2", "linear3", "linear5", "linear8a", "linear9", "linear10", "linear10b", "linear11", "linear12", "linear14", "partial_start" - ]) +function test_runtests() + model = MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + MOI.instantiate(DSDP.Optimizer, with_bridge_type = Float64), + ) + # `Variable.ZerosBridge` makes dual needed by some tests fail. + MOI.Bridges.remove_bridge( + model.optimizer, + MathOptInterface.Bridges.Variable.ZerosBridge{Float64}, + ) + MOI.set(model, MOI.Silent(), true) + MOI.Test.runtests( + model, + MOI.Test.Config( + rtol = 1e-3, + atol = 1e-3, + exclude = Any[ + MOI.ConstraintBasisStatus, + MOI.VariableBasisStatus, + MOI.ObjectiveBound, + MOI.SolverVersion, + ], + ), + exclude = String[ + # ArgumentError: DSDP does not support problems with no constraint. + # See https://github.com/jump-dev/MathOptInterface.jl/issues/1741#issuecomment-1057286739 + "test_solve_optimize_twice", + "test_solve_result_index", + "test_quadratic_nonhomogeneous", + "test_quadratic_integration", + "test_objective_ObjectiveFunction_constant", + "test_objective_ObjectiveFunction_VariableIndex", + "test_objective_FEASIBILITY_SENSE_clears_objective", + "test_modification_transform_singlevariable_lessthan", + "test_modification_set_singlevariable_lessthan", + "test_modification_delete_variables_in_a_batch", + "test_modification_delete_variable_with_single_variable_obj", + "test_modification_const_scalar_objective", + "test_modification_coef_scalar_objective", + "test_attribute_RawStatusString", + "test_attribute_SolveTimeSec", + "test_objective_ObjectiveFunction_blank", + "test_objective_ObjectiveFunction_duplicate_terms", + "test_solve_TerminationStatus_DUAL_INFEASIBLE", + # TODO investigate + # Expression: MOI.get(model, MOI.TerminationStatus()) == config.infeasible_status + # Evaluated: MathOptInterface.OPTIMAL == MathOptInterface.INFEASIBLE + "test_conic_NormInfinityCone_INFEASIBLE", + "test_conic_NormOneCone_INFEASIBLE", + "test_conic_linear_INFEASIBLE", + "test_conic_linear_INFEASIBLE_2", + "test_solve_DualStatus_INFEASIBILITY_CERTIFICATE_EqualTo_lower", + "test_solve_DualStatus_INFEASIBILITY_CERTIFICATE_EqualTo_upper", + "test_solve_DualStatus_INFEASIBILITY_CERTIFICATE_GreaterThan", + "test_solve_DualStatus_INFEASIBILITY_CERTIFICATE_Interval_lower", + "test_solve_DualStatus_INFEASIBILITY_CERTIFICATE_Interval_upper", + "test_solve_DualStatus_INFEASIBILITY_CERTIFICATE_LessThan", + "test_solve_DualStatus_INFEASIBILITY_CERTIFICATE_VariableIndex_LessThan", + # TODO investigate + # Incorrect result value + "test_conic_NormInfinityCone_3", + "test_conic_NormInfinityCone_VectorAffineFunction", + "test_conic_NormInfinityCone_VectorOfVariables", + "test_conic_NormOneCone", + "test_conic_NormOneCone_VectorAffineFunction", + "test_conic_NormOneCone_VectorOfVariables", + "test_conic_linear_VectorAffineFunction", + "test_conic_linear_VectorAffineFunction_2", + "test_conic_linear_VectorOfVariables", + "test_constraint_ScalarAffineFunction_Interval", + # Incorrect objective + # See https://github.com/jump-dev/MathOptInterface.jl/issues/1759 + "test_infeasible_MAX_SENSE", + "test_infeasible_MAX_SENSE_offset", + "test_infeasible_MIN_SENSE", + "test_infeasible_MIN_SENSE_offset", + "test_infeasible_affine_MAX_SENSE", + "test_infeasible_affine_MAX_SENSE_offset", + "test_infeasible_affine_MIN_SENSE", + "test_infeasible_affine_MIN_SENSE_offset", + "test_linear_Interval_inactive", + "test_linear_integration", + "test_linear_integration_Interval", + "test_linear_integration_delete_variables", + "test_linear_transform", + "test_modification_affine_deletion_edge_cases", + "test_modification_multirow_vectoraffine_nonpos", + "test_modification_set_scalaraffine_lessthan", + "test_variable_solve_with_lowerbound", + "test_variable_solve_with_upperbound", + ], + ) + return end -#@testset "Conic tests" begin -# contconictest(bridged, config) -#end +end # module + +TestDSDP.runtests()