Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

upgrade to MOI 10 #203

Merged
merged 16 commits into from
Sep 27, 2021
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: 33 additions & 15 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
MOIU.supports_default_copy_to(::Optimizer, copy_names::Bool) = !copy_names
matbesancon marked this conversation as resolved.
Show resolved Hide resolved

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 @@ -214,7 +219,7 @@ function MOI.empty!(o::Optimizer)
end

function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike; kws...)
matbesancon marked this conversation as resolved.
Show resolved Hide resolved
return MOIU.automatic_copy_to(dest, src; kws...)
return MOIU.default_copy_to(dest, src; kws...)
matbesancon marked this conversation as resolved.
Show resolved Hide resolved
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 list_indices
end

function set_start_values(o::Optimizer)
if isempty(o.start)
# no primal start values are given
Expand Down Expand Up @@ -257,6 +273,8 @@ function MOI.optimize!(o::Optimizer)
return nothing
end

MOI.supports_incremental_interface(::Optimizer) = true

include(joinpath("MOI_wrapper", "variable.jl"))
include(joinpath("MOI_wrapper", "constraints.jl"))
include(joinpath("MOI_wrapper", "linear_constraints.jl"))
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