Skip to content

Commit

Permalink
Merge pull request #133 from SCIP-Interfaces/rs/constraint_name
Browse files Browse the repository at this point in the history
Add/fix some MOI methods
  • Loading branch information
rschwarz authored Sep 26, 2019
2 parents 4eb528b + 07c59a9 commit 2fccb13
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 51 deletions.
3 changes: 3 additions & 0 deletions src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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}}
Expand Down Expand Up @@ -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"))
Expand Down
19 changes: 19 additions & 0 deletions src/MOI_wrapper/abspower_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 10 additions & 0 deletions src/MOI_wrapper/constraints.jl
Original file line number Diff line number Diff line change
@@ -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
29 changes: 29 additions & 0 deletions src/MOI_wrapper/indicator_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 0 additions & 9 deletions src/MOI_wrapper/linear_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions src/MOI_wrapper/objective.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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
16 changes: 16 additions & 0 deletions src/MOI_wrapper/soc_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions src/managed_scip.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
101 changes: 61 additions & 40 deletions test/MOI_additional.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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))
Expand All @@ -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()

Expand Down Expand Up @@ -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)

Expand Down

0 comments on commit 2fccb13

Please sign in to comment.