diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 8a64e542..ef5ca3aa 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -484,17 +484,6 @@ function MOI.get( return attributes end -# MOI.ListOfOptimizerAttributesSet - -function MOI.get(::Optimizer, ::MOI.ListOfOptimizerAttributesSet) - attributes = MOI.ListOfOptimizerAttributesSet[] - timelim = MOI.get(o, MOI.TimeLimitSec()) - if timelim !== nothing - push!(attributes, MOI.TimeLimitSec()) - end - return attributes -end - include(joinpath("MOI_wrapper", "variable.jl")) include(joinpath("MOI_wrapper", "constraints.jl")) include(joinpath("MOI_wrapper", "linear_constraints.jl")) diff --git a/src/MOI_wrapper/HeuristicCallback.jl b/src/MOI_wrapper/HeuristicCallback.jl index a9102b77..3c33d5fd 100644 --- a/src/MOI_wrapper/HeuristicCallback.jl +++ b/src/MOI_wrapper/HeuristicCallback.jl @@ -15,10 +15,9 @@ end mutable struct HeuristicCb <: Heuristic scipd::SCIPData heurcallback::Function -end -# If no cut callback is given, the cut callback does nothing. -HeuristicCb(scipd::SCIPData) = HeuristicCb(scipd, cb_data -> nothing) + HeuristicCb(scipd::SCIPData, cb=cb_data -> nothing) = new(scipd, cb) +end """ Used for an argument to the heuristic callback, which in turn uses that argument to diff --git a/src/MOI_wrapper/UserCutCallback.jl b/src/MOI_wrapper/UserCutCallback.jl index 1d52ec09..b374ac72 100644 --- a/src/MOI_wrapper/UserCutCallback.jl +++ b/src/MOI_wrapper/UserCutCallback.jl @@ -29,10 +29,11 @@ end mutable struct CutCbSeparator <: AbstractSeparator scipd::SCIPData cutcallback::Function + + CutCbSeparator(scipd::SCIPData, cb=cb_data -> nothing) = new(scipd, cb) end # If no cut callback is given, the cut callback does nothing. -CutCbSeparator(scipd::SCIPData) = CutCbSeparator(scipd, cb_data -> nothing) """ Used for an argument to the cut callback, which in turn uses that argument to diff --git a/src/MOI_wrapper/quadratic_constraints.jl b/src/MOI_wrapper/quadratic_constraints.jl index bf59585c..ab8c3d82 100644 --- a/src/MOI_wrapper/quadratic_constraints.jl +++ b/src/MOI_wrapper/quadratic_constraints.jl @@ -50,19 +50,6 @@ function MOI.add_constraint( return ci end -function MOI.set( - o::SCIP.Optimizer, - ::MOI.ConstraintSet, - ci::MOI.ConstraintIndex{MOI.ScalarQuadraticFunction{Float64},S}, - set::S, -) where {S<:BOUNDS} - allow_modification(o) - lhs, rhs = bounds(o, set) - @SCIP_CALL SCIPchgLhsQuadratic(o, cons(o, ci), lhs) - @SCIP_CALL SCIPchgRhsQuadratic(o, cons(o, ci), rhs) - return nothing -end - function MOI.get( o::Optimizer, ::MOI.ConstraintFunction, diff --git a/src/MOI_wrapper/variable.jl b/src/MOI_wrapper/variable.jl index e437be03..c164eaed 100644 --- a/src/MOI_wrapper/variable.jl +++ b/src/MOI_wrapper/variable.jl @@ -199,10 +199,10 @@ function MOI.add_constraint(o::Optimizer, vi::MOI.VariableIndex, ::MOI.ZeroOne) allow_modification(o) v = var(o, vi) p_infeas = Ref{SCIP_Bool}() - @SCIP_CALL SCIPchgVarType(o, v, SCIP_VARTYPE_BINARY, p_infeas) lb, ub = SCIPvarGetLbOriginal(v), SCIPvarGetUbOriginal(v) # Store old bounds for later recovery. o.binbounds[vi] = MOI.Interval(lb, ub) + @SCIP_CALL SCIPchgVarType(o, v, SCIP_VARTYPE_BINARY, p_infeas) @SCIP_CALL SCIPchgVarLb(o, v, max(lb, 0.0)) @SCIP_CALL SCIPchgVarUb(o, v, min(ub, 1.0)) ci = MOI.ConstraintIndex{MOI.VariableIndex,MOI.ZeroOne}(vi.value) @@ -370,7 +370,7 @@ function MOI.delete( if type == _kSCIP_LESS_AND_GREATER_THAN && S <: MOI.LessThan o.bound_types[vi] = _kSCIP_GREATER_THAN elseif type == _kSCIP_LESS_AND_GREATER_THAN && S <: MOI.GreaterThan - o.bound_types[vi] = _kSCIP_LESS_AND_GREATER_THAN + o.bound_types[vi] = _kSCIP_LESS_THAN else delete!(o.bound_types, vi) end diff --git a/test/MOI_tests.jl b/test/MOI_tests.jl index 1b8435e4..560c8a81 100644 --- a/test/MOI_tests.jl +++ b/test/MOI_tests.jl @@ -730,6 +730,7 @@ function _test_presolving(presolving) ), ) MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MAX_SENSE) + @test MOI.supports(optimizer, SCIP.Presolving()) @test MOI.get(optimizer, SCIP.Presolving()) == true MOI.set(optimizer, SCIP.Presolving(), presolving) @test MOI.get(optimizer, SCIP.Presolving()) == presolving @@ -865,9 +866,11 @@ function test_heuristic_callback() values[findmax(x_frac)[2]] = 1.0 values[1] = 1.0 values[2] = 1.0 + @test MOI.supports(o, MOI.HeuristicSolution(callback_data)) MOI.submit(o, MOI.HeuristicSolution(callback_data), x, values) global ncalls[] += 1 end + @test MOI.supports(o, MOI.HeuristicCallback()) MOI.set(o, MOI.HeuristicCallback(), heuristic_callback) MOI.optimize!(o) @test ncalls[] > 0 @@ -1195,6 +1198,7 @@ function test_obtaining_the_LP_solution() ) calls += 1 end + @test MOI.supports(optimizer, MOI.UserCutCallback()) MOI.set(optimizer, MOI.UserCutCallback(), cutcallback) # solve the problem MOI.optimize!(optimizer) @@ -1237,6 +1241,7 @@ function test_cutting_one_optimal_solution() MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MAX_SENSE) calls = 0 function cutcallback(cb_data) + @test MOI.supports(optimizer, MOI.UserCut{SCIP.CutCbData}(cb_data)) MOI.submit( optimizer, MOI.UserCut{SCIP.CutCbData}(cb_data), @@ -1500,6 +1505,147 @@ function test_AddSingleCut_too_strong_cut() return end +function test_ScalarFunctionConstantNotZero_quadratic() + model = SCIP.Optimizer() + x = MOI.add_variable(model) + f = 1.0 * x * x + 2.0 + @test_throws( + MOI.ScalarFunctionConstantNotZero, + MOI.add_constraint(model, f, MOI.LessThan(3.0)), + ) + return +end + +function test_ListOfSupportedNonlinearOperators() + model = SCIP.Optimizer() + op = MOI.get(model, MOI.ListOfSupportedNonlinearOperators()) + @test op isa Vector{Symbol} + @test length(op) == 11 + return +end + +function test_nonlinear_epigraph_functions() + op(f, args...) = MOI.ScalarNonlinearFunction(f, Any[args...]) + default_set = MOI.Interval(1.0, 2.0) + for (fn, set, t_value) in [ + (x -> op(:exp, 1.0 * x + 2.0), default_set, exp(3)), + (x -> op(:exp, 1.0 * x * x + 2.0), default_set, exp(3)), + (x -> op(:exp, 1.0 * x * x + 2.0 * x + 3.0), default_set, exp(6)), + # :/ + (x -> op(:/, 2, x), MOI.Interval(1.0, 2.0), 1.0), + (x -> op(:/, 2, x), MOI.Interval(1.0, 1.5), 4 / 3), + # :abs + (x -> op(:abs, x), MOI.Interval(1.0, 2.0), 1.0), + (x -> op(:abs, x), MOI.Interval(-2.0, 2.0), 0.0), + (x -> op(:abs, x), MOI.Interval(-2.0, -1.3), 1.3), + # :exp + (x -> op(:exp, x), MOI.Interval(-2.0, -1.3), exp(-2)), + (x -> op(:exp, x), MOI.Interval(2.0, 3.0), exp(2)), + # :log + (x -> op(:-, op(:log, x)), MOI.Interval(2.0, 3.0), -log(3)), + # :sin + (x -> op(:sin, x), MOI.Interval(pi, 2 * pi), -1.0), + (x -> op(:sin, x), MOI.Interval(0.0, pi), 0.0), + # :cos + (x -> op(:cos, x), MOI.Interval(pi, 2 * pi), -1.0), + (x -> op(:cos, x), MOI.Interval(0.0, pi), -1.0), + (x -> op(:cos, x), MOI.Interval(-pi / 2, pi / 2), 0.0), + # :sqrt + (x -> op(:-, op(:sqrt, x)), MOI.Interval(2.0, 3.0), -sqrt(3)), + ] + model = SCIP.Optimizer() + MOI.set(model, MOI.Silent(), true) + x, _ = MOI.add_constrained_variable(model, set) + t = MOI.add_variable(model) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + F = MOI.ScalarAffineFunction{Float64} + MOI.set(model, MOI.ObjectiveFunction{F}(), 1.0 * t) + MOI.add_constraint(model, op(:-, t, fn(x)), MOI.GreaterThan(0.0)) + MOI.optimize!(model) + @test ≈(MOI.get(model, MOI.VariablePrimal(), t), t_value; atol=1e-4) + end + return +end + +function test_unsupported_nonlinear_operator() + model = SCIP.Optimizer() + x = MOI.add_variable(model) + f = MOI.ScalarNonlinearFunction(:foo, Any[x]) + @test_throws( + MOI.UnsupportedNonlinearOperator(:foo), + MOI.add_constraint(model, f, MOI.GreaterThan(0.0)), + ) + f = MOI.ScalarNonlinearFunction(:^, Any[x, x]) + @test_throws( + MOI.UnsupportedNonlinearOperator(:^), + MOI.add_constraint(model, f, MOI.GreaterThan(0.0)), + ) + return +end + +function test_delete_variable_with_bounds() + for sets in ( + (MOI.LessThan(1.0),), + (MOI.GreaterThan(1.0),), + (MOI.EqualTo(1.0),), + (MOI.Interval(1.0, 2.0),), + (MOI.GreaterThan(1.0), MOI.LessThan(2.0)), + ) + model = SCIP.Optimizer() + x = MOI.add_variable(model) + for set in sets + MOI.add_constraint(model, x, set) + end + ret = MOI.get(model, MOI.ListOfConstraintTypesPresent()) + @test length(ret) == length(sets) + for set in sets + (MOI.VariableIndex, typeof(set)) in ret + end + MOI.delete(model, x) + @test isempty(MOI.get(model, MOI.ListOfConstraintTypesPresent())) + end + return +end + +function test_BoundAlreadySet() + model = SCIP.Optimizer() + x = MOI.add_variable(model) + MOI.add_constraint(model, x, MOI.GreaterThan(1.0)) + MOI.add_constraint(model, x, MOI.LessThan(2.0)) + @test_throws( + MOI.LowerBoundAlreadySet, + MOI.add_constraint(model, x, MOI.GreaterThan(1.0)), + ) + @test_throws( + MOI.UpperBoundAlreadySet, + MOI.add_constraint(model, x, MOI.LessThan(1.0)), + ) + return +end + +function test_delete_bounds() + model = SCIP.Optimizer() + x = MOI.add_variable(model) + c_l = MOI.add_constraint(model, x, MOI.GreaterThan(1.0)) + c_u = MOI.add_constraint(model, x, MOI.LessThan(2.0)) + MOI.delete(model, c_u) + MOI.add_constraint(model, x, MOI.LessThan(3.0)) + @test MOI.get(model, MOI.ConstraintSet(), c_u) == MOI.LessThan(3.0) + MOI.delete(model, c_l) + MOI.add_constraint(model, x, MOI.GreaterThan(-1.0)) + @test MOI.get(model, MOI.ConstraintSet(), c_l) == MOI.GreaterThan(-1.0) + return +end + +function test_get_binary_with_bounds() + model = SCIP.Optimizer() + x = MOI.add_variable(model) + c = MOI.add_constraint(model, x, MOI.Interval(0.5, 1.1)) + MOI.add_constraint(model, x, MOI.ZeroOne()) + @test MOI.get(model, MOI.ConstraintSet(), c) == MOI.Interval(0.5, 1.1) + return +end + end # module TestMOIWrapper TestMOIWrapper.runtests()