Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 100 additions & 108 deletions src/range.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

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