diff --git a/Project.toml b/Project.toml index 9f58b56..62579e6 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "SDPT3" uuid = "e33b2407-87ff-50a0-8b27-f0fe7855237d" repo = "https://github.com/jump-dev/SDPT3.jl.git" -version = "0.0.3" +version = "0.1.0" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" @@ -11,8 +11,8 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [compat] MATLAB = "0.7, 0.8" -MathOptInterface = "0.10.4" -julia = "1" +MathOptInterface = "1" +julia = "1.6" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 8053e58..3334044 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -72,8 +72,8 @@ mutable struct Optimizer <: MOI.AbstractOptimizer silent::Bool options::Dict{Symbol, Any} - function Optimizer(; kwargs...) - optimizer = new( + function Optimizer() + return new( Float64[], VariableInfo[], Int[], Vector{Int}[], Vector{Float64}[], Vector{Int}[], Vector{Int}[], Vector{Float64}[], Int[], Vector{Int}[], Vector{Float64}[], Vector{Int}[], Vector{Int}[], Vector{Float64}[], @@ -86,20 +86,8 @@ mutable struct Optimizer <: MOI.AbstractOptimizer Float64[], Float64[], Vector{Float64}[], Vector{Float64}[], # Z Dict{String, Any}(), Dict{String, Any}(), nothing, NaN, - false, Dict{Symbol, Any}()) - if !isempty(kwargs) - @warn("""Passing optimizer attributes as keyword arguments to - SDPT3.Optimizer is deprecated. Use - MOI.set(model, MOI.RawOptimizerAttribute("key"), value) - or - JuMP.set_optimizer_attribute(model, "key", value) - instead. - """) - end - for (key, value) in kwargs - MOI.set(optimizer, MOI.RawOptimizerAttribute(string(key)), value) - end - return optimizer + false, Dict{Symbol, Any}(), + ) end end @@ -340,6 +328,9 @@ function MOI.add_constraint(optimizer::Optimizer, func::MOI.ScalarAffineFunction end push!(optimizer.b, MOI.constant(set)) con = length(optimizer.b) + if isempty(func.terms) + throw(ArgumentError("SDPT3 does not support equality constraints with no term: 0 == $(MOI.constant(set)).")) + end for term in func.terms info = optimizer.variable_info[term.variable.value] if info.variable_type == FREE @@ -556,11 +547,15 @@ end MOI.get(::Optimizer, ::MOI.ResultCount) = 1 function MOI.get(optimizer::Optimizer, attr::MOI.ObjectiveValue) MOI.check_result_index_bounds(optimizer, attr) + # FIXME For `INFEASIBILITY_CERTIFICATE`, SDPT3 just returns infinite values + # See https://github.com/jump-dev/MathOptInterface.jl/issues/1759 sign = sense_to_sign(optimizer.objective_sense) return sign * optimizer.primal_objective_value + optimizer.objective_constant end function MOI.get(optimizer::Optimizer, attr::MOI.DualObjectiveValue) MOI.check_result_index_bounds(optimizer, attr) + # FIXME For `INFEASIBILITY_CERTIFICATE`, SDPT3 just returns infinite values + # See https://github.com/jump-dev/MathOptInterface.jl/issues/1759 sign = sense_to_sign(optimizer.objective_sense) return sign * optimizer.dual_objective_value + optimizer.objective_constant end diff --git a/src/SDPT3.jl b/src/SDPT3.jl index 12b00c9..f304b7b 100644 --- a/src/SDPT3.jl +++ b/src/SDPT3.jl @@ -51,6 +51,9 @@ function sdpt3(blk::Matrix, options = Dict{String, Any}(string(key) => value for (key, value) in kws) @assert all(i -> size(At[i], 2) == length(b), 1:length(At)) @assert length(At) == size(blk, 1) + if isempty(b) + throw(ArgumentError("SDPT3 does not support problems with no constraint.")) + end #@assert all(i -> size(A[i], 1) == dim(blk[i, 1], blk[i, 2]), 1:length(A)) #@assert all(i -> length(C[i], 1) == dim(blk[i, 1], blk[i, 2]), 1:length(A)) # There are 6 output arguments so we use `6` below diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index 8b92b5d..42241ee 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -1,85 +1,148 @@ -using Test +module TestSDPT3 +using Test using MathOptInterface -const MOI = MathOptInterface -const MOIT = MOI.DeprecatedTest -const MOIU = MOI.Utilities -const MOIB = MOI.Bridges - import SDPT3 -const OPTIMIZER = SDPT3.Optimizer() -MOI.set(OPTIMIZER, MOI.Silent(), true) -@testset "SolverName" begin - @test MOI.get(OPTIMIZER, MOI.SolverName()) == "SDPT3" +const MOI = MathOptInterface + +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end + end + return end -@testset "supports_incremental_interface" begin - @test MOI.supports_incremental_interface(OPTIMIZER) +function test_solver_name() + @test MOI.get(SDPT3.Optimizer(), MOI.SolverName()) == "SDPT3" end -# UniversalFallback is needed for starting values, even if they are ignored by SDPT3 -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-4, rtol=1e-4) +function test_supports_incremental_interface() + @test MOI.supports_incremental_interface(SDPT3.Optimizer()) +end -@testset "Options" begin - optimizer = SDPT3.Optimizer(printlevel = 1) +function test_options() + optimizer = SDPT3.Optimizer() + MOI.set(optimizer, MOI.RawOptimizerAttribute("printlevel"), 1) @test MOI.get(optimizer, MOI.RawOptimizerAttribute("printlevel")) == 1 param = MOI.RawOptimizerAttribute("bad_option") err = MOI.UnsupportedAttribute(param) - @test_throws err SDPT3.Optimizer(bad_option = 1) + @test_throws err MOI.set(optimizer, MOI.RawOptimizerAttribute("bad_option"), 1) end -@testset "Unit" begin - MOIT.unittest(BRIDGED, CONFIG, [ - # Need MOI v0.9.5 - "solve_result_index", - # Get `termcode` -1, i.e. "relative gap < infeasibility". - "solve_blank_obj", - # Get `termcode` 3, i.e. "norm(X) or norm(Z) diverging". - "solve_affine_equalto", - # Fails because there is no constraint. - "solve_unbounded_model", - # `TimeLimitSec` not supported. - "time_limit_sec", - # `NumberOfThreads` not supported. - "number_threads", - # 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 - # 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, String[ - # Throws error: total dimension of C should be > length(b) - "linear15", - "partial_start" - ]) -end -@testset "Continuous Quadratic" begin - MOIT.contquadratictest(BRIDGED, CONFIG, [ - # Non-convex - "ncqcp", - # Quadratic function not strictly convex - "socp"]) -end -@testset "Continuous Conic" begin - MOIT.contconictest(BRIDGED, CONFIG, [ - # `MOI.OTHER_ERROR` - "lin2f", "geomean3v", - # `MOI.NUMERICAL_ERROR` - "lin4", - # `ExponentialCone` and `PowerCone` not supported. - "exp", "dualexp", "pow", "dualpow", "logdet", "relentr", - # `RootDetConeSquare` -> `RootDetConeTriangle` bridge missing. - "rootdets" - ]) +function test_runtests() + model = MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + MOI.Bridges.full_bridge_optimizer( + MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + SDPT3.Optimizer(), + ), + Float64, + ), + # This does not work as with some modifications, the bridges with try + # getting `ConstraintFunction` which is not supported by SDPT3 + #MOI.instantiate(SDPT3.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-4, + atol = 1e-4, + exclude = Any[ + MOI.ConstraintBasisStatus, + MOI.VariableBasisStatus, + MOI.ObjectiveBound, + MOI.SolverVersion, + ], + ), + exclude = String[ + # Expected test failures: + #"test_attribute_SolverVersion", + # TODO Remove when https://github.com/jump-dev/MathOptInterface.jl/issues/1758 is fixed + "test_model_copy_to_UnsupportedAttribute", + # `NUMERICAL_ERROR` + "test_conic_linear_INFEASIBLE_2", + "test_solve_DualStatus_INFEASIBILITY_CERTIFICATE_EqualTo_upper", + "test_solve_DualStatus_INFEASIBILITY_CERTIFICATE_EqualTo_lower", + "test_solve_DualStatus_INFEASIBILITY_CERTIFICATE_GreaterThan", + "test_solve_DualStatus_INFEASIBILITY_CERTIFICATE_Interval_upper", + "test_solve_DualStatus_INFEASIBILITY_CERTIFICATE_LessThan", + "test_solve_DualStatus_INFEASIBILITY_CERTIFICATE_VariableIndex_LessThan", + "test_modification_const_vectoraffine_zeros", + "test_constraint_ScalarAffineFunction_EqualTo", + # `OTHER_ERROR` + "test_conic_linear_VectorAffineFunction_2", + "test_conic_GeometricMeanCone_VectorOfVariables_3", + # ArgumentError: SDPT3 does not support problems with no constraint. + "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", + # ArgumentError: SDPT3 does not support equality constraints with no term + "test_linear_VectorAffineFunction_empty_row", + "test_conic_PositiveSemidefiniteConeTriangle", + # FIXME + # Expression: ≈(MOI.get(model, MOI.ConstraintPrimal(), c2), 0, atol = atol, rtol = rtol) + # Evaluated: 1.7999998823840366 ≈ 0 (atol=0.0001, rtol=0.0001) + "test_linear_FEASIBILITY_SENSE", + # FIXME + # Error using pretransfo (line 149) + # Size b mismatch + "test_conic_SecondOrderCone_negative_post_bound_ii", + "test_conic_SecondOrderCone_negative_post_bound_iii", + "test_conic_SecondOrderCone_no_initial_bound", + # TODO SDPT3 just returns an infinite ObjectiveValue + "test_unbounded_MIN_SENSE", + "test_unbounded_MIN_SENSE_offset", + "test_unbounded_MAX_SENSE", + "test_unbounded_MAX_SENSE_offset", + # TODO SDPT3 just returns an infinite DualObjectiveValue + "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", + # TODO investigate + "test_conic_GeometricMeanCone_VectorAffineFunction_2", + "test_conic_GeometricMeanCone_VectorOfVariables_2", + "test_objective_qp_ObjectiveFunction_edge_cases", + "test_objective_qp_ObjectiveFunction_zero_ofdiag", + "test_variable_solve_with_lowerbound", + # FIXME + # test_linear_DUAL_INFEASIBLE_2: Test Failed at /home/blegat/.julia/packages/MathOptInterface/IIN1o/src/Test/test_linear.jl:1514 + # Expression: MOI.get(model, MOI.TerminationStatus()) == MOI.DUAL_INFEASIBLE || MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE_OR_UNBOUNDED + "test_linear_DUAL_INFEASIBLE_2", + ], + ) + return end + +end # module + +TestSDPT3.runtests()