diff --git a/src/range.jl b/src/range.jl index 743bc92..39d91be 100644 --- a/src/range.jl +++ b/src/range.jl @@ -3,103 +3,6 @@ # Use of this source code is governed by an MIT-style license that can be found # in the LICENSE.md file or at https://opensource.org/licenses/MIT. -function _range_infeasibility!( - optimizer::Optimizer, - ::Type{T}, - variables, - lb_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex}, - ub_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex}, -) where {T} - range_consistent = true - - affine_cons = vcat( - MOI.get( - optimizer.original_model, - MOI.ListOfConstraintIndices{ - MOI.ScalarAffineFunction{T}, - MOI.EqualTo{T}, - }(), - ), - MOI.get( - optimizer.original_model, - MOI.ListOfConstraintIndices{ - MOI.ScalarAffineFunction{T}, - MOI.LessThan{T}, - }(), - ), - MOI.get( - optimizer.original_model, - MOI.ListOfConstraintIndices{ - MOI.ScalarAffineFunction{T}, - MOI.GreaterThan{T}, - }(), - ), - ) - - for con in affine_cons - if !_in_time(optimizer) - return range_consistent - end - func = MOI.get(optimizer.original_model, MOI.ConstraintFunction(), con) - failed = false - list_of_variables = MOI.VariableIndex[] - interval = _eval_variables(func) do var_idx - push!(list_of_variables, var_idx) - # this only fails if we allow continuing after bounds issues - if !haskey(variables, var_idx) - failed = true - return Interval(-Inf, Inf) - end - return variables[var_idx] - end - if failed - continue - end - set = MOI.get(optimizer.original_model, MOI.ConstraintSet(), con) - if _invalid_range(set, interval) - cons = Set{MOI.ConstraintIndex}() - push!(cons, con) - for var in list_of_variables - if haskey(lb_con, var) - push!(cons, lb_con[var]) - end - if haskey(ub_con, var) - push!(cons, ub_con[var]) - end - end - push!( - optimizer.results, - InfeasibilityData( - collect(cons), - true, # strictly speaking, we might need the propor "sides" - RangeData(interval.lo, interval.hi, set), - ), - ) - range_consistent = false - end - end - return range_consistent -end - -function _invalid_range(set::MOI.EqualTo, interval) - rhs = set.value - return interval.lo > rhs || interval.hi < rhs -end - -function _invalid_range(set::MOI.LessThan, interval) - rhs = set.upper - return interval.lo > rhs -end - -function _invalid_range(set::MOI.GreaterThan, interval) - rhs = set.lower - return interval.hi < rhs -end - -#= - Helpers -=# - # This type and the associated function were inspired by IntervalArithmetic.jl # Copyright (c) 2014-2021: David P. Sanders & Luis Benet @@ -132,23 +35,112 @@ function Base.:*(x::T, a::Interval{T}) where {T<:Real} return Interval(a.hi * x, a.lo * x) end -# This type and the associated function were inspired by JuMP.jl and -# MathOptInterface.jl +# Back to functions written for MathOptIIS.jl + +function _range_infeasibility!( + optimizer::Optimizer, + ::Type{T}, + variables::Dict{MOI.VariableIndex,Interval{T}}, + lb_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex}, + ub_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex}, +) where {T} + range_consistent = _range_infeasibility!( + optimizer, + optimizer.original_model, + T, + variables, + lb_con, + ub_con, + MOI.EqualTo{T}, + ) + range_consistent &= _range_infeasibility!( + optimizer, + optimizer.original_model, + T, + variables, + lb_con, + ub_con, + MOI.LessThan{T}, + ) + return _range_infeasibility!( + optimizer, + optimizer.original_model, + T, + variables, + lb_con, + ub_con, + MOI.GreaterThan{T}, + ) +end -function _eval_variables(value_fn::Function, t::MOI.ScalarAffineTerm) - return t.coefficient * value_fn(t.variable) +function _range_infeasibility!( + optimizer::Optimizer, + original_model::MOI.ModelLike, + ::Type{T}, + variables::Dict{MOI.VariableIndex,Interval{T}}, + lb_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex}, + ub_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex}, + ::Type{S}, +) where {T,S} + range_consistent = true + for con in MOI.get( + original_model, + MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T},S}(), + ) + if !_in_time(optimizer) + return range_consistent + end + func = MOI.get(original_model, MOI.ConstraintFunction(), con) + interval = _eval_variables(variables, func) + if interval === nothing + continue + end + set = MOI.get(original_model, MOI.ConstraintSet(), con)::S + if !_invalid_range(set, interval) + continue + end + cons = Set{MOI.ConstraintIndex}() + push!(cons, con) + for t in func.terms + if (c = get(lb_con, t.variable, nothing)) !== nothing + push!(cons, c) + end + if (c = get(ub_con, t.variable, nothing)) !== nothing + push!(cons, c) + end + end + push!( + optimizer.results, + InfeasibilityData( + collect(cons), + true, # strictly speaking, we might need the proper "sides" + RangeData(interval.lo, interval.hi, set), + ), + ) + range_consistent = false + end + return range_consistent end function _eval_variables( - value_fn::Function, - f::MOI.ScalarAffineFunction{T}, -) where {T} - # TODO: this conversion exists in JuMP, but not in MOI - S = Base.promote_op(value_fn, MOI.VariableIndex) - U = MOI.MA.promote_operation(*, T, S) + map::AbstractDict{MOI.VariableIndex,U}, + f::MOI.ScalarAffineFunction, +) where {U} out = convert(U, f.constant) for t in f.terms - out += _eval_variables(value_fn, t) + v = get(map, t.variable, nothing) + if v === nothing + return + end + out += t.coefficient * v end return out end + +function _invalid_range(set::MOI.EqualTo, interval) + return !(interval.lo <= set.value <= interval.hi) +end + +_invalid_range(set::MOI.LessThan, interval) = set.upper < interval.lo + +_invalid_range(set::MOI.GreaterThan, interval) = interval.hi < set.lower