Skip to content

Commit 79494f9

Browse files
committed
Allocation-free range
1 parent cb9be9d commit 79494f9

File tree

3 files changed

+52
-60
lines changed

3 files changed

+52
-60
lines changed

src/iis.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Base.@kwdef mutable struct Optimizer
5353
#
5454
# iis attributes
5555
time_limit::Float64 = Inf
56-
verbose::Bool = false
56+
verbose::Bool = true
5757
skip_feasibility_check::Bool = false
5858
stop_if_infeasible_bounds::Bool = true
5959
stop_if_infeasible_ranges::Bool = true
@@ -287,7 +287,7 @@ function MOI.compute_conflict!(optimizer::Optimizer)
287287
println("Starting bound analysis.")
288288
end
289289
bounds_consistent, variables, lb_con, ub_con =
290-
_bound_infeasibility!(optimizer, T)
290+
@time _bound_infeasibility!(optimizer, T)
291291
bound_infeasibilities = length(optimizer.results)
292292
if optimizer.verbose
293293
println(
@@ -310,7 +310,7 @@ function MOI.compute_conflict!(optimizer::Optimizer)
310310
println("Starting range analysis.")
311311
end
312312
range_consistent =
313-
_range_infeasibility!(optimizer, T, variables, lb_con, ub_con)
313+
@time _range_infeasibility!(optimizer, T, variables, lb_con, ub_con)
314314
range_infeasibilities = length(optimizer.results) - bound_infeasibilities
315315
if optimizer.verbose
316316
println(
@@ -320,6 +320,7 @@ function MOI.compute_conflict!(optimizer::Optimizer)
320320
if length(optimizer.results) > 0
321321
optimizer.status = MOI.CONFLICT_FOUND
322322
end
323+
return
323324

324325
if (!range_consistent && optimizer.stop_if_infeasible_ranges) ||
325326
!_in_time(optimizer)

src/range.jl

Lines changed: 37 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,57 +9,33 @@ function _range_infeasibility!(
99
variables,
1010
lb_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex},
1111
ub_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex},
12-
) where {T}
12+
::Type{S},
13+
) where {T,S}
1314
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-
),
15+
for con in MOI.get(
16+
optimizer.original_model,
17+
MOI.ListOfConstraintIndices{
18+
MOI.ScalarAffineFunction{T},
19+
S,
20+
}(),
3721
)
38-
39-
for con in affine_cons
4022
if !_in_time(optimizer)
4123
return range_consistent
4224
end
4325
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
26+
interval = @time _eval_variables(variables, func)::Union{Nothing,Interval{T}}
27+
if isnothing(interval)
5628
continue
5729
end
5830
set = MOI.get(optimizer.original_model, MOI.ConstraintSet(), con)
5931
if _invalid_range(set, interval)
32+
if optimizer.verbose
33+
println("Found one inconsistent range")
34+
end
6035
cons = Set{MOI.ConstraintIndex}()
6136
push!(cons, con)
62-
for var in list_of_variables
37+
for t in func.terms
38+
var = t.variable
6339
if haskey(lb_con, var)
6440
push!(cons, lb_con[var])
6541
end
@@ -81,6 +57,20 @@ function _range_infeasibility!(
8157
return range_consistent
8258
end
8359

60+
function _range_infeasibility!(
61+
optimizer::Optimizer,
62+
::Type{T},
63+
variables,
64+
lb_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex},
65+
ub_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex},
66+
) where {T}
67+
range_consistent = true
68+
range_consistent &= _range_infeasibility!(optimizer, T, variables, lb_con, ub_con, MOI.EqualTo{T})
69+
range_consistent &= _range_infeasibility!(optimizer, T, variables, lb_con, ub_con, MOI.LessThan{T})
70+
range_consistent &= _range_infeasibility!(optimizer, T, variables, lb_con, ub_con, MOI.GreaterThan{T})
71+
return range_consistent
72+
end
73+
8474
function _invalid_range(set::MOI.EqualTo, interval)
8575
rhs = set.value
8676
return interval.lo > rhs || interval.hi < rhs
@@ -135,20 +125,17 @@ end
135125
# This type and the associated function were inspired by JuMP.jl and
136126
# MathOptInterface.jl
137127

138-
function _eval_variables(value_fn::Function, t::MOI.ScalarAffineTerm)
139-
return t.coefficient * value_fn(t.variable)
140-
end
141-
142128
function _eval_variables(
143-
value_fn::Function,
129+
map::AbstractDict{MOI.VariableIndex,U},
144130
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)
131+
) where {T,U}
149132
out = convert(U, f.constant)
150-
for t in f.terms
151-
out += _eval_variables(value_fn, t)
133+
@time for t in f.terms
134+
if haskey(map, t.variable)
135+
out += t.coefficient * map[t.variable]
136+
else
137+
return
138+
end
152139
end
153140
return out
154141
end

src/solver.jl

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,24 +84,30 @@ function _elastic_filter(optimizer::Optimizer)
8484

8585
de_elastisized = []
8686

87-
changed_obj = false
88-
8987
# all (affine, non-bound) constraints are relaxed at this point
90-
# we will try to set positive slacks to zero until the model infeasible
91-
# the constraints of the fixed slacks are a IIS candidate
88+
# we will try to set positive slacks to zero until the model becomes
89+
# infeasible. The constraints of the fixed slacks then form a IIS candidate.
9290

9391
for i in 1:max_iterations
9492
if !_in_time(optimizer)
9593
return nothing
9694
end
95+
if optimizer.verbose
96+
println("Solving iteration $i...")
97+
end
9798
MOI.optimize!(model)
99+
if optimizer.verbose
100+
println("Iteration $i solved.")
101+
end
98102
status = MOI.get(model, MOI.TerminationStatus())
99103
if status in ( # possibily primal unbounded statuses
100104
MOI.INFEASIBLE_OR_UNBOUNDED,
101105
MOI.DUAL_INFEASIBLE,
102106
MOI.ALMOST_DUAL_INFEASIBLE,
103107
)
104-
#
108+
if optimizer.verbose
109+
@warn("Termination status is $status")
110+
end
105111
end
106112
if status in
107113
(MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE, MOI.LOCALLY_INFEASIBLE)
@@ -143,8 +149,6 @@ function _elastic_filter(optimizer::Optimizer)
143149
# consider deleting all no iis constraints
144150
# be careful with intervals
145151

146-
obj_type = MOI.get(model, MOI.ObjectiveFunctionType())
147-
obj_func = MOI.get(model, MOI.ObjectiveFunction{obj_type}())
148152
obj_sense = MOI.get(model, MOI.ObjectiveSense())
149153

150154
candidates = MOI.ConstraintIndex[]

0 commit comments

Comments
 (0)