diff --git a/Project.toml b/Project.toml index bacc0b72..47b9db86 100644 --- a/Project.toml +++ b/Project.toml @@ -10,7 +10,7 @@ SCIP_jll = "e5ac4fe4-a920-5659-9bf8-f9f73e9e79ce" [compat] Ipopt_jll = "^3.13.2" -MathOptInterface = "^0.9.6" +MathOptInterface = "0.10" SCIP_jll = "^0.1.2" julia = "1" diff --git a/README.md b/README.md index da923c5e..bf911925 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ features through MathOptInterface. However, the focus is on keeping the wrapper simple and avoiding duplicate storage of model data. As a consequence, we do not currently support some features such as retrieving -constraints by name (`SingleVariable`-set constraints are not stored as SCIP +constraints by name (`VariableIndex`-set constraints are not stored as SCIP constraints explicitly). Support for more constraint types (quadratic/SOC, SOS1/2, nonlinear expression) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 9efb401c..44ce32ae 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -6,7 +6,6 @@ const MOIU = MOI.Utilities const VI = MOI.VariableIndex const CI = MOI.ConstraintIndex # supported functions -const SVF = MOI.SingleVariable const SAF = MOI.ScalarAffineFunction{Float64} const SQF = MOI.ScalarQuadraticFunction{Float64} const VAF = MOI.VectorAffineFunction{Float64} @@ -44,8 +43,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer scip_data = SCIPData(scip, Dict(), Dict(), 0, 0, Dict(), Dict(), Dict()) - o = new(scip_data, PtrMap(), ConsTypeMap(), Dict(), Dict(), Dict(), - nothing) + o = new(scip_data, PtrMap(), ConsTypeMap(), Dict(), Dict(), Dict(), nothing) finalizer(free_scip, o) # Set all parameters given as keyword arguments, replacing the @@ -53,7 +51,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer # allowed in Julia identifiers. for (key, value) in kwargs name = replace(String(key),"_" => "/") - MOI.set(o, Param(name), value) + MOI.set(o, MOI.RawOptimizerAttribute(name), value) end return o end @@ -135,16 +133,23 @@ end MOI.get(::Optimizer, ::MOI.SolverName) = "SCIP" -MOIU.supports_default_copy_to(model::Optimizer, copy_names::Bool) = !copy_names +MOI.supports_incremental_interface(::Optimizer) = true + +function _throw_if_invalid(o::Optimizer, ci::CI{F, S}) where {F, S} + if !in(ConsRef(ci.value), o.constypes[F, S]) + throw(MOI.InvalidIndex(ci)) + end + return nothing +end # Keep SCIP-specific alias for backwards-compatibility. -const Param = MOI.RawParameter +const Param = MOI.RawOptimizerAttribute -function MOI.get(o::Optimizer, param::MOI.RawParameter) +function MOI.get(o::Optimizer, param::MOI.RawOptimizerAttribute) return get_parameter(o.inner, param.name) end -function MOI.set(o::Optimizer, param::MOI.RawParameter, value) +function MOI.set(o::Optimizer, param::MOI.RawOptimizerAttribute, value) o.params[param.name] = value set_parameter(o.inner, param.name, value) return nothing @@ -153,11 +158,11 @@ end MOI.supports(o::Optimizer, ::MOI.Silent) = true function MOI.get(o::Optimizer, ::MOI.Silent) - return MOI.get(o, MOI.RawParameter("display/verblevel")) == 0 + return MOI.get(o, MOI.RawOptimizerAttribute("display/verblevel")) == 0 end function MOI.set(o::Optimizer, ::MOI.Silent, value) - param = MOI.RawParameter("display/verblevel") + param = MOI.RawOptimizerAttribute("display/verblevel") if value MOI.set(o, param, 0) # no output at all else @@ -168,7 +173,7 @@ end MOI.supports(o::Optimizer, ::MOI.TimeLimitSec) = true function MOI.get(o::Optimizer, ::MOI.TimeLimitSec) - raw_value = MOI.get(o, MOI.RawParameter("limits/time")) + raw_value = MOI.get(o, MOI.RawOptimizerAttribute("limits/time")) if raw_value == SCIPinfinity(o) return nothing else @@ -178,9 +183,9 @@ end function MOI.set(o::Optimizer, ::MOI.TimeLimitSec, value) if value === nothing - MOI.set(o, MOI.RawParameter("limits/time"), SCIPinfinity(o)) + MOI.set(o, MOI.RawOptimizerAttribute("limits/time"), SCIPinfinity(o)) else - MOI.set(o, MOI.RawParameter("limits/time"), value) + MOI.set(o, MOI.RawOptimizerAttribute("limits/time"), value) end end @@ -213,8 +218,8 @@ function MOI.empty!(o::Optimizer) return nothing end -function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike; kws...) - return MOIU.automatic_copy_to(dest, src; kws...) +function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike) + return MOIU.default_copy_to(dest, src) end MOI.get(o::Optimizer, ::MOI.Name) = SCIPgetProbName(o) @@ -224,10 +229,21 @@ function MOI.get(o::Optimizer, ::MOI.NumberOfConstraints{F,S}) where {F,S} return haskey(o.constypes, (F, S)) ? length(o.constypes[F, S]) : 0 end -function MOI.get(o::Optimizer, ::MOI.ListOfConstraints) +function MOI.get(o::Optimizer, ::MOI.ListOfConstraintTypesPresent) return collect(keys(o.constypes)) end +function MOI.get(o::Optimizer, ::MOI.ListOfConstraintIndices{F, S}) where {F, S} + list_indices = Vector{CI{F,S}}() + if !haskey(o.constypes, (F, S)) + return list_indices + end + for cref in o.constypes[F, S] + push!(list_indices, CI{F,S}(cref.val)) + end + return sort!(list_indices, by=v->v.value) +end + function set_start_values(o::Optimizer) if isempty(o.start) # no primal start values are given diff --git a/src/MOI_wrapper/abspower_constraints.jl b/src/MOI_wrapper/abspower_constraints.jl index 36480f1f..3f2b7c27 100644 --- a/src/MOI_wrapper/abspower_constraints.jl +++ b/src/MOI_wrapper/abspower_constraints.jl @@ -47,6 +47,7 @@ function MOI.add_constraint(o::Optimizer, func::VECTOR, set::ABSPOWER) end function MOI.delete(o::Optimizer, ci::CI{VECTOR, ABSPOWER}) + _throw_if_invalid(o, ci) allow_modification(o) delete!(o.constypes[VECTOR, ABSPOWER], ConsRef(ci.value)) delete!(o.reference, cons(o, ci)) @@ -55,6 +56,7 @@ function MOI.delete(o::Optimizer, ci::CI{VECTOR, ABSPOWER}) end function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{VECTOR, ABSPOWER}) + _throw_if_invalid(o, ci) c = cons(o, ci)::Ptr{SCIP_CONS} lvar = SCIPgetLinearVarAbspower(o, c) nlvar = SCIPgetNonlinearVarAbspower(o, c) @@ -62,13 +64,12 @@ function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{VECTOR, ABSPOWER end function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{VECTOR, ABSPOWER}) + _throw_if_invalid(o, ci) c = cons(o, ci)::Ptr{SCIP_CONS} - n = SCIPgetExponentAbspower(o, c) a = SCIPgetOffsetAbspower(o, c) coef = SCIPgetCoefLinearAbspower(o, c) lhs = SCIPgetLhsAbspower(o, c) rhs = SCIPgetRhsAbspower(o, c) - return AbsolutePowerSet(n, a, coef, lhs, rhs) end diff --git a/src/MOI_wrapper/constraints.jl b/src/MOI_wrapper/constraints.jl index 0ff432d0..ea960170 100644 --- a/src/MOI_wrapper/constraints.jl +++ b/src/MOI_wrapper/constraints.jl @@ -8,3 +8,27 @@ function MOI.set(o::Optimizer, ::MOI.ConstraintName, ci::CI, name::String) @SCIP_CALL SCIPchgConsName(o, cons(o, ci), name) return nothing end + +function MOI.set(o::Optimizer, ::MOI.ConstraintName, ci::CI{VI}, name::String) + throw(MOI.VariableIndexConstraintNameError()) + return nothing +end + +function MOI.is_valid(o::Optimizer, c::CI{F, S}) where {F, S} + cons_set = get(o.constypes, (F, S), nothing) + if cons_set === nothing + return false + end + if !in(ConsRef(c.value), cons_set) + return false + end + return haskey(o.inner.conss, SCIP.ConsRef(c.value)) +end + +# function MOI.get(::Optimizer, ::MOI.ListOfConstraintAttributesSet{F, S}) where {F, S} +# return [MOI.ConstraintName()] +# end + +# function MOI.get(::Optimizer, ::MOI.ListOfConstraintAttributesSet{<:Union{SAF, SQF}, S}) where {S} +# return Any[MOI.ConstraintName(), MOI.ConstraintPrimal()] +# end diff --git a/src/MOI_wrapper/indicator_constraints.jl b/src/MOI_wrapper/indicator_constraints.jl index f02b7427..7f264545 100644 --- a/src/MOI_wrapper/indicator_constraints.jl +++ b/src/MOI_wrapper/indicator_constraints.jl @@ -1,34 +1,36 @@ # Indicator constraints -MOI.supports_constraint(::Optimizer, ::Type{<:MOI.VectorAffineFunction}, ::Type{<:MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE, <:MOI.LessThan}}) = true +MOI.supports_constraint(::Optimizer, ::Type{<:MOI.VectorAffineFunction}, ::Type{<:MOI.Indicator{MOI.ACTIVATE_ON_ONE, <:MOI.LessThan}}) = true -function MOI.add_constraint(o::Optimizer, func::MOI.VectorAffineFunction{T}, set::MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE, LT}) where {T<:Real, LT<:MOI.LessThan} +function MOI.add_constraint(o::Optimizer, func::MOI.VectorAffineFunction{T}, set::MOI.Indicator{MOI.ACTIVATE_ON_ONE, LT}) where {T<:Real, LT<:MOI.LessThan} allow_modification(o) first_index_terms = [v.scalar_term for v in func.terms if v.output_index == 1] scalar_index_terms = [v.scalar_term for v in func.terms if v.output_index != 1] length(first_index_terms) == 1 || error("There should be exactly one term in output_index 1, found $(length(first_index_terms))") - y = VarRef(first_index_terms[1].variable_index.value) - x = [VarRef(vi.variable_index.value) for vi in scalar_index_terms] + y = VarRef(first_index_terms[1].variable.value) + x = [VarRef(vi.variable.value) for vi in scalar_index_terms] a = [vi.coefficient for vi in scalar_index_terms] b = func.constants[2] # a^T x + b <= c ===> a^T <= c - b cr = add_indicator_constraint(o.inner, y, x, a, MOI.constant(set.set) - b) - ci = CI{MOI.VectorAffineFunction{T}, MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE, LT}}(cr.val) + ci = CI{MOI.VectorAffineFunction{T}, MOI.Indicator{MOI.ACTIVATE_ON_ONE, LT}}(cr.val) register!(o, ci) register!(o, cons(o, ci), cr) return ci end -function MOI.delete(o::Optimizer, ci::CI{MOI.VectorAffineFunction{T}, MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE, LT}}) where {T<:Real, LT<:MOI.LessThan} +function MOI.delete(o::Optimizer, ci::CI{MOI.VectorAffineFunction{T}, MOI.Indicator{MOI.ACTIVATE_ON_ONE, LT}}) where {T<:Real, LT<:MOI.LessThan} + _throw_if_invalid(o, ci) allow_modification(o) - delete!(o.constypes[MOI.VectorAffineFunction{T}, MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE, LT}], ConsRef(ci.value)) + delete!(o.constypes[MOI.VectorAffineFunction{T}, MOI.Indicator{MOI.ACTIVATE_ON_ONE, LT}], ConsRef(ci.value)) delete!(o.reference, cons(o, ci)) delete(o.inner, ConsRef(ci.value)) return nothing end -function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{MOI.VectorAffineFunction{T}, MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE, LT}}) where {T<:Real, LT<:MOI.LessThan} +function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{MOI.VectorAffineFunction{T}, MOI.Indicator{MOI.ACTIVATE_ON_ONE, LT}}) where {T<:Real, LT<:MOI.LessThan} + _throw_if_invalid(o, ci) indicator_cons = cons(o, ci)::Ptr{SCIP_CONS} bin_var = SCIPgetBinaryVarIndicator(indicator_cons)::Ptr{SCIP_VAR} slack_var = SCIPgetSlackVarIndicator(indicator_cons)::Ptr{SCIP_VAR} @@ -46,7 +48,8 @@ function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{MOI.VectorAffine return VAF(vcat(ind_terms, vec_terms), [0.0, 0.0]) end -function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{MOI.VectorAffineFunction{T}, MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE, LT}}) where {T<:Real, LT<:MOI.LessThan} +function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{MOI.VectorAffineFunction{T}, MOI.Indicator{MOI.ACTIVATE_ON_ONE, LT}}) where {T<:Real, LT<:MOI.LessThan} + _throw_if_invalid(o, ci) indicator_cons = cons(o, ci)::Ptr{SCIP_CONS} linear_cons = SCIPgetLinearConsIndicator(indicator_cons)::Ptr{SCIP_CONS} @@ -54,5 +57,9 @@ function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{MOI.VectorAffineFunct rhs = SCIPgetRhsLinear(o, linear_cons) lhs == -SCIPinfinity(o) || error("Have lower bound on indicator constraint!") - return MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(rhs)) + return MOI.Indicator{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(rhs)) +end + +function MOI.get(o::Optimizer, ::MOI.ListOfConstraintIndices{F, S}) where {F <: MOI.VectorAffineFunction{Float64}, S <: MOI.Indicator{MOI.ACTIVATE_ON_ONE, MOI.LessThan{Float64}}} + return Vector{F, S}(o.constypes[F,S]) end diff --git a/src/MOI_wrapper/linear_constraints.jl b/src/MOI_wrapper/linear_constraints.jl index 348e1b31..711f505e 100644 --- a/src/MOI_wrapper/linear_constraints.jl +++ b/src/MOI_wrapper/linear_constraints.jl @@ -2,14 +2,14 @@ MOI.supports_constraint(o::Optimizer, ::Type{SAF}, ::Type{<:BOUNDS}) = true -function MOI.add_constraint(o::Optimizer, func::SAF, set::S) where {S <: BOUNDS} +function MOI.add_constraint(o::Optimizer, func::F, set::S) where {F <: SAF, S <: BOUNDS} if func.constant != 0.0 error("SCIP does not support linear constraints with a constant offset.") end allow_modification(o) - varrefs = [VarRef(t.variable_index.value) for t in func.terms] + varrefs = [VarRef(t.variable.value) for t in func.terms] coefs = [t.coefficient for t in func.terms] lhs, rhs = bounds(set) @@ -17,13 +17,14 @@ function MOI.add_constraint(o::Optimizer, func::SAF, set::S) where {S <: BOUNDS} rhs = rhs === nothing ? SCIPinfinity(o) : rhs cr = add_linear_constraint(o.inner, varrefs, coefs, lhs, rhs) - ci = CI{SAF, S}(cr.val) + ci = CI{F, S}(cr.val) register!(o, ci) register!(o, cons(o, ci), cr) return ci end -function MOI.delete(o::Optimizer, ci::CI{SAF, S}) where {S <: BOUNDS} +function MOI.delete(o::Optimizer, ci::CI{<:SAF, S}) where {S <: BOUNDS} + _throw_if_invalid(o, ci) allow_modification(o) delete!(o.constypes[SAF, S], ConsRef(ci.value)) delete!(o.reference, cons(o, ci)) @@ -31,7 +32,7 @@ function MOI.delete(o::Optimizer, ci::CI{SAF, S}) where {S <: BOUNDS} return nothing end -function MOI.set(o::SCIP.Optimizer, ::MOI.ConstraintSet, ci::CI{SAF,S}, set::S) where {S <: BOUNDS} +function MOI.set(o::SCIP.Optimizer, ::MOI.ConstraintSet, ci::CI{<:SAF,S}, set::S) where {S <: BOUNDS} allow_modification(o) lhs, rhs = bounds(set) @@ -44,7 +45,8 @@ function MOI.set(o::SCIP.Optimizer, ::MOI.ConstraintSet, ci::CI{SAF,S}, set::S) return nothing end -function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{SAF, S}) where S <: BOUNDS +function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{<:SAF, S}) where {S <: BOUNDS} + _throw_if_invalid(o, ci) c = cons(o, ci) nvars::Int = SCIPgetNVarsLinear(o, c) vars = unsafe_wrap(Array{Ptr{SCIP_VAR}}, SCIPgetVarsLinear(o, c), nvars) @@ -55,13 +57,14 @@ function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{SAF, S}) where S return SAF(terms, 0.0) end -function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{SAF, S}) where S <: BOUNDS +function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{<:SAF, S}) where {S <: BOUNDS} + _throw_if_invalid(o, ci) lhs = SCIPgetLhsLinear(o, cons(o, ci)) rhs = SCIPgetRhsLinear(o, cons(o, ci)) return from_bounds(S, lhs, rhs) end -function MOI.modify(o::Optimizer, ci::CI{SAF, <:BOUNDS}, +function MOI.modify(o::Optimizer, ci::CI{<:SAF, <:BOUNDS}, change::MOI.ScalarCoefficientChange{Float64}) allow_modification(o) @SCIP_CALL SCIPchgCoefLinear(o, cons(o, ci), var(o, change.variable), change.new_coefficient) diff --git a/src/MOI_wrapper/objective.jl b/src/MOI_wrapper/objective.jl index f9f51e9c..18c014db 100644 --- a/src/MOI_wrapper/objective.jl +++ b/src/MOI_wrapper/objective.jl @@ -6,7 +6,7 @@ MOI.supports(::Optimizer, ::MOI.ObjectiveSense) = true MOI.supports(::Optimizer, ::MOI.ObjectiveFunction{SAF}) = true -MOI.supports(::Optimizer, ::MOI.ObjectiveFunction{SVF}) = true +MOI.supports(::Optimizer, ::MOI.ObjectiveFunction{VI}) = true function MOI.set(o::Optimizer, ::MOI.ObjectiveFunction{SAF}, obj::SAF) allow_modification(o) @@ -18,7 +18,7 @@ function MOI.set(o::Optimizer, ::MOI.ObjectiveFunction{SAF}, obj::SAF) # set new objective coefficients, summing coefficients for t in obj.terms - v = var(o, t.variable_index) + v = var(o, t.variable) oldcoef = SCIPvarGetObj(v) newcoef = oldcoef + t.coefficient @SCIP_CALL SCIPchgVarObj(o, v, newcoef) @@ -30,8 +30,8 @@ function MOI.set(o::Optimizer, ::MOI.ObjectiveFunction{SAF}, obj::SAF) end # Note that SCIP always uses a scalar affine function internally! -function MOI.set(o::Optimizer, ::MOI.ObjectiveFunction{SVF}, obj::SVF) - aff_obj = SAF([AFF_TERM(1.0, obj.variable)], 0.0) +function MOI.set(o::Optimizer, ::MOI.ObjectiveFunction{VI}, obj::VI) + aff_obj = SAF([AFF_TERM(1.0, obj)], 0.0) return MOI.set(o, MOI.ObjectiveFunction{SAF}(), aff_obj) end @@ -47,14 +47,14 @@ function MOI.get(o::Optimizer, ::MOI.ObjectiveFunction{SAF}) end # Note that SCIP always uses a scalar affine function internally! -function MOI.get(o::Optimizer, ::MOI.ObjectiveFunction{SVF}) +function MOI.get(o::Optimizer, ::MOI.ObjectiveFunction{VI}) aff_obj = MOI.get(o, MOI.ObjectiveFunction{SAF}()) if (length(aff_obj.terms) != 1 || aff_obj.terms[1].coefficient != 1.0 || aff_obj.constant != 0.0) - error("Objective is not single variable: $aff_obj !") + throw(InexactError(:get, VI, aff_obj)) end - return SVF(aff_obj.terms[1].variable_index) + return aff_obj.terms[1].variable end function MOI.set(o::Optimizer, ::MOI.ObjectiveSense, sense::MOI.OptimizationSense) @@ -63,7 +63,7 @@ function MOI.set(o::Optimizer, ::MOI.ObjectiveSense, sense::MOI.OptimizationSens @SCIP_CALL SCIPsetObjsense(o, SCIP_OBJSENSE_MINIMIZE) elseif sense == MOI.MAX_SENSE @SCIP_CALL SCIPsetObjsense(o, SCIP_OBJSENSE_MAXIMIZE) - elseif sense == MOI.FEASIBLITY_SENSE + elseif sense == MOI.FEASIBILITY_SENSE @warn "FEASIBLITY_SENSE not supported by SCIP.jl" maxlog=1 end return nothing diff --git a/src/MOI_wrapper/quadratic_constraints.jl b/src/MOI_wrapper/quadratic_constraints.jl index 9eb081bb..b1a5e676 100644 --- a/src/MOI_wrapper/quadratic_constraints.jl +++ b/src/MOI_wrapper/quadratic_constraints.jl @@ -10,12 +10,12 @@ function MOI.add_constraint(o::Optimizer, func::SQF, set::S) where {S <: BOUNDS} allow_modification(o) # affine terms - linrefs = [VarRef(t.variable_index.value) for t in func.affine_terms] + linrefs = [VarRef(t.variable.value) for t in func.affine_terms] lincoefs = [t.coefficient for t in func.affine_terms] # quadratic terms - quadrefs1 = [VarRef(t.variable_index_1.value) for t in func.quadratic_terms] - quadrefs2 = [VarRef(t.variable_index_2.value) for t in func.quadratic_terms] + quadrefs1 = [VarRef(t.variable_1.value) for t in func.quadratic_terms] + quadrefs2 = [VarRef(t.variable_2.value) for t in func.quadratic_terms] # Divide coefficients by 2 iff they come from the diagonal: # Take coef * x * y as-is, but turn coef * x^2 into coef/2 * x^2. factor = 1.0 .- 0.5 * (quadrefs1 .== quadrefs2) @@ -35,6 +35,7 @@ function MOI.add_constraint(o::Optimizer, func::SQF, set::S) where {S <: BOUNDS} end function MOI.delete(o::Optimizer, ci::CI{SQF, S}) where {S <: BOUNDS} + _throw_if_invalid(o, ci) allow_modification(o) delete!(o.constypes[SQF, S], ConsRef(ci.value)) delete!(o.reference, cons(o, ci)) @@ -55,13 +56,8 @@ function MOI.set(o::SCIP.Optimizer, ::MOI.ConstraintSet, ci::CI{SQF,S}, set::S) return nothing end -function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{SQF, S}) where S <: BOUNDS - lhs = SCIPgetLhsQuadratic(o, cons(o, ci)) - rhs = SCIPgetRhsQuadratic(o, cons(o, ci)) - return from_bounds(S, lhs, rhs) -end - -function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{SQF, S}) where S <: BOUNDS +function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{SQF, S}) where {S <: BOUNDS} + _throw_if_invalid(o, ci) c = cons(o, ci) affterms = AFF_TERM[] @@ -93,10 +89,17 @@ function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{SQF, S}) where S push!(quadterms, QUAD_TERM(term.coef, VI(ref(o, term.var1).val), VI(ref(o, term.var2).val))) end - return SQF(affterms, quadterms, 0.0) + return SQF(quadterms, affterms, 0.0) +end + +function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{SQF, S}) where {S <: BOUNDS} + _throw_if_invalid(o, ci) + lhs = SCIPgetLhsQuadratic(o, cons(o, ci)) + rhs = SCIPgetRhsQuadratic(o, cons(o, ci)) + return from_bounds(S, lhs, rhs) end -function MOI.get(o::Optimizer, ::MOI.ConstraintPrimal, ci::CI{SQF, S}) where S <: BOUNDS +function MOI.get(o::Optimizer, ::MOI.ConstraintPrimal, ci::CI{SQF, S}) where {S <: BOUNDS} activity = Ref{Cdouble}() @SCIP_CALL SCIPgetActivityQuadratic(o, cons(o, ci), SCIPgetBestSol(o), activity) return activity[] diff --git a/src/MOI_wrapper/results.jl b/src/MOI_wrapper/results.jl index f685fea1..a973978f 100644 --- a/src/MOI_wrapper/results.jl +++ b/src/MOI_wrapper/results.jl @@ -24,7 +24,7 @@ function MOI.get(o::Optimizer, ::MOI.TerminationStatus) end function MOI.get(o::Optimizer, attr::MOI.PrimalStatus) - return if 1 <= attr.N <= MOI.get(o, MOI.ResultCount()) + return if 1 <= attr.result_index <= MOI.get(o, MOI.ResultCount()) MOI.FEASIBLE_POINT else MOI.NO_SOLUTION @@ -83,21 +83,21 @@ function MOI.get(o::Optimizer, attr::MOI.VariablePrimal, vi::VI) assert_solved(o) MOI.check_result_index_bounds(o, attr) sols = unsafe_wrap(Array{Ptr{SCIP_SOL}}, SCIPgetSols(o), SCIPgetNSols(o)) - return SCIPgetSolVal(o, sols[attr.N], var(o, vi)) + return SCIPgetSolVal(o, sols[attr.result_index], var(o, vi)) end -function MOI.get(o::Optimizer, attr::MOI.ConstraintPrimal, ci::CI{SVF,<:BOUNDS}) +function MOI.get(o::Optimizer, attr::MOI.ConstraintPrimal, ci::CI{VI,<:BOUNDS}) assert_solved(o) MOI.check_result_index_bounds(o, attr) sols = unsafe_wrap(Array{Ptr{SCIP_SOL}}, SCIPgetSols(o), SCIPgetNSols(o)) - return SCIPgetSolVal(o, sols[attr.N], var(o, VI(ci.value))) + return SCIPgetSolVal(o, sols[attr.result_index], var(o, VI(ci.value))) end -function MOI.get(o::Optimizer, attr::MOI.ConstraintPrimal, ci::CI{SAF,<:BOUNDS}) +function MOI.get(o::Optimizer, attr::MOI.ConstraintPrimal, ci::CI{<:SAF,<:BOUNDS}) assert_solved(o) MOI.check_result_index_bounds(o, attr) sols = unsafe_wrap(Array{Ptr{SCIP_SOL}}, SCIPgetSols(o), SCIPgetNSols(o)) - return SCIPgetActivityLinear(o, cons(o, ci), sols[attr.N]) + return SCIPgetActivityLinear(o, cons(o, ci), sols[attr.result_index]) end function MOI.get(o::Optimizer, ::MOI.ObjectiveBound) @@ -110,7 +110,7 @@ function MOI.get(o::Optimizer, ::MOI.RelativeGap) return SCIPgetGap(o) end -function MOI.get(o::Optimizer, ::MOI.SolveTime) +function MOI.get(o::Optimizer, ::MOI.SolveTimeSec) return SCIPgetSolvingTime(o) end diff --git a/src/MOI_wrapper/sepa.jl b/src/MOI_wrapper/sepa.jl index d7392538..91d594cd 100644 --- a/src/MOI_wrapper/sepa.jl +++ b/src/MOI_wrapper/sepa.jl @@ -78,8 +78,8 @@ end MOI.supports(::Optimizer, ::MOI.UserCutCallback) = true function MOI.submit(o::Optimizer, cb_data::MOI.UserCut{CutCbData}, - func::SAF, set::S) where S <: BOUNDS - varrefs = [VarRef(t.variable_index.value) for t in func.terms] + func::SAF, set::S) where {S <: BOUNDS} + varrefs = [VarRef(t.variable.value) for t in func.terms] coefs = [t.coefficient for t in func.terms] lhs, rhs = bounds(set) diff --git a/src/MOI_wrapper/soc_constraints.jl b/src/MOI_wrapper/soc_constraints.jl index 6357c338..f317ce8e 100644 --- a/src/MOI_wrapper/soc_constraints.jl +++ b/src/MOI_wrapper/soc_constraints.jl @@ -21,6 +21,7 @@ function MOI.add_constraint(o::Optimizer, func::VECTOR, set::SOC) end function MOI.delete(o::Optimizer, ci::CI{VECTOR, SOC}) + _throw_if_invalid(o, ci) allow_modification(o) delete!(o.constypes[VECTOR, SOC], ConsRef(ci.value)) delete!(o.reference, cons(o, ci)) @@ -29,6 +30,7 @@ function MOI.delete(o::Optimizer, ci::CI{VECTOR, SOC}) end function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{VECTOR, SOC}) + _throw_if_invalid(o, ci) c = cons(o, ci)::Ptr{SCIP_CONS} nvars::Int = SCIPgetNLhsVarsSOC(o, c) vars = unsafe_wrap(Array{Ptr{SCIP_VAR}}, SCIPgetLhsVarsSOC(o, c), nvars) @@ -39,6 +41,7 @@ function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{VECTOR, SOC}) end function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{VECTOR, SOC}) + _throw_if_invalid(o, ci) c = cons(o, ci)::Ptr{SCIP_CONS} nvars::Int = SCIPgetNLhsVarsSOC(o, c) return MOI.SecondOrderCone(nvars + 1) diff --git a/src/MOI_wrapper/sos_constraints.jl b/src/MOI_wrapper/sos_constraints.jl index 3c96583c..377f5e37 100644 --- a/src/MOI_wrapper/sos_constraints.jl +++ b/src/MOI_wrapper/sos_constraints.jl @@ -14,6 +14,7 @@ function MOI.add_constraint(o::Optimizer, func::VECTOR, set::SOS1) end function MOI.delete(o::Optimizer, ci::CI{VECTOR, SOS1}) + _throw_if_invalid(o, ci) allow_modification(o) delete!(o.constypes[VECTOR, SOS1], ConsRef(ci.value)) delete!(o.reference, cons(o, ci)) @@ -22,6 +23,7 @@ function MOI.delete(o::Optimizer, ci::CI{VECTOR, SOS1}) end function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{VECTOR, SOS1}) + _throw_if_invalid(o, ci) c = cons(o, ci) nvars::Int = SCIPgetNVarsSOS1(o, c) vars = unsafe_wrap(Array{Ptr{SCIP_VAR}}, SCIPgetVarsSOS1(o, c), nvars) @@ -29,6 +31,7 @@ function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{VECTOR, SOS1}) end function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{VECTOR, SOS1}) + _throw_if_invalid(o, ci) c = cons(o, ci) nvars::Int = SCIPgetNVarsSOS1(o, c) weights = unsafe_wrap(Array{Float64}, SCIPgetWeightsSOS1(o, c), nvars) @@ -51,6 +54,7 @@ function MOI.add_constraint(o::Optimizer, func::VECTOR, set::SOS2) end function MOI.delete(o::Optimizer, ci::CI{VECTOR, SOS2}) + _throw_if_invalid(o, ci) allow_modification(o) delete!(o.constypes[VECTOR, SOS2], ConsRef(ci.value)) delete!(o.reference, cons(o, ci)) @@ -59,6 +63,7 @@ function MOI.delete(o::Optimizer, ci::CI{VECTOR, SOS2}) end function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{VECTOR, SOS2}) + _throw_if_invalid(o, ci) c = cons(o, ci) nvars::Int = SCIPgetNVarsSOS2(o, c) vars = unsafe_wrap(Array{Ptr{SCIP_VAR}}, SCIPgetVarsSOS2(o, c), nvars) @@ -66,6 +71,7 @@ function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{VECTOR, SOS2}) end function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{VECTOR, SOS2}) + _throw_if_invalid(o, ci) c = cons(o, ci) nvars::Int = SCIPgetNVarsSOS2(o, c) weights = unsafe_wrap(Array{Float64}, SCIPgetWeightsSOS2(o, c), nvars) diff --git a/src/MOI_wrapper/variable.jl b/src/MOI_wrapper/variable.jl index a73e746e..92ed4be1 100644 --- a/src/MOI_wrapper/variable.jl +++ b/src/MOI_wrapper/variable.jl @@ -10,7 +10,7 @@ end MOI.add_variables(o::Optimizer, n) = [MOI.add_variable(o) for i=1:n] MOI.get(o::Optimizer, ::MOI.NumberOfVariables) = length(o.inner.vars) -MOI.get(o::Optimizer, ::MOI.ListOfVariableIndices) = [VI(k.val) for k in keys(o.inner.vars)] +MOI.get(o::Optimizer, ::MOI.ListOfVariableIndices) = sort!([VI(k.val) for k in keys(o.inner.vars)], by=v->v.value) MOI.is_valid(o::Optimizer, vi::VI) = haskey(o.inner.vars, VarRef(vi.value)) function MOI.get(o::Optimizer, ::MOI.VariableName, vi::VI)::String @@ -30,8 +30,14 @@ function MOI.delete(o::Optimizer, vi::VI) if length(o.inner.conss) > 0 error("Can not delete variable while model contains constraints!") end - allow_modification(o) + if !haskey(o.inner.vars, VarRef(vi.value)) + throw(MOI.InvalidIndex(vi)) + end + # TODO still necessary? + if !haskey(o.reference, var(o, vi)) + throw(MOI.InvalidIndex(vi)) + end haskey(o.binbounds, vi) && delete!(o.binbounds, vi) delete!(o.reference, var(o, vi)) delete(o.inner, VarRef(vi.value)) @@ -40,14 +46,13 @@ end ## variable types (binary, integer) -MOI.supports_constraint(o::Optimizer, ::Type{SVF}, ::Type{<:VAR_TYPES}) = true +MOI.supports_constraint(o::Optimizer, ::Type{VI}, ::Type{<:VAR_TYPES}) = true scip_vartype(::Type{MOI.ZeroOne}) = SCIP_VARTYPE_BINARY scip_vartype(::Type{MOI.Integer}) = SCIP_VARTYPE_INTEGER -function MOI.add_constraint(o::Optimizer, func::SVF, set::S) where {S <: VAR_TYPES} +function MOI.add_constraint(o::Optimizer, vi::VI, set::S) where {S <: VAR_TYPES} allow_modification(o) - vi = func.variable v = var(o, vi) infeasible = Ref{SCIP_Bool}() @SCIP_CALL SCIPchgVarType(o, v, scip_vartype(S), infeasible) @@ -74,11 +79,12 @@ function MOI.add_constraint(o::Optimizer, func::SVF, set::S) where {S <: VAR_TYP end end # use var index for cons index of this type - i = func.variable.value - return register!(o, CI{SVF, S}(i)) + i = vi.value + return register!(o, CI{VI, S}(i)) end -function MOI.delete(o::Optimizer, ci::CI{SVF,S}) where {S <: VAR_TYPES} +function MOI.delete(o::Optimizer, ci::CI{VI,S}) where {S <: VAR_TYPES} + _throw_if_invalid(o, ci) allow_modification(o) vi = VI(ci.value) @@ -92,26 +98,27 @@ function MOI.delete(o::Optimizer, ci::CI{SVF,S}) where {S <: VAR_TYPES} @SCIP_CALL SCIPchgVarType(o, var(o, vi), SCIP_VARTYPE_CONTINUOUS, infeasible) # TODO: warn if infeasible[] == TRUE? - # Can only change bounds after chaging the var type. + # Can only change bounds after changing the var type. if reset_bounds - bounds = o.binbounds[vi] - @SCIP_CALL SCIPchgVarLb(o, v, bounds.lower) - @SCIP_CALL SCIPchgVarUb(o, v, bounds.upper) + bounds = get(o.binbounds, vi, nothing) + if bounds !== nothing + @SCIP_CALL SCIPchgVarLb(o, v, bounds.lower) + @SCIP_CALL SCIPchgVarUb(o, v, bounds.upper) + end end # but do delete the constraint reference - delete!(o.constypes[SVF, S], ConsRef(ci.value)) + delete!(o.constypes[VI, S], ConsRef(ci.value)) return nothing end ## variable bounds -MOI.supports_constraint(o::Optimizer, ::Type{SVF}, ::Type{<:BOUNDS}) = true +MOI.supports_constraint(o::Optimizer, ::Type{VI}, ::Type{<:BOUNDS}) = true -function MOI.add_constraint(o::Optimizer, func::SVF, set::S) where S <: BOUNDS +function MOI.add_constraint(o::Optimizer, vi::VI, set::S) where {S <: BOUNDS} allow_modification(o) - vi = func.variable v = var(o, vi) inf = SCIPinfinity(o) @@ -141,7 +148,8 @@ function MOI.add_constraint(o::Optimizer, func::SVF, set::S) where S <: BOUNDS @debug "Overwriting existing bounds [0.0,1.0] with [$newlb,$newub] for binary variable at $(vi.value)!" end else # general case (non-binary variable) - error("Already have bounds [$oldlb,$oldub] for variable at $(vi.value)!") + # TODO find initially-set bound constraint + throw(MOI.LowerBoundAlreadySet{S,S}(vi)) end end @@ -154,25 +162,26 @@ function MOI.add_constraint(o::Optimizer, func::SVF, set::S) where S <: BOUNDS end # use var index for cons index of this type - i = func.variable.value - return register!(o, CI{SVF, S}(i)) + i = vi.value + return register!(o, CI{VI, S}(i)) end -function reset_bounds(o::Optimizer, v, lb, ub, ci::CI{SVF, S}) where S <: Union{MOI.Interval{Float64}, MOI.EqualTo{Float64}} +function reset_bounds(o::Optimizer, v, lb, ub, ci::CI{VI, S}) where S <: Union{MOI.Interval{Float64}, MOI.EqualTo{Float64}} @SCIP_CALL SCIPchgVarLb(o, v, lb) @SCIP_CALL SCIPchgVarUb(o, v, ub) end -function reset_bounds(o::Optimizer, v, lb, ub, ci::CI{SVF, MOI.GreaterThan{Float64}}) +function reset_bounds(o::Optimizer, v, lb, ub, ci::CI{VI, MOI.GreaterThan{Float64}}) @SCIP_CALL SCIPchgVarLb(o, v, lb) end -function reset_bounds(o::Optimizer, v, lb, ub, ci::CI{SVF, MOI.LessThan{Float64}}) +function reset_bounds(o::Optimizer, v, lb, ub, ci::CI{VI, MOI.LessThan{Float64}}) @SCIP_CALL SCIPchgVarUb(o, v, ub) end -function MOI.delete(o::Optimizer, ci::CI{SVF,S}) where S <: BOUNDS +function MOI.delete(o::Optimizer, ci::CI{VI,S}) where {S <: BOUNDS} + _throw_if_invalid(o, ci) allow_modification(o) # Don't actually delete any SCIP constraint, just reset bounds @@ -187,12 +196,12 @@ function MOI.delete(o::Optimizer, ci::CI{SVF,S}) where S <: BOUNDS # but do delete the constraint reference haskey(o.binbounds, vi) && delete!(o.binbounds, vi) - delete!(o.constypes[SVF, S], ConsRef(ci.value)) + delete!(o.constypes[VI, S], ConsRef(ci.value)) return nothing end -function MOI.set(o::SCIP.Optimizer, ::MOI.ConstraintSet, ci::CI{SVF,S}, set::S) where {S <: BOUNDS} +function MOI.set(o::SCIP.Optimizer, ::MOI.ConstraintSet, ci::CI{VI,S}, set::S) where {S <: BOUNDS} allow_modification(o) v = var(o, VI(ci.value)) # cons index is actually var index lb, ub = bounds(set) @@ -209,15 +218,28 @@ function MOI.set(o::SCIP.Optimizer, ::MOI.ConstraintSet, ci::CI{SVF,S}, set::S) end # TODO: is actually wrong for unbounded variables? -function MOI.is_valid(o::Optimizer, ci::CI{SVF,<:BOUNDS}) - return haskey(o.inner.vars, VarRef(ci.value)) +function MOI.is_valid(o::Optimizer, ci::CI{VI,S}) where {S <: BOUNDS} + if !haskey(o.inner.vars, VarRef(ci.value)) + return false + end + cons_set = get(o.constypes, (VI, S), nothing) + if cons_set === nothing + return false + end + return ConsRef(ci.value) in o.constypes[VI, S] end -function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{SVF, S}) where S <: BOUNDS - return SVF(VI(ci.value)) +function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{VI, S}) where {S <: BOUNDS} + if !MOI.is_valid(o, ci) + throw(MOI.InvalidIndex(ci)) + end + return VI(ci.value) end -function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{SVF, S}) where S <: BOUNDS +function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{VI, S}) where {S <: BOUNDS} + if !MOI.is_valid(o, ci) + throw(MOI.InvalidIndex(ci)) + end vi = VI(ci.value) v = var(o, vi) lb, ub = SCIPvarGetLbOriginal(v), SCIPvarGetUbOriginal(v) @@ -228,6 +250,49 @@ function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{SVF, S}) where S <: B return from_bounds(S, lb, ub) end +function MOI.is_valid(o::Optimizer, ci::CI{VI,S}) where {S <: Union{MOI.ZeroOne, MOI.Integer}} + if !haskey(o.inner.vars, VarRef(ci.value)) + return false + end + return ConsRef(ci.value) in o.constypes[VI, S] +end + +function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{VI, MOI.ZeroOne}) + vi = VI(ci.value) + v = var(o, vi) + if SCIPvarGetType(v) == SCIP_VARTYPE_BINARY + return MOI.ZeroOne() + end + throw(MOI.InvalidIndex(ci)) +end + +function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{VI, MOI.Integer}) + vi = VI(ci.value) + v = var(o, vi) + if SCIPvarGetType(v) == SCIP_VARTYPE_INTEGER + return MOI.Integer() + end + throw(MOI.InvalidIndex(ci)) +end + +function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{VI, MOI.ZeroOne}) + vi = VI(ci.value) + v = var(o, vi) + if SCIPvarGetType(v) == SCIP_VARTYPE_BINARY + return vi + end + throw(MOI.InvalidIndex(ci)) +end + +function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{VI, MOI.Integer}) + vi = VI(ci.value) + v = var(o, vi) + if SCIPvarGetType(v) == SCIP_VARTYPE_INTEGER + return vi + end + throw(MOI.InvalidIndex(ci)) +end + # (partial) warm starts MOI.supports(::Optimizer, ::MOI.VariablePrimalStart, ::Type{VI}) = true @@ -251,3 +316,7 @@ function MOI.set(o::Optimizer, ::MOI.VariablePrimalStart, vi::VI, value::Nothing end return end + +function MOI.set(::Optimizer, ::MOI.ConstraintFunction, ::CI{VI}, ::VI) + throw(MOI.SettingVariableIndexNotAllowed()) +end diff --git a/test/MOI_additional.jl b/test/MOI_additional.jl index cbe9135f..129fe460 100644 --- a/test/MOI_additional.jl +++ b/test/MOI_additional.jl @@ -3,204 +3,17 @@ const MOI = MathOptInterface const VI = MOI.VariableIndex const CI = MOI.ConstraintIndex -const SVF = MOI.SingleVariable function var_bounds(o::SCIP.Optimizer, vi::VI) - return MOI.get(o, MOI.ConstraintSet(), CI{SVF,MOI.Interval{Float64}}(vi.value)) + return MOI.get(o, MOI.ConstraintSet(), CI{VI,MOI.Interval{Float64}}(vi.value)) end function chg_bounds(o::SCIP.Optimizer, vi::VI, set::S) where S - ci = CI{SVF,S}(vi.value) + ci = CI{VI,S}(vi.value) MOI.set(o, MOI.ConstraintSet(), ci, set) return nothing end -@testset "Binary variables and explicit bounds" begin - optimizer = SCIP.Optimizer() - - # Binary variable without explicit bounds. - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - t = MOI.add_constraint(optimizer, x, MOI.ZeroOne()) - @test_throws KeyError var_bounds(optimizer, x) == MOI.Interval(0.0, 1.0) - - # Binary variable with [0, 1] bounds. - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - b = MOI.add_constraint(optimizer, x, MOI.Interval(0.0, 1.0)) - t = MOI.add_constraint(optimizer, x, MOI.ZeroOne()) - @test var_bounds(optimizer, x) == MOI.Interval(0.0, 1.0) - MOI.delete(optimizer, t) - @test var_bounds(optimizer, x) == MOI.Interval(0.0, 1.0) - - # Binary variable with [0, 1] bounds (order should not matter). - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - t = MOI.add_constraint(optimizer, x, MOI.ZeroOne()) - b = MOI.add_constraint(optimizer, x, MOI.Interval(0.0, 1.0)) - @test var_bounds(optimizer, x) == MOI.Interval(0.0, 1.0) - MOI.delete(optimizer, t) - @test var_bounds(optimizer, x) == MOI.Interval(0.0, 1.0) - - # Binary variable fixed to 0. - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - t = MOI.add_constraint(optimizer, x, MOI.ZeroOne()) - b = MOI.add_constraint(optimizer, x, MOI.EqualTo(0.0)) - @test var_bounds(optimizer, x) == MOI.Interval(0.0, 0.0) - MOI.delete(optimizer, t) - @test var_bounds(optimizer, x) == MOI.Interval(0.0, 0.0) - - # Binary variable fixed to 0 (different order). - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - b = MOI.add_constraint(optimizer, x, MOI.EqualTo(0.0)) - t = MOI.add_constraint(optimizer, x, MOI.ZeroOne()) - @test var_bounds(optimizer, x) == MOI.Interval(0.0, 0.0) - MOI.delete(optimizer, t) - @test var_bounds(optimizer, x) == MOI.Interval(0.0, 0.0) - - # Binary variable fixed to 1. - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - t = MOI.add_constraint(optimizer, x, MOI.ZeroOne()) - b = MOI.add_constraint(optimizer, x, MOI.EqualTo(1.0)) - @test var_bounds(optimizer, x) == MOI.Interval(1.0, 1.0) - MOI.delete(optimizer, t) - @test var_bounds(optimizer, x) == MOI.Interval(1.0, 1.0) - - # Binary variable fixed to 1 (different order). - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - b = MOI.add_constraint(optimizer, x, MOI.EqualTo(1.0)) - t = MOI.add_constraint(optimizer, x, MOI.ZeroOne()) - @test var_bounds(optimizer, x) == MOI.Interval(1.0, 1.0) - MOI.delete(optimizer, t) - @test var_bounds(optimizer, x) == MOI.Interval(1.0, 1.0) - - # Tightened bounds for binary variable - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - b = MOI.add_constraint(optimizer, x, MOI.Interval(-1.0, 2.0)) - t = MOI.add_constraint(optimizer, x, MOI.ZeroOne()) - @test var_bounds(optimizer, x) == MOI.Interval(-1.0, 2.0) - MOI.delete(optimizer, t) - @test var_bounds(optimizer, x) == MOI.Interval(-1.0, 2.0) - - # Tightened bounds for binary variable (different order). - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - t = MOI.add_constraint(optimizer, x, MOI.ZeroOne()) - b = MOI.add_constraint(optimizer, x, MOI.Interval(-1.0, 2.0)) - @test var_bounds(optimizer, x) == MOI.Interval(-1.0, 2.0) - MOI.delete(optimizer, t) - @test var_bounds(optimizer, x) == MOI.Interval(-1.0, 2.0) - - # No error: binary variable with conflicting bounds (infeasible). - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - b = MOI.add_constraint(optimizer, x, MOI.Interval(2.0, 3.0)) - t = MOI.add_constraint(optimizer, x, MOI.ZeroOne()) - @test var_bounds(optimizer, x) == MOI.Interval(2.0, 3.0) - - # No error: binary variable with conflicting bounds (infeasible, different order). - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - t = MOI.add_constraint(optimizer, x, MOI.ZeroOne()) - b = MOI.add_constraint(optimizer, x, MOI.Interval(2.0, 3.0)) - @test var_bounds(optimizer, x) == MOI.Interval(2.0, 3.0) -end - -@testset "Bound constraints for a general variable." begin - optimizer = SCIP.Optimizer() - inf = SCIP.SCIPinfinity(optimizer) - - # Should work: variable without explicit bounds - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - @test var_bounds(optimizer, x) == MOI.Interval(-inf, inf) - - # Should work: variable with range bounds, but only once! - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - b = MOI.add_constraint(optimizer, x, MOI.Interval(2.0, 3.0)) - @test var_bounds(optimizer, x) == MOI.Interval(2.0, 3.0) - @test_throws ErrorException MOI.add_constraint(optimizer, x, MOI.Interval(3.0, 4.0)) - - # Should work: variable with lower bound, but only once! - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - b = MOI.add_constraint(optimizer, x, MOI.GreaterThan(2.0)) - @test var_bounds(optimizer, x) == MOI.Interval(2.0, inf) - @test_throws ErrorException MOI.add_constraint(optimizer, x, MOI.GreaterThan(3.0)) - - # Should work: variable with lower bound, but only once! - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - b = MOI.add_constraint(optimizer, x, MOI.LessThan(2.0)) - @test var_bounds(optimizer, x) == MOI.Interval(-inf, 2.0) - @test_throws ErrorException MOI.add_constraint(optimizer, x, MOI.LessThan(3.0)) - - # Should work: fixed variable, but only once! - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - b = MOI.add_constraint(optimizer, x, MOI.EqualTo(2.0)) - @test var_bounds(optimizer, x) == MOI.Interval(2.0, 2.0) - @test_throws ErrorException MOI.add_constraint(optimizer, x, MOI.EqualTo(3.0)) - - # Mixed constraint types now allowed (when disjoint)! - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - lb = MOI.add_constraint(optimizer, x, MOI.GreaterThan(2.0)) - ub = MOI.add_constraint(optimizer, x, MOI.LessThan(3.0)) - @test var_bounds(optimizer, x) == MOI.Interval(2.0, 3.0) -end - -@testset "Changing bounds for variable." begin - optimizer = SCIP.Optimizer() - inf = SCIP.SCIPinfinity(optimizer) - - # change interval bounds - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - b = MOI.add_constraint(optimizer, x, MOI.Interval(2.0, 3.0)) - @test var_bounds(optimizer, x) == MOI.Interval(2.0, 3.0) - chg_bounds(optimizer, x, MOI.Interval(4.0, 5.0)) - @test var_bounds(optimizer, x) == MOI.Interval(4.0, 5.0) - - # change lower bound - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - b = MOI.add_constraint(optimizer, x, MOI.GreaterThan(2.0)) - @test var_bounds(optimizer, x) == MOI.Interval(2.0, inf) - chg_bounds(optimizer, x, MOI.GreaterThan(4.0)) - @test var_bounds(optimizer, x) == MOI.Interval(4.0, inf) - - # change upper bound - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - b = MOI.add_constraint(optimizer, x, MOI.LessThan(3.0)) - @test var_bounds(optimizer, x) == MOI.Interval(-inf, 3.0) - chg_bounds(optimizer, x, MOI.LessThan(5.0)) - @test var_bounds(optimizer, x) == MOI.Interval(-inf, 5.0) - - # change fixed value - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - b = MOI.add_constraint(optimizer, x, MOI.EqualTo(2.5)) - @test var_bounds(optimizer, x) == MOI.Interval(2.5, 2.5) - chg_bounds(optimizer, x, MOI.EqualTo(4.5)) - @test var_bounds(optimizer, x) == MOI.Interval(4.5, 4.5) - - # change mixed - MOI.empty!(optimizer) - x = MOI.add_variable(optimizer) - b = MOI.add_constraint(optimizer, x, MOI.GreaterThan(2.0)) - @test var_bounds(optimizer, x) == MOI.Interval(2.0, inf) - chg_bounds(optimizer, x, MOI.LessThan(4.0)) - @test var_bounds(optimizer, x) == MOI.Interval(-inf, 4.0) -end - @testset "Second Order Cone Constraint" begin # Derived from MOI's problem SOC1 # max 0x + 1y + 1z @@ -217,7 +30,7 @@ end MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0,1.0], [y,z]), 0.0)) MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MAX_SENSE) - ceq = MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.EqualTo(1.0)) + ceq = MOI.add_constraint(optimizer, x, MOI.EqualTo(1.0)) csoc = MOI.add_constraint(optimizer, MOI.VectorOfVariables([x, y, z]), MOI.SecondOrderCone(3)) @@ -246,8 +59,8 @@ end x, y = MOI.add_variables(optimizer, 2) - MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.Interval(0.0, 1.0)) - MOI.add_constraint(optimizer, MOI.SingleVariable(y), MOI.GreaterThan(2.0)) + MOI.add_constraint(optimizer, x, MOI.Interval(0.0, 1.0)) + MOI.add_constraint(optimizer, y, MOI.GreaterThan(2.0)) MOI.add_constraint(optimizer, MOI.VectorOfVariables([x, y]), MOI.SecondOrderCone(2)) MOI.optimize!(optimizer) @@ -261,7 +74,7 @@ end @test_throws ErrorException MOI.add_constraint( optimizer, MOI.VectorOfVariables([x, y]), MOI.SecondOrderCone(2)) - MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.GreaterThan(0.0)) + MOI.add_constraint(optimizer, x, MOI.GreaterThan(0.0)) MOI.add_constraint(optimizer, MOI.VectorOfVariables([x, y]), MOI.SecondOrderCone(2)) # no error @@ -271,9 +84,9 @@ end optimizer = SCIP.Optimizer(display_verblevel=0) x, y, z = MOI.add_variables(optimizer, 3) - MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.LessThan(1.0)) - MOI.add_constraint(optimizer, MOI.SingleVariable(y), MOI.LessThan(1.0)) - MOI.add_constraint(optimizer, MOI.SingleVariable(z), MOI.LessThan(1.0)) + MOI.add_constraint(optimizer, x, MOI.LessThan(1.0)) + MOI.add_constraint(optimizer, y, MOI.LessThan(1.0)) + MOI.add_constraint(optimizer, z, MOI.LessThan(1.0)) c = MOI.add_constraint(optimizer, MOI.VectorOfVariables([x,y,z]), MOI.SOS1([1.0,2.0,3.0])) @@ -299,9 +112,9 @@ end optimizer = SCIP.Optimizer(display_verblevel=0) x, y, z = MOI.add_variables(optimizer, 3) - MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.LessThan(1.0)) - MOI.add_constraint(optimizer, MOI.SingleVariable(y), MOI.LessThan(1.0)) - MOI.add_constraint(optimizer, MOI.SingleVariable(z), MOI.LessThan(1.0)) + MOI.add_constraint(optimizer, x, MOI.LessThan(1.0)) + MOI.add_constraint(optimizer, y, MOI.LessThan(1.0)) + MOI.add_constraint(optimizer, z, MOI.LessThan(1.0)) c = MOI.add_constraint(optimizer, MOI.VectorOfVariables([x,y,z]), MOI.SOS2([1.0,2.0,3.0])) @@ -332,8 +145,8 @@ end optimizer = SCIP.Optimizer(display_verblevel=0) x1, x2, z1, z2 = MOI.add_variables(optimizer, 4) - MOI.add_constraint(optimizer, MOI.SingleVariable(z1), MOI.LessThan(4.0)) - MOI.add_constraint(optimizer, MOI.SingleVariable(z2), MOI.GreaterThan(-8.0)) + MOI.add_constraint(optimizer, z1, MOI.LessThan(4.0)) + MOI.add_constraint(optimizer, z2, MOI.GreaterThan(-8.0)) c1 = MOI.add_constraint(optimizer, MOI.VectorOfVariables([x1, z1]), SCIP.AbsolutePowerSet(2.0)) @@ -369,7 +182,7 @@ end x3 = MOI.add_variable(optimizer) y = MOI.add_variable(optimizer) t = MOI.add_constraint(optimizer, y, MOI.ZeroOne()) - iset = MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(1.0)) + iset = MOI.Indicator{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(1.0)) ind_func = MOI.VectorAffineFunction( [MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, y)), MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x1)), @@ -428,10 +241,10 @@ end # Happy Path: add objective and retrieve it. x = MOI.add_variable(optimizer) - obj = MOI.SingleVariable(x) - MOI.set(optimizer, MOI.ObjectiveFunction{MOI.SingleVariable}(), obj) + obj = x + MOI.set(optimizer, MOI.ObjectiveFunction{MOI.VariableIndex}(), obj) MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MAX_SENSE) - @test MOI.get(optimizer, MOI.ObjectiveFunction{MOI.SingleVariable}()) == obj + @test MOI.get(optimizer, MOI.ObjectiveFunction{MOI.VariableIndex}()) == obj @test MOI.get(optimizer, MOI.ObjectiveSense()) == MOI.MAX_SENSE @test MOI.get(optimizer, MOI.ObjectiveFunctionType()) == MOI.ScalarAffineFunction{Float64} @@ -443,33 +256,33 @@ end aff_obj = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 3.0) MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), aff_obj) MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MAX_SENSE) - @test_throws ErrorException MOI.get(optimizer, MOI.ObjectiveFunction{MOI.SingleVariable}()) + @test_throws InexactError MOI.get(optimizer, MOI.ObjectiveFunction{MOI.VariableIndex}()) end @testset "set_parameter" begin # bool optimizer = SCIP.Optimizer(branching_preferbinary=true) - @test MOI.get(optimizer, MOI.RawParameter("branching/preferbinary")) == true + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("branching/preferbinary")) == true # int optimizer = SCIP.Optimizer(conflict_minmaxvars=1) - @test MOI.get(optimizer, MOI.RawParameter("conflict/minmaxvars")) == 1 + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("conflict/minmaxvars")) == 1 # long int optimizer = SCIP.Optimizer(heuristics_alns_maxnodes=2) - @test MOI.get(optimizer, MOI.RawParameter("heuristics/alns/maxnodes")) == 2 + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("heuristics/alns/maxnodes")) == 2 # real optimizer = SCIP.Optimizer(branching_scorefac=0.15) - @test MOI.get(optimizer, MOI.RawParameter("branching/scorefac")) == 0.15 + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("branching/scorefac")) == 0.15 # char optimizer = SCIP.Optimizer(branching_scorefunc='s') - @test MOI.get(optimizer, MOI.RawParameter("branching/scorefunc")) == 's' + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("branching/scorefunc")) == 's' # string optimizer = SCIP.Optimizer(heuristics_alns_rewardfilename="abc.txt") - @test MOI.get(optimizer, MOI.RawParameter("heuristics/alns/rewardfilename")) == "abc.txt" + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("heuristics/alns/rewardfilename")) == "abc.txt" # invalid @test_throws ErrorException SCIP.Optimizer(some_invalid_param_name=true) @@ -479,31 +292,31 @@ end optimizer = SCIP.Optimizer() # bool - MOI.set(optimizer, MOI.RawParameter("branching/preferbinary"), true) - @test MOI.get(optimizer, MOI.RawParameter("branching/preferbinary")) == true + MOI.set(optimizer, MOI.RawOptimizerAttribute("branching/preferbinary"), true) + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("branching/preferbinary")) == true # int - MOI.set(optimizer, MOI.RawParameter("conflict/minmaxvars"), 1) - @test MOI.get(optimizer, MOI.RawParameter("conflict/minmaxvars")) == 1 + MOI.set(optimizer, MOI.RawOptimizerAttribute("conflict/minmaxvars"), 1) + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("conflict/minmaxvars")) == 1 # long int - MOI.set(optimizer, MOI.RawParameter("heuristics/alns/maxnodes"), 2) - @test MOI.get(optimizer, MOI.RawParameter("heuristics/alns/maxnodes")) == 2 + MOI.set(optimizer, MOI.RawOptimizerAttribute("heuristics/alns/maxnodes"), 2) + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("heuristics/alns/maxnodes")) == 2 # real - MOI.set(optimizer, MOI.RawParameter("branching/scorefac"), 0.15) - @test MOI.get(optimizer, MOI.RawParameter("branching/scorefac")) == 0.15 + MOI.set(optimizer, MOI.RawOptimizerAttribute("branching/scorefac"), 0.15) + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("branching/scorefac")) == 0.15 # char - MOI.set(optimizer, MOI.RawParameter("branching/scorefunc"), 's') - @test MOI.get(optimizer, MOI.RawParameter("branching/scorefunc")) == 's' + MOI.set(optimizer, MOI.RawOptimizerAttribute("branching/scorefunc"), 's') + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("branching/scorefunc")) == 's' # string - MOI.set(optimizer, MOI.RawParameter("heuristics/alns/rewardfilename"), "abc.txt") - @test MOI.get(optimizer, MOI.RawParameter("heuristics/alns/rewardfilename")) == "abc.txt" + MOI.set(optimizer, MOI.RawOptimizerAttribute("heuristics/alns/rewardfilename"), "abc.txt") + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("heuristics/alns/rewardfilename")) == "abc.txt" # invalid - @test_throws ErrorException MOI.set(optimizer, MOI.RawParameter("some/invalid/param/name"), true) + @test_throws ErrorException MOI.set(optimizer, MOI.RawOptimizerAttribute("some/invalid/param/name"), true) end @testset "Silent" begin @@ -513,17 +326,17 @@ end # "loud" by default @test MOI.get(optimizer, MOI.Silent()) == false - @test MOI.get(optimizer, MOI.RawParameter("display/verblevel")) == 4 + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("display/verblevel")) == 4 # make it silent MOI.set(optimizer, MOI.Silent(), true) @test MOI.get(optimizer, MOI.Silent()) == true - @test MOI.get(optimizer, MOI.RawParameter("display/verblevel")) == 0 + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("display/verblevel")) == 0 # but a user can override it - MOI.set(optimizer, MOI.RawParameter("display/verblevel"), 1) + MOI.set(optimizer, MOI.RawOptimizerAttribute("display/verblevel"), 1) @test MOI.get(optimizer, MOI.Silent()) == false - @test MOI.get(optimizer, MOI.RawParameter("display/verblevel")) == 1 + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("display/verblevel")) == 1 end @testset "Query results (before/after solve)" begin @@ -531,8 +344,8 @@ end atol, rtol = 1e-6, 1e-6 x, y = MOI.add_variables(optimizer, 2) - MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.GreaterThan(0.0)) - MOI.add_constraint(optimizer, MOI.SingleVariable(y), MOI.GreaterThan(0.0)) + MOI.add_constraint(optimizer, x, MOI.GreaterThan(0.0)) + MOI.add_constraint(optimizer, y, MOI.GreaterThan(0.0)) c = MOI.add_constraint( optimizer, @@ -552,7 +365,7 @@ end @test_throws ErrorException MOI.get(optimizer, MOI.ConstraintPrimal(), c) @test_throws ErrorException MOI.get(optimizer, MOI.ObjectiveBound()) @test_throws ErrorException MOI.get(optimizer, MOI.RelativeGap()) - @test MOI.get(optimizer, MOI.SolveTime()) ≈ 0.0 atol=atol rtol=rtol + @test MOI.get(optimizer, MOI.SolveTimeSec()) ≈ 0.0 atol=atol rtol=rtol @test_throws ErrorException MOI.get(optimizer, MOI.SimplexIterations()) @test MOI.get(optimizer, MOI.NodeCount()) == 0 @@ -567,7 +380,7 @@ end @test MOI.get(optimizer, MOI.ConstraintPrimal(), c) ≈ 1.0 atol=atol rtol=rtol @test MOI.get(optimizer, MOI.ObjectiveBound()) ≈ 2.0 atol=atol rtol=rtol @test MOI.get(optimizer, MOI.RelativeGap()) ≈ 0.0 atol=atol rtol=rtol - @test MOI.get(optimizer, MOI.SolveTime()) < 1.0 + @test MOI.get(optimizer, MOI.SolveTimeSec()) < 1.0 @test MOI.get(optimizer, MOI.SimplexIterations()) <= 3 # conservative @test MOI.get(optimizer, MOI.NodeCount()) <= 1 # conservative end diff --git a/test/MOI_conshdlr.jl b/test/MOI_conshdlr.jl index 2b1c78f5..1cc93a32 100644 --- a/test/MOI_conshdlr.jl +++ b/test/MOI_conshdlr.jl @@ -7,8 +7,8 @@ const MOI = MathOptInterface # add two binary variables: x, y x, y = MOI.add_variables(optimizer, 2) - MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.ZeroOne()) - MOI.add_constraint(optimizer, MOI.SingleVariable(y), MOI.ZeroOne()) + MOI.add_constraint(optimizer, x, MOI.ZeroOne()) + MOI.add_constraint(optimizer, y, MOI.ZeroOne()) # maximize 2x + y MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), @@ -38,9 +38,9 @@ end # add three binary variables x, y, z = MOI.add_variables(optimizer, 3) - MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.ZeroOne()) - MOI.add_constraint(optimizer, MOI.SingleVariable(y), MOI.ZeroOne()) - MOI.add_constraint(optimizer, MOI.SingleVariable(z), MOI.ZeroOne()) + MOI.add_constraint(optimizer, x, MOI.ZeroOne()) + MOI.add_constraint(optimizer, y, MOI.ZeroOne()) + MOI.add_constraint(optimizer, z, MOI.ZeroOne()) # maximize 2x + 3y + 2z MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), @@ -72,9 +72,9 @@ end # add three binary variables x, y, z = MOI.add_variables(optimizer, 3) - MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.ZeroOne()) - MOI.add_constraint(optimizer, MOI.SingleVariable(y), MOI.ZeroOne()) - MOI.add_constraint(optimizer, MOI.SingleVariable(z), MOI.ZeroOne()) + MOI.add_constraint(optimizer, x, MOI.ZeroOne()) + MOI.add_constraint(optimizer, y, MOI.ZeroOne()) + MOI.add_constraint(optimizer, z, MOI.ZeroOne()) # maximize 2x + 3y + 2z MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), @@ -104,9 +104,9 @@ end # add three binary variables x, y, z = MOI.add_variables(optimizer, 3) - MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.ZeroOne()) - MOI.add_constraint(optimizer, MOI.SingleVariable(y), MOI.ZeroOne()) - MOI.add_constraint(optimizer, MOI.SingleVariable(z), MOI.ZeroOne()) + MOI.add_constraint(optimizer, x, MOI.ZeroOne()) + MOI.add_constraint(optimizer, y, MOI.ZeroOne()) + MOI.add_constraint(optimizer, z, MOI.ZeroOne()) # maximize 2x + 3y + 2z MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), @@ -135,8 +135,8 @@ end # add three integer variables, in {0, 1, 2} x, y, z = MOI.add_variables(optimizer, 3) for v in [x, y, z] - MOI.add_constraint(optimizer, MOI.SingleVariable(v), MOI.Integer()) - MOI.add_constraint(optimizer, MOI.SingleVariable(v), MOI.Interval(0.0, 2.0)) + MOI.add_constraint(optimizer, v, MOI.Integer()) + MOI.add_constraint(optimizer, v, MOI.Interval(0.0, 2.0)) end # maximize 2x + y @@ -166,9 +166,9 @@ end optimizer = SCIP.Optimizer(display_verblevel=0, presolving_maxrounds=0) allow_dual_reductions = if SCIP.SCIPmajorVersion() < 7 - MOI.RawParameter("misc/allowdualreds") + MOI.RawOptimizerAttribute("misc/allowdualreds") else - MOI.RawParameter("misc/allowstrongdualreds") + MOI.RawOptimizerAttribute("misc/allowstrongdualreds") end MOI.set(optimizer, allow_dual_reductions, SCIP.FALSE) @@ -176,8 +176,8 @@ end # add binary variables x, y = MOI.add_variables(optimizer, 2) - MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.ZeroOne()) - MOI.add_constraint(optimizer, MOI.SingleVariable(y), MOI.ZeroOne()) + MOI.add_constraint(optimizer, x, MOI.ZeroOne()) + MOI.add_constraint(optimizer, y, MOI.ZeroOne()) # maximize 2x + y MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), diff --git a/test/MOI_nonlinear_exprs.jl b/test/MOI_nonlinear_exprs.jl index b8607543..ba27ef93 100644 --- a/test/MOI_nonlinear_exprs.jl +++ b/test/MOI_nonlinear_exprs.jl @@ -118,7 +118,7 @@ end @testset "add nonlinear constraint after solve" begin optimizer = SCIP.Optimizer() - MOI.set(optimizer, MOI.RawParameter("display/verblevel"), 0) + MOI.set(optimizer, MOI.RawOptimizerAttribute("display/verblevel"), 0) x, y = MOI.add_variables(optimizer, 2) diff --git a/test/MOI_wrapper_bridged.jl b/test/MOI_wrapper_bridged.jl index ee653a8b..e441bfb8 100644 --- a/test/MOI_wrapper_bridged.jl +++ b/test/MOI_wrapper_bridged.jl @@ -4,60 +4,21 @@ const MOIB = MOI.Bridges const MOIT = MOI.Test const BRIDGED = MOIB.full_bridge_optimizer(SCIP.Optimizer(display_verblevel=0), Float64) -const CONFIG = MOIT.TestConfig(atol=1e-5, rtol=1e-5, duals=false, infeas_certificates=false) -const CONFIG3 = MOIT.TestConfig(atol=1e-3, rtol=1e-2, duals=false, infeas_certificates=false) - -@testset "MOI Continuous Linear" begin - excluded = [ - "linear1", # needs MOI.delete (of variables in constraints) - "linear5", # needs MOI.delete (of variables in constraints) - "linear11", # broken in SCIP (#2556) - "linear13", # TODO: support MOI.FEASIBILITY_SENSE - "linear14", # needs MOI.delete (of variables in constraints) - ] - MOIT.contlineartest(BRIDGED, CONFIG, excluded) -end - -@testset "MOI Continuous Conic" begin - MOIT.lintest(BRIDGED, CONFIG) - - # SOC tests fail because of lower bound requirement of RHS var. - # MOIT.soctest(BRIDGED, CONFIG) - # MOIT.rsoctest(BRIDGED, CONFIG) - - # other cones not supported - # MOIT.geomeantest(BRIDGED, CONFIG) - # MOIT.exptest(BRIDGED, CONFIG) - # MOIT.sdptest(BRIDGED, CONFIG) - # MOIT.logdettest(BRIDGED, CONFIG) - # MOIT.rootdettest(BRIDGED, CONFIG) -end - -@testset "MOI Quadratic Constraint" begin - # needs objective bridge (MOI/#529) - # MOIT.qptest(BRIDGED, CONFIG) - - MOIT.qcptest(BRIDGED, CONFIG) - MOIT.socptest(BRIDGED, CONFIG3) -end - -@testset "MOI Integer Linear" begin - excluded = String["semiconttest", "semiinttest"] - MOIT.intlineartest(BRIDGED, CONFIG, excluded) - MOIT.indicator3_test(BRIDGED, CONFIG) -end - -@testset "MOI Integer Conic" begin - # SOC tests fail because of lower bound requirement of RHS var. - # MOIT.intconictest(BRIDGED, CONFIG) -end - -@testset "MOI NLP" begin - # None of tests provide expression graphs in the evaluator. - # MOIT.nlptest(BRIDGED, CONFIG) -end - -@testset "MOI Unit tests" begin - # TODO: most tests need get-variable-by-name etc. - # MOIT.unittest(BRIDGED, CONFIG) +const CONFIG_BRIDGED = MOIT.Config(atol=1e-5, rtol=1e-5, exclude=Any[ + MOI.ConstraintDual, MOI.ConstraintName, MOI.VariableName, MOI.DualObjectiveValue, MOI.VariableBasisStatus, MOI.ConstraintBasisStatus, +]) + +@testset "MOI unit tests" begin + excluded = copy(MOI_BASE_EXCLUDED) + append!(excluded, [ + "test_linear_integration", # Can not delete variable while model contains constraints + "test_quadratic_duplicate_terms", # Can not delete variable while model contains constraints + "test_quadratic_nonhomogeneous", # unsupported by bridge + ]) + MOIT.runtests( + BRIDGED, + CONFIG_BRIDGED, + warn_unsupported=false, + exclude = excluded, + ) end diff --git a/test/MOI_wrapper_cached.jl b/test/MOI_wrapper_cached.jl index a0c148ea..195d8469 100644 --- a/test/MOI_wrapper_cached.jl +++ b/test/MOI_wrapper_cached.jl @@ -17,19 +17,25 @@ const CACHE = MOIU.UniversalFallback(ModelData{Float64}()) const CACHED = MOIU.CachingOptimizer(CACHE, SCIP.Optimizer(display_verblevel=0)) const BRIDGED2 = MOIB.full_bridge_optimizer(CACHED, Float64) -const CONFIG = MOIT.TestConfig(atol=1e-5, rtol=1e-5, duals=false, infeas_certificates=false) -const CONFIG3 = MOIT.TestConfig(atol=1e-3, rtol=1e-2, duals=false, infeas_certificates=false) - -@testset "MOI Unit tests" begin - excluded = [ - "feasibility_sense", # TODO: support feasibility sense - "solve_qp_edge_cases", # needs objective bridge - "number_threads", # can't set num. threads in single-threaded SCIP! - "delete_soc_variables", # SCIP requires non-negative "rhs variable" - ] - MOIT.unittest(BRIDGED2, CONFIG, excluded) -end +const CONFIG_CACHED = MOIT.Config( + atol=1e-5, rtol=1e-5, + exclude=Any[ + MOI.ConstraintDual, MOI.ConstraintName, MOI.VariableName, MOI.DualObjectiveValue, MOI.VariableBasisStatus, MOI.ConstraintBasisStatus, + ], +) @testset "MOI Modification tests" begin - MOIT.modificationtest(BRIDGED2, CONFIG) + exclude_list = copy(MOI_BASE_EXCLUDED) + append!( + exclude_list, + [ + "RawStatusString", + "SolveTimeSec", + "test_conic_", + "test_linear_integration", + "test_quadratic_duplicate_terms", + "test_quadratic_nonhomogeneous", + ] + ) + MOIT.runtests(BRIDGED2, CONFIG_CACHED, exclude = exclude_list) end diff --git a/test/MOI_wrapper_direct.jl b/test/MOI_wrapper_direct.jl index 2d74bd0c..371cf620 100644 --- a/test/MOI_wrapper_direct.jl +++ b/test/MOI_wrapper_direct.jl @@ -3,51 +3,25 @@ const MOI = MathOptInterface const MOIT = MOI.Test const OPTIMIZER = SCIP.Optimizer(display_verblevel=0) -const CONFIG = MOIT.TestConfig(atol=1e-5, rtol=1e-5, duals=false, - infeas_certificates=false) - -@testset "MOI Continuous Linear" begin - # remember reason for excluded tests: - excluded = [ - "linear1", # needs MOI.delete (of variables in constraints) - "linear5", # needs MOI.delete (of variables in constraints) - "linear7", # needs MOI.VectorAffineFunction - "linear11", # broken in SCIP (#2556) - "linear13", # TODO: support MOI.FEASIBILITY_SENSE - "linear14", # needs MOI.delete (of variables in constraints) - "linear15", # needs MOI.VectorAffineFunction - ] - # MOIT.contlineartest(OPTIMIZER, CONFIG, excluded) - - # call individual tests - MOIT.linear2test(OPTIMIZER, CONFIG) - MOIT.linear3test(OPTIMIZER, CONFIG) - MOIT.linear4test(OPTIMIZER, CONFIG) - MOIT.linear6test(OPTIMIZER, CONFIG) - MOIT.linear8atest(OPTIMIZER, CONFIG) - MOIT.linear8btest(OPTIMIZER, CONFIG) - MOIT.linear8ctest(OPTIMIZER, CONFIG) - MOIT.linear9test(OPTIMIZER, CONFIG) - MOIT.linear10test(OPTIMIZER, CONFIG) - MOIT.linear10btest(OPTIMIZER, CONFIG) - MOIT.linear12test(OPTIMIZER, CONFIG) - MOIT.partial_start_test(OPTIMIZER, CONFIG) -end - -@testset "MOI Quadratic Constraint" begin - # remember reason for excluded tests: - excluded = [ - "qcp1", # needs VectorAffineFunction - ] - # MOIT.qcptest(OPTIMIZER, CONFIG, excluded) - - # call individual tests - MOIT.qcp2test(OPTIMIZER, CONFIG) - MOIT.qcp3test(OPTIMIZER, CONFIG) - MOIT.qcp4test(OPTIMIZER, CONFIG) +const CONFIG_DIRECT = MOIT.Config( + atol=1e-5, rtol=1e-5, + exclude=Any[ + MOI.ConstraintDual, MOI.ConstraintName, MOI.VariableName, MOI.DualObjectiveValue, MOI.VariableBasisStatus, MOI.ConstraintBasisStatus, + ], +) + +@testset "MOI unit tests" begin + excluded = copy(MOI_BASE_EXCLUDED) + append!(excluded, [ + "test_linear_integration", # Can not delete variable while model contains constraints + ]) + MOIT.runtests( + OPTIMIZER, + CONFIG_DIRECT, + warn_unsupported=false, + exclude = excluded, + ) - MOIT.ncqcp1test(OPTIMIZER, CONFIG) - MOIT.ncqcp2test(OPTIMIZER, CONFIG) end function indicator4_test(model::MOI.ModelLike, config) @@ -67,10 +41,10 @@ function indicator4_test(model::MOI.ModelLike, config) @test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()) @test MOI.supports(model, MOI.ObjectiveSense()) - @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.ZeroOne) - @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.Interval{Float64}) + @test MOI.supports_constraint(model, MOI.VariableIndex, MOI.ZeroOne) + @test MOI.supports_constraint(model, MOI.VariableIndex, MOI.Interval{Float64}) @test MOI.supports_constraint(model, MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) - @test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE, MOI.LessThan{Float64}}) + @test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.Indicator{MOI.ACTIVATE_ON_ONE, MOI.LessThan{Float64}}) x1 = MOI.add_variable(model) x2 = MOI.add_variable(model) z1 = MOI.add_variable(model) @@ -83,7 +57,7 @@ function indicator4_test(model::MOI.ModelLike, config) ], [0.0, -1.0] ) - iset1 = MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(7.0)) + iset1 = MOI.Indicator{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(7.0)) MOI.add_constraint(model, f1, iset1) f2 = MOI.VectorAffineFunction( @@ -93,7 +67,7 @@ function indicator4_test(model::MOI.ModelLike, config) ], [0.0, 1.0], ) - iset2 = MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(10.0)) + iset2 = MOI.Indicator{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(10.0)) MOI.add_constraint(model, f2, iset2) @@ -114,32 +88,20 @@ function indicator4_test(model::MOI.ModelLike, config) ) MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - if config.solve - @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED + @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED - MOI.optimize!(model) + MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL - @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT - @test MOI.get(model, MOI.ObjectiveValue()) ≈ 28.75 atol=atol rtol=rtol - @test MOI.get(model, MOI.VariablePrimal(), x1) ≈ 1.25 atol=atol rtol=rtol - @test MOI.get(model, MOI.VariablePrimal(), x2) ≈ 8.75 atol=atol rtol=rtol - @test MOI.get(model, MOI.VariablePrimal(), z1) ≈ 0.0 atol=atol rtol=rtol - @test MOI.get(model, MOI.VariablePrimal(), z2) ≈ 1.0 atol=atol rtol=rtol - end + @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL + @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT + @test MOI.get(model, MOI.ObjectiveValue()) ≈ 28.75 atol=atol rtol=rtol + @test MOI.get(model, MOI.VariablePrimal(), x1) ≈ 1.25 atol=atol rtol=rtol + @test MOI.get(model, MOI.VariablePrimal(), x2) ≈ 8.75 atol=atol rtol=rtol + @test MOI.get(model, MOI.VariablePrimal(), z1) ≈ 0.0 atol=atol rtol=rtol + @test MOI.get(model, MOI.VariablePrimal(), z2) ≈ 1.0 atol=atol rtol=rtol end @testset "MOI Integer Linear" begin - # MOIT.intlineartest(OPTIMIZER, CONFIG) - - # call individual tests - MOIT.knapsacktest(OPTIMIZER, CONFIG) - MOIT.int1test(OPTIMIZER, CONFIG) - MOIT.int2test(OPTIMIZER, CONFIG) - MOIT.int3test(OPTIMIZER, CONFIG) - MOIT.indicator1_test(OPTIMIZER, CONFIG) - MOIT.indicator2_test(OPTIMIZER, CONFIG) # replace with MOIT when MathOptInterface.jl#929 merged - indicator4_test(OPTIMIZER, CONFIG) - # MOIT.indicator3_test(OPTIMIZER, CONFIG) # no support for ACTIVATE_ON_ZERO + indicator4_test(OPTIMIZER, CONFIG_DIRECT) end diff --git a/test/cutcallback.jl b/test/cutcallback.jl index 6d270eba..632bd9a5 100644 --- a/test/cutcallback.jl +++ b/test/cutcallback.jl @@ -14,8 +14,8 @@ const MOI = MathOptInterface # add variables x, y = MOI.add_variables(optimizer, 2) - MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.ZeroOne()) - MOI.add_constraint(optimizer, MOI.SingleVariable(y), MOI.ZeroOne()) + MOI.add_constraint(optimizer, x, MOI.ZeroOne()) + MOI.add_constraint(optimizer, y, MOI.ZeroOne()) # add constraint: x + y ≤ 1.5 MOI.add_constraint(optimizer, @@ -69,8 +69,8 @@ end # add variables x, y = MOI.add_variables(optimizer, 2) - MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.ZeroOne()) - MOI.add_constraint(optimizer, MOI.SingleVariable(y), MOI.ZeroOne()) + MOI.add_constraint(optimizer, x, MOI.ZeroOne()) + MOI.add_constraint(optimizer, y, MOI.ZeroOne()) # add constraint: x + y ≤ 1.5 MOI.add_constraint(optimizer, @@ -120,8 +120,8 @@ end # add variables x, y = MOI.add_variables(optimizer, 2) - MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.ZeroOne()) - MOI.add_constraint(optimizer, MOI.SingleVariable(y), MOI.ZeroOne()) + MOI.add_constraint(optimizer, x, MOI.ZeroOne()) + MOI.add_constraint(optimizer, y, MOI.ZeroOne()) # add constraint: x + y ≤ 1.5 MOI.add_constraint(optimizer, diff --git a/test/runtests.jl b/test/runtests.jl index e8c29e1e..3dcf375d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -28,6 +28,55 @@ else @info "Separation and callbacks not tested for SCIP versions below 7" end +const MOI_BASE_EXCLUDED = [ + "Semicontinuous", + "ScalarAffineFunction_Semiinteger", + "ScalarAffineFunction_ZeroOne", + "ScalarQuadraticFunction_Semiinteger", + "ScalarQuadraticFunction_ZeroOne", + "VectorAffineFunction_GeometricMeanCone", + "Indicator_GreaterThan", + "Indicator_LessThan", + "VectorAffineFunction_NormOneCone", + "VectorQuadraticFunction_NormOneCone", + "VectorAffineFunction_RotatedSecondOrderCone", # SOC tests fail because of lower bound requirement of RHS var. + "VectorAffineFunction_SOS", + "VectorQuadraticFunction_SOS", + "VectorAffineFunction_SecondOrderCone", + "GeometricMeanCone", + "RotatedSecondOrderCone", + "SecondOrderCone", + "Indicator_ACTIVATE_ON_ZERO", + "test_constraint_ZeroOne_bounds", # accessing variable from string name + "test_constraint_get_ConstraintIndex", + "test_model_ListOfConstraintAttributesSet", + "BoundAlreadySet", # see TODO, + "ListOfConstraintIndices", # MathOptInterface.ListOfModelAttributesSet + "ListOfConstraintTypesPresent", + "ScalarAffineFunction_ConstraintName", # get(::SCIP.Optimizer, ::Type{MathOptInterface.ConstraintIndex}, ::String) + "ScalarFunctionConstantNotZero", + "UnsupportedAttribute", # test_model_copy_to_UnsupportedAttribute: MOI.copy_to(model, BadVariableAttributeModel()) + "UnsupportedConstraint", + "test_model_delete", # MOI.ListOfConstraintTypesPresent + "duplicate_VariableName", # get variable by name + "test_modification_coef_scalaraffine_", + "test_modification_coef_vectoraffine_", + "test_modification_const_vectoraffine_nonpos", + "test_modification_delete_variables_in_a_batch", + "test_modification_func_scalaraffine_", + "test_modification_func_vectoraffine_", + "test_modification_set_function_single_variable", + "test_modification_set_scalaraffine_", + "test_modification_set_singlevariable_", + "test_modification_transform_", + "test_nonlinear_", # None of tests provide expression graphs in the evaluator. + "FEASIBILITY_SENSE", # TODO + "ObjectiveFunction_ScalarAffineFunction", + "test_objective_set_via_modify", + "test_solve_ObjectiveBound_MAX_SENSE", + "test_solve_ObjectiveBound_MIN_SENSE", +] + @testset "MathOptInterface tests (bridged)" begin include("MOI_wrapper_bridged.jl") end diff --git a/test/sepa.jl b/test/sepa.jl index f2a9285b..853d8388 100644 --- a/test/sepa.jl +++ b/test/sepa.jl @@ -10,8 +10,8 @@ const MOI = MathOptInterface # add variables x, y = MOI.add_variables(optimizer, 2) - MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.ZeroOne()) - MOI.add_constraint(optimizer, MOI.SingleVariable(y), MOI.ZeroOne()) + MOI.add_constraint(optimizer, x, MOI.ZeroOne()) + MOI.add_constraint(optimizer, y, MOI.ZeroOne()) # add constraint: x + y ≤ 1.5 MOI.add_constraint(optimizer, @@ -50,8 +50,8 @@ end # add variables x, y = MOI.add_variables(optimizer, 2) - MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.ZeroOne()) - MOI.add_constraint(optimizer, MOI.SingleVariable(y), MOI.ZeroOne()) + MOI.add_constraint(optimizer, x, MOI.ZeroOne()) + MOI.add_constraint(optimizer, y, MOI.ZeroOne()) # add constraint: x + y ≤ 1.5 MOI.add_constraint(optimizer, @@ -97,8 +97,8 @@ end # add variables x, y = MOI.add_variables(optimizer, 2) - MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.ZeroOne()) - MOI.add_constraint(optimizer, MOI.SingleVariable(y), MOI.ZeroOne()) + MOI.add_constraint(optimizer, x, MOI.ZeroOne()) + MOI.add_constraint(optimizer, y, MOI.ZeroOne()) # add constraint: x + y ≤ 1.5 MOI.add_constraint(optimizer, @@ -144,8 +144,8 @@ end # add variables x, y = MOI.add_variables(optimizer, 2) - MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.ZeroOne()) - MOI.add_constraint(optimizer, MOI.SingleVariable(y), MOI.ZeroOne()) + MOI.add_constraint(optimizer, x, MOI.ZeroOne()) + MOI.add_constraint(optimizer, y, MOI.ZeroOne()) # add constraint: x + y ≤ 1.5 MOI.add_constraint(optimizer,