diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index a6c07dfc..1a3bcacd 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -9,6 +9,7 @@ const CI = MOI.ConstraintIndex const SVF = MOI.SingleVariable const SAF = MOI.ScalarAffineFunction{Float64} const SQF = MOI.ScalarQuadraticFunction{Float64} +const VAF = MOI.VectorAffineFunction{Float64} const VECTOR = MOI.VectorOfVariables # supported sets const BOUNDS = Union{MOI.EqualTo{Float64}, MOI.GreaterThan{Float64}, @@ -20,6 +21,7 @@ const SOS2 = MOI.SOS2{Float64} # other MOI types const AFF_TERM = MOI.ScalarAffineTerm{Float64} const QUAD_TERM = MOI.ScalarQuadraticTerm{Float64} +const VEC_TERM = MOI.VectorAffineTerm{Float64} const PtrMap = Dict{Ptr{Cvoid}, Union{VarRef, ConsRef}} const ConsTypeMap = Dict{Tuple{DataType, DataType}, Set{ConsRef}} @@ -193,6 +195,7 @@ function MOI.optimize!(o::Optimizer) end include(joinpath("MOI_wrapper", "variable.jl")) +include(joinpath("MOI_wrapper", "constraints.jl")) include(joinpath("MOI_wrapper", "linear_constraints.jl")) include(joinpath("MOI_wrapper", "quadratic_constraints.jl")) include(joinpath("MOI_wrapper", "soc_constraints.jl")) diff --git a/src/MOI_wrapper/abspower_constraints.jl b/src/MOI_wrapper/abspower_constraints.jl index 92acc65c..73efe5e0 100644 --- a/src/MOI_wrapper/abspower_constraints.jl +++ b/src/MOI_wrapper/abspower_constraints.jl @@ -53,3 +53,22 @@ function MOI.delete(o::Optimizer, ci::CI{VECTOR, ABSPOWER}) delete(o.mscip, ConsRef(ci.value)) return nothing end + +function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{VECTOR, ABSPOWER}) + c = cons(o, ci)::Ptr{SCIP_CONS} + lvar = SCIPgetLinearVarAbspower(o, c) + nlvar = SCIPgetNonlinearVarAbspower(o, c) + return VECTOR([VI(ref(o, nlvar).val), VI(ref(o, lvar).val)]) +end + +function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{VECTOR, ABSPOWER}) + 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 new file mode 100644 index 00000000..6d5f025c --- /dev/null +++ b/src/MOI_wrapper/constraints.jl @@ -0,0 +1,10 @@ +# generic constraints + +function MOI.get(o::Optimizer, ::MOI.ConstraintName, ci::CI)::String + return GC.@preserve o unsafe_string(SCIPconsGetName(cons(o, ci))) +end + +function MOI.set(o::Optimizer, ::MOI.ConstraintName, ci::CI, name::String) + @SC SCIPchgConsName(o, cons(o, ci), name) + return nothing +end diff --git a/src/MOI_wrapper/indicator_constraints.jl b/src/MOI_wrapper/indicator_constraints.jl index b4852eca..90e96902 100644 --- a/src/MOI_wrapper/indicator_constraints.jl +++ b/src/MOI_wrapper/indicator_constraints.jl @@ -25,3 +25,32 @@ function MOI.delete(o::Optimizer, ci::CI{MOI.VectorAffineFunction{T}, MOI.Indica delete(o.mscip, 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} + indicator_cons = cons(o, ci)::Ptr{SCIP_CONS} + bin_var = SCIPgetBinaryVarIndicator(indicator_cons)::Ptr{SCIP_VAR} + slack_var = SCIPgetSlackVarIndicator(indicator_cons)::Ptr{SCIP_VAR} + linear_cons = SCIPgetLinearConsIndicator(indicator_cons)::Ptr{SCIP_CONS} + + nvars::Int = SCIPgetNVarsLinear(o, linear_cons) + vars = unsafe_wrap(Array{Ptr{SCIP_VAR}}, SCIPgetVarsLinear(o, linear_cons), nvars) + vals = unsafe_wrap(Array{Float64}, SCIPgetValsLinear(o, linear_cons), nvars) + aff_terms = [AFF_TERM(vals[i], VI(ref(o, vars[i]).val)) for i=1:nvars + if vars[i] != slack_var] + + ind_terms = [VEC_TERM(1, AFF_TERM(1.0, VI(ref(o, bin_var).val)))] + vec_terms = [VEC_TERM(2, term) for term in aff_terms] + + 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} + indicator_cons = cons(o, ci)::Ptr{SCIP_CONS} + linear_cons = SCIPgetLinearConsIndicator(indicator_cons)::Ptr{SCIP_CONS} + + lhs = SCIPgetLhsLinear(o, linear_cons) + 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)) +end diff --git a/src/MOI_wrapper/linear_constraints.jl b/src/MOI_wrapper/linear_constraints.jl index 4dd78f99..4a32462f 100644 --- a/src/MOI_wrapper/linear_constraints.jl +++ b/src/MOI_wrapper/linear_constraints.jl @@ -61,15 +61,6 @@ function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{SAF, S}) where S <: B return from_bounds(S, lhs, rhs) end -function MOI.get(o::Optimizer, ::MOI.ConstraintName, ci::CI{SAF,<:BOUNDS}) - return GC.@preserve o SCIPconsGetName(cons(o, ci)) -end - -function MOI.set(o::Optimizer, ::MOI.ConstraintName, ci::CI{SAF,<:BOUNDS}, name::String) - @SC SCIPchgConsName(o, cons(o, ci), name) - return nothing -end - function MOI.modify(o::Optimizer, ci::CI{SAF, <:BOUNDS}, change::MOI.ScalarCoefficientChange{Float64}) allow_modification(o) diff --git a/src/MOI_wrapper/objective.jl b/src/MOI_wrapper/objective.jl index 51c059ec..97a9baa1 100644 --- a/src/MOI_wrapper/objective.jl +++ b/src/MOI_wrapper/objective.jl @@ -29,6 +29,7 @@ function MOI.set(o::Optimizer, ::MOI.ObjectiveFunction{SAF}, obj::SAF) return nothing 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) return MOI.set(o, MOI.ObjectiveFunction{SAF}(), aff_obj) @@ -45,6 +46,7 @@ function MOI.get(o::Optimizer, ::MOI.ObjectiveFunction{SAF}) return SAF(terms, constant) end +# Note that SCIP always uses a scalar affine function internally! function MOI.get(o::Optimizer, ::MOI.ObjectiveFunction{SVF}) aff_obj = MOI.get(o, MOI.ObjectiveFunction{SAF}()) if (length(aff_obj.terms) != 1 @@ -77,3 +79,5 @@ function MOI.modify(o::Optimizer, ::MOI.ObjectiveFunction{SAF}, @SC SCIPchgVarObj(o, var(o, change.variable), change.new_coefficient) return nothing end + +MOI.get(o::Optimizer, ::MOI.ObjectiveFunctionType) = SAF diff --git a/src/MOI_wrapper/soc_constraints.jl b/src/MOI_wrapper/soc_constraints.jl index b033883a..dab20eeb 100644 --- a/src/MOI_wrapper/soc_constraints.jl +++ b/src/MOI_wrapper/soc_constraints.jl @@ -27,3 +27,19 @@ function MOI.delete(o::Optimizer, ci::CI{VECTOR, SOC}) delete(o.mscip, ConsRef(ci.value)) return nothing end + +function MOI.get(o::Optimizer, ::MOI.ConstraintFunction, ci::CI{VECTOR, SOC}) + c = cons(o, ci)::Ptr{SCIP_CONS} + nvars::Int = SCIPgetNLhsVarsSOC(o, c) + vars = unsafe_wrap(Array{Ptr{SCIP_VAR}}, SCIPgetLhsVarsSOC(o, c), nvars) + rhsvar = SCIPgetRhsVarSOC(o, c) + + ptr2index(p) = VI(ref(o, p).val) + return VECTOR(vcat([ptr2index(rhsvar)], ptr2index.(vars))) +end + +function MOI.get(o::Optimizer, ::MOI.ConstraintSet, ci::CI{VECTOR, SOC}) + c = cons(o, ci)::Ptr{SCIP_CONS} + nvars::Int = SCIPgetNLhsVarsSOC(o, c) + return MOI.SecondOrderCone(nvars + 1) +end diff --git a/src/managed_scip.jl b/src/managed_scip.jl index d3ee38a3..5f559deb 100644 --- a/src/managed_scip.jl +++ b/src/managed_scip.jl @@ -310,13 +310,13 @@ Add indicator constraint to problem, return cons ref. y has to be a binary variable, or SCIP will error. # Arguments -- `y::VarRef`: reference for binary variable +- `y::VarRef`: reference for binary indicator variable - `x::Vector{VarRef}`: reference vector for variables - `a::Float64`: coefficients for x variable - `rhs::Float64`: right-hand side for linear constraint """ function add_indicator_constraint(mscip::ManagedSCIP, y, x, a, rhs) - SCIPvarIsBinary(var(mscip, y)) > 0 || error("y variable must be binary for indicator constraint") + SCIPvarIsBinary(var(mscip, y)) > 0 || error("indicator variable must be binary.") cons__ = Ref{Ptr{SCIP_CONS}}(C_NULL) xref = [var(mscip, x[i]) for i in eachindex(x)] @SC SCIPcreateConsBasicIndicator( diff --git a/test/MOI_additional.jl b/test/MOI_additional.jl index 0f2c538c..adf08f72 100644 --- a/test/MOI_additional.jl +++ b/test/MOI_additional.jl @@ -109,40 +109,6 @@ end 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) - - MOI.empty!(optimizer) - x1 = MOI.add_variable(optimizer) - x2 = MOI.add_variable(optimizer) - 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)) - ind_func = MOI.VectorAffineFunction( - [MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, y)), - MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x1)), - MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x2)), - MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x3)), - ], [0.0, 0.0], - ) - - c = MOI.add_constraint(optimizer, ind_func, iset) - @test MOI.delete(optimizer, c) === nothing - - # adding incorrect function throws - ind_func_wrong = MOI.VectorAffineFunction( - [MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, y)), - MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x1)), - MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x2)), - MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x3)), - ], [0.0, 0.0], - ) - @test_throws ErrorException MOI.add_constraint(optimizer, ind_func_wrong, iset) - ind_func_wrong2 = MOI.VectorAffineFunction( - [MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x2)), - MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x3)), - ], [0.0, 0.0], - ) - @test_throws ErrorException MOI.add_constraint(optimizer, ind_func_wrong2, iset) end @testset "Bound constraints for a general variable." begin @@ -255,6 +221,9 @@ end csoc = MOI.add_constraint(optimizer, MOI.VectorOfVariables([x, y, z]), MOI.SecondOrderCone(3)) + @test MOI.get(optimizer, MOI.ConstraintFunction(), csoc) == MOI.VectorOfVariables([x, y, z]) + @test MOI.get(optimizer, MOI.ConstraintSet(), csoc) == MOI.SecondOrderCone(3) + MOI.optimize!(optimizer) @test MOI.get(optimizer, MOI.TerminationStatus()) == MOI.OPTIMAL @test MOI.get(optimizer, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT @@ -306,12 +275,15 @@ end 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, MOI.VectorOfVariables([x,y,z]), MOI.SOS1([1.0,2.0,3.0])) + c = MOI.add_constraint(optimizer, MOI.VectorOfVariables([x,y,z]), MOI.SOS1([1.0,2.0,3.0])) MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0,2.0,3.0], [x,y,z]), 0.0)) MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MAX_SENSE) + @test MOI.get(optimizer, MOI.ConstraintFunction(), c) == MOI.VectorOfVariables([x,y,z]) + @test MOI.get(optimizer, MOI.ConstraintSet(), c) == MOI.SOS1([1.0,2.0,3.0]) + MOI.optimize!(optimizer) @test MOI.get(optimizer, MOI.TerminationStatus()) == MOI.OPTIMAL @test MOI.get(optimizer, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT @@ -331,12 +303,15 @@ end 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, MOI.VectorOfVariables([x,y,z]), MOI.SOS2([1.0,2.0,3.0])) + c = MOI.add_constraint(optimizer, MOI.VectorOfVariables([x,y,z]), MOI.SOS2([1.0,2.0,3.0])) MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0,2.0,3.0], [x,y,z]), 0.0)) MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MAX_SENSE) + @test MOI.get(optimizer, MOI.ConstraintFunction(), c) == MOI.VectorOfVariables([x,y,z]) + @test MOI.get(optimizer, MOI.ConstraintSet(), c) == MOI.SOS2([1.0,2.0,3.0]) + MOI.optimize!(optimizer) @test MOI.get(optimizer, MOI.TerminationStatus()) == MOI.OPTIMAL @test MOI.get(optimizer, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT @@ -360,10 +335,15 @@ end 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, MOI.VectorOfVariables([x1, z1]), - SCIP.AbsolutePowerSet(2.0)) - MOI.add_constraint(optimizer, MOI.VectorOfVariables([x2, z2]), - SCIP.AbsolutePowerSet(3.0)) + c1 = MOI.add_constraint(optimizer, MOI.VectorOfVariables([x1, z1]), + SCIP.AbsolutePowerSet(2.0)) + c2 = MOI.add_constraint(optimizer, MOI.VectorOfVariables([x2, z2]), + SCIP.AbsolutePowerSet(3.0)) + + @test MOI.get(optimizer, MOI.ConstraintFunction(), c1) == MOI.VectorOfVariables([x1, z1]) + @test MOI.get(optimizer, MOI.ConstraintSet(), c1) == SCIP.AbsolutePowerSet(2.0) + @test MOI.get(optimizer, MOI.ConstraintFunction(), c2) == MOI.VectorOfVariables([x2, z2]) + @test MOI.get(optimizer, MOI.ConstraintSet(), c2) == SCIP.AbsolutePowerSet(3.0) MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, -1.0], [x1, x2]), 0.0)) @@ -381,6 +361,46 @@ end @test MOI.get(optimizer, MOI.VariablePrimal(), z2) ≈ -8.0 atol=atol rtol=rtol end +@testset "indicator constraints" begin + optimizer = SCIP.Optimizer() + + x1 = MOI.add_variable(optimizer) + x2 = MOI.add_variable(optimizer) + 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)) + ind_func = MOI.VectorAffineFunction( + [MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, y)), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x1)), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x2)), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x3)), + ], [0.0, 0.0], + ) + + c = MOI.add_constraint(optimizer, ind_func, iset) + @test MOI.get(optimizer, MOI.ConstraintFunction(), c) ≈ ind_func + @test MOI.get(optimizer, MOI.ConstraintSet(), c) == iset + + @test MOI.delete(optimizer, c) === nothing + + # adding incorrect function throws + ind_func_wrong = MOI.VectorAffineFunction( + [MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, y)), + MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x1)), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x2)), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x3)), + ], [0.0, 0.0], + ) + @test_throws ErrorException MOI.add_constraint(optimizer, ind_func_wrong, iset) + ind_func_wrong2 = MOI.VectorAffineFunction( + [MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x2)), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x3)), + ], [0.0, 0.0], + ) + @test_throws ErrorException MOI.add_constraint(optimizer, ind_func_wrong2, iset) +end + @testset "deleting variables" begin optimizer = SCIP.Optimizer() @@ -413,6 +433,7 @@ end MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MAX_SENSE) @test MOI.get(optimizer, MOI.ObjectiveFunction{MOI.SingleVariable}()) == obj @test MOI.get(optimizer, MOI.ObjectiveSense()) == MOI.MAX_SENSE + @test MOI.get(optimizer, MOI.ObjectiveFunctionType()) == MOI.ScalarAffineFunction{Float64} MOI.empty!(optimizer)