Skip to content

Commit

Permalink
Merge pull request #203 from scipopt/fix-moi10
Browse files Browse the repository at this point in the history
upgrade to MOI 10
  • Loading branch information
matbesancon authored Sep 27, 2021
2 parents 7aa79aa + a94a111 commit 8e277d4
Show file tree
Hide file tree
Showing 23 changed files with 425 additions and 502 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
48 changes: 32 additions & 16 deletions src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -44,16 +43,15 @@ 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
# delimiter, since "/" is used by all SCIP parameters, but is not
# 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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions src/MOI_wrapper/abspower_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -55,20 +56,20 @@ 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)
return VECTOR([VI(ref(o, nlvar).val), VI(ref(o, lvar).val)])
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
24 changes: 24 additions & 0 deletions src/MOI_wrapper/constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
27 changes: 17 additions & 10 deletions src/MOI_wrapper/indicator_constraints.jl
Original file line number Diff line number Diff line change
@@ -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}
Expand All @@ -46,13 +48,18 @@ 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}

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))
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
19 changes: 11 additions & 8 deletions src/MOI_wrapper/linear_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,37 @@

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)
lhs = lhs === nothing ? -SCIPinfinity(o) : lhs
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))
delete(o.inner, ConsRef(ci.value))
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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
16 changes: 8 additions & 8 deletions src/MOI_wrapper/objective.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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

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

0 comments on commit 8e277d4

Please sign in to comment.