|
3 | 3 | # Use of this source code is governed by an MIT-style license that can be found |
4 | 4 | # in the LICENSE.md file or at https://opensource.org/licenses/MIT. |
5 | 5 |
|
6 | | -function _range_infeasibility!( |
7 | | - optimizer::Optimizer, |
8 | | - ::Type{T}, |
9 | | - variables, |
10 | | - lb_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex}, |
11 | | - ub_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex}, |
12 | | -) where {T} |
13 | | - range_consistent = true |
14 | | - |
15 | | - affine_cons = vcat( |
16 | | - MOI.get( |
17 | | - optimizer.original_model, |
18 | | - MOI.ListOfConstraintIndices{ |
19 | | - MOI.ScalarAffineFunction{T}, |
20 | | - MOI.EqualTo{T}, |
21 | | - }(), |
22 | | - ), |
23 | | - MOI.get( |
24 | | - optimizer.original_model, |
25 | | - MOI.ListOfConstraintIndices{ |
26 | | - MOI.ScalarAffineFunction{T}, |
27 | | - MOI.LessThan{T}, |
28 | | - }(), |
29 | | - ), |
30 | | - MOI.get( |
31 | | - optimizer.original_model, |
32 | | - MOI.ListOfConstraintIndices{ |
33 | | - MOI.ScalarAffineFunction{T}, |
34 | | - MOI.GreaterThan{T}, |
35 | | - }(), |
36 | | - ), |
37 | | - ) |
38 | | - |
39 | | - for con in affine_cons |
40 | | - if !_in_time(optimizer) |
41 | | - return range_consistent |
42 | | - end |
43 | | - func = MOI.get(optimizer.original_model, MOI.ConstraintFunction(), con) |
44 | | - failed = false |
45 | | - list_of_variables = MOI.VariableIndex[] |
46 | | - interval = _eval_variables(func) do var_idx |
47 | | - push!(list_of_variables, var_idx) |
48 | | - # this only fails if we allow continuing after bounds issues |
49 | | - if !haskey(variables, var_idx) |
50 | | - failed = true |
51 | | - return Interval(-Inf, Inf) |
52 | | - end |
53 | | - return variables[var_idx] |
54 | | - end |
55 | | - if failed |
56 | | - continue |
57 | | - end |
58 | | - set = MOI.get(optimizer.original_model, MOI.ConstraintSet(), con) |
59 | | - if _invalid_range(set, interval) |
60 | | - cons = Set{MOI.ConstraintIndex}() |
61 | | - push!(cons, con) |
62 | | - for var in list_of_variables |
63 | | - if haskey(lb_con, var) |
64 | | - push!(cons, lb_con[var]) |
65 | | - end |
66 | | - if haskey(ub_con, var) |
67 | | - push!(cons, ub_con[var]) |
68 | | - end |
69 | | - end |
70 | | - push!( |
71 | | - optimizer.results, |
72 | | - InfeasibilityData( |
73 | | - collect(cons), |
74 | | - true, # strictly speaking, we might need the propor "sides" |
75 | | - RangeData(interval.lo, interval.hi, set), |
76 | | - ), |
77 | | - ) |
78 | | - range_consistent = false |
79 | | - end |
80 | | - end |
81 | | - return range_consistent |
82 | | -end |
83 | | - |
84 | | -function _invalid_range(set::MOI.EqualTo, interval) |
85 | | - rhs = set.value |
86 | | - return interval.lo > rhs || interval.hi < rhs |
87 | | -end |
88 | | - |
89 | | -function _invalid_range(set::MOI.LessThan, interval) |
90 | | - rhs = set.upper |
91 | | - return interval.lo > rhs |
92 | | -end |
93 | | - |
94 | | -function _invalid_range(set::MOI.GreaterThan, interval) |
95 | | - rhs = set.lower |
96 | | - return interval.hi < rhs |
97 | | -end |
98 | | - |
99 | | -#= |
100 | | - Helpers |
101 | | -=# |
102 | | - |
103 | 6 | # This type and the associated function were inspired by IntervalArithmetic.jl |
104 | 7 | # Copyright (c) 2014-2021: David P. Sanders & Luis Benet |
105 | 8 |
|
@@ -132,23 +35,112 @@ function Base.:*(x::T, a::Interval{T}) where {T<:Real} |
132 | 35 | return Interval(a.hi * x, a.lo * x) |
133 | 36 | end |
134 | 37 |
|
135 | | -# This type and the associated function were inspired by JuMP.jl and |
136 | | -# MathOptInterface.jl |
| 38 | +# Back to functions written for MathOptIIS.jl |
| 39 | + |
| 40 | +function _range_infeasibility!( |
| 41 | + optimizer::Optimizer, |
| 42 | + ::Type{T}, |
| 43 | + variables::Dict{MOI.VariableIndex,Interval{T}}, |
| 44 | + lb_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex}, |
| 45 | + ub_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex}, |
| 46 | +) where {T} |
| 47 | + range_consistent = _range_infeasibility!( |
| 48 | + optimizer, |
| 49 | + optimizer.original_model, |
| 50 | + T, |
| 51 | + variables, |
| 52 | + lb_con, |
| 53 | + ub_con, |
| 54 | + MOI.EqualTo{T}, |
| 55 | + ) |
| 56 | + range_consistent &= _range_infeasibility!( |
| 57 | + optimizer, |
| 58 | + optimizer.original_model, |
| 59 | + T, |
| 60 | + variables, |
| 61 | + lb_con, |
| 62 | + ub_con, |
| 63 | + MOI.LessThan{T}, |
| 64 | + ) |
| 65 | + return _range_infeasibility!( |
| 66 | + optimizer, |
| 67 | + optimizer.original_model, |
| 68 | + T, |
| 69 | + variables, |
| 70 | + lb_con, |
| 71 | + ub_con, |
| 72 | + MOI.GreaterThan{T}, |
| 73 | + ) |
| 74 | +end |
137 | 75 |
|
138 | | -function _eval_variables(value_fn::Function, t::MOI.ScalarAffineTerm) |
139 | | - return t.coefficient * value_fn(t.variable) |
| 76 | +function _range_infeasibility!( |
| 77 | + optimizer::Optimizer, |
| 78 | + original_model::MOI.ModelLike, |
| 79 | + ::Type{T}, |
| 80 | + variables::Dict{MOI.VariableIndex,Interval{T}}, |
| 81 | + lb_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex}, |
| 82 | + ub_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex}, |
| 83 | + ::Type{S}, |
| 84 | +) where {T,S} |
| 85 | + range_consistent = true |
| 86 | + for con in MOI.get( |
| 87 | + original_model, |
| 88 | + MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T},S}(), |
| 89 | + ) |
| 90 | + if !_in_time(optimizer) |
| 91 | + return range_consistent |
| 92 | + end |
| 93 | + func = MOI.get(original_model, MOI.ConstraintFunction(), con) |
| 94 | + interval = _eval_variables(variables, func) |
| 95 | + if interval === nothing |
| 96 | + continue |
| 97 | + end |
| 98 | + set = MOI.get(original_model, MOI.ConstraintSet(), con)::S |
| 99 | + if !_invalid_range(set, interval) |
| 100 | + continue |
| 101 | + end |
| 102 | + cons = Set{MOI.ConstraintIndex}() |
| 103 | + push!(cons, con) |
| 104 | + for t in func.terms |
| 105 | + if (c = get(lb_con, t.variable, nothing)) !== nothing |
| 106 | + push!(cons, c) |
| 107 | + end |
| 108 | + if (c = get(ub_con, t.variable, nothing)) !== nothing |
| 109 | + push!(cons, c) |
| 110 | + end |
| 111 | + end |
| 112 | + push!( |
| 113 | + optimizer.results, |
| 114 | + InfeasibilityData( |
| 115 | + collect(cons), |
| 116 | + true, # strictly speaking, we might need the proper "sides" |
| 117 | + RangeData(interval.lo, interval.hi, set), |
| 118 | + ), |
| 119 | + ) |
| 120 | + range_consistent = false |
| 121 | + end |
| 122 | + return range_consistent |
140 | 123 | end |
141 | 124 |
|
142 | 125 | function _eval_variables( |
143 | | - value_fn::Function, |
144 | | - f::MOI.ScalarAffineFunction{T}, |
145 | | -) where {T} |
146 | | - # TODO: this conversion exists in JuMP, but not in MOI |
147 | | - S = Base.promote_op(value_fn, MOI.VariableIndex) |
148 | | - U = MOI.MA.promote_operation(*, T, S) |
| 126 | + map::AbstractDict{MOI.VariableIndex,U}, |
| 127 | + f::MOI.ScalarAffineFunction, |
| 128 | +) where {U} |
149 | 129 | out = convert(U, f.constant) |
150 | 130 | for t in f.terms |
151 | | - out += _eval_variables(value_fn, t) |
| 131 | + v = get(map, t.variable, nothing) |
| 132 | + if v === nothing |
| 133 | + return |
| 134 | + end |
| 135 | + out += t.coefficient * v |
152 | 136 | end |
153 | 137 | return out |
154 | 138 | end |
| 139 | + |
| 140 | +function _invalid_range(set::MOI.EqualTo, interval) |
| 141 | + return !(interval.lo <= set.value <= interval.hi) |
| 142 | +end |
| 143 | + |
| 144 | +_invalid_range(set::MOI.LessThan, interval) = set.upper < interval.lo |
| 145 | + |
| 146 | +_invalid_range(set::MOI.GreaterThan, interval) = interval.hi < set.lower |
0 commit comments