Skip to content

Commit dfb77ce

Browse files
committed
Update
1 parent 3ff5950 commit dfb77ce

3 files changed

Lines changed: 125 additions & 44 deletions

File tree

src/MathOptIIS.jl

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ function MOI.compute_conflict!(optimizer::Optimizer)
187187
empty!(optimizer.results)
188188
optimizer.start_time = time()
189189
if optimizer.verbose
190-
println("Starting MathOptIIS IIS search.")
190+
println("[MathOptIIS] starting compute_conflict!")
191191
end
192192
if _feasibility_check(optimizer, optimizer.infeasible_model)
193193
optimizer.status = MOI.NO_CONFLICT_EXISTS
@@ -216,7 +216,7 @@ function MOI.compute_conflict!(optimizer::Optimizer)
216216
end
217217
if optimizer.verbose
218218
println(
219-
"Complete elastic filter solver found $(length(optimizer.results)) infeasibilities.",
219+
"[MathOptIIS] elastic filter found $(length(optimizer.results)) infeasible subsets",
220220
)
221221
end
222222
return
@@ -232,15 +232,15 @@ function _feasibility_check(
232232
)
233233
termination_status = MOI.get(infeasible_model, MOI.TerminationStatus())
234234
if optimizer.verbose
235-
println("Original model termination status: $(termination_status)")
235+
println("[MathOptIIS] model termination status: $(termination_status)")
236236
end
237237
if termination_status in
238238
(MOI.OTHER_ERROR, MOI.INVALID_MODEL, MOI.OPTIMIZE_NOT_CALLED)
239239
return false # because we can assert it is feasible
240240
end
241241
primal_status = MOI.get(infeasible_model, MOI.PrimalStatus())
242242
if optimizer.verbose
243-
println("Original model primal status: $(primal_status)")
243+
println("[MathOptIIS] model primal status: $(primal_status)")
244244
end
245245
if primal_status in (MOI.FEASIBLE_POINT, MOI.NEARLY_FEASIBLE_POINT) && !(
246246
termination_status in
@@ -354,7 +354,7 @@ function _bound_infeasibility!(
354354
::Type{T},
355355
) where {T}
356356
if optimizer.verbose
357-
println("Starting bound analysis.")
357+
println("[MathOptIIS] starting bound analysis")
358358
end
359359
variable_info = Dict(
360360
x => _VariableInfo{T}() for
@@ -375,7 +375,7 @@ function _bound_infeasibility!(
375375
append!(optimizer.results, results)
376376
if optimizer.verbose
377377
println(
378-
"Complete bound analysis found $(length(results)) infeasibilities.",
378+
"[MathOptIIS] bound analysis found $(length(results)) infeasible subsets",
379379
)
380380
end
381381
return variable_info
@@ -398,7 +398,7 @@ function _range_infeasibility!(
398398
variable_info::Dict{MOI.VariableIndex,_VariableInfo{T}},
399399
) where {T}
400400
if optimizer.verbose
401-
println("Starting range analysis.")
401+
println("[MathOptIIS] starting range analysis")
402402
end
403403
variables = Dict{MOI.VariableIndex,Interval{T}}(
404404
x => Interval(_lower(T, info.lower), _upper(T, info.upper)) for
@@ -422,7 +422,7 @@ function _range_infeasibility!(
422422
append!(optimizer.results, results)
423423
if optimizer.verbose
424424
println(
425-
"Complete range analysis found $(length(results)) infeasibilities.",
425+
"[MathOptIIS] range analysis found $(length(results)) infeasible subsets",
426426
)
427427
end
428428
return
@@ -444,9 +444,6 @@ function _range_infeasibility!(
444444
func = MOI.get(infeasible_model, MOI.ConstraintFunction(), con)
445445
cons = Set{MOI.ConstraintIndex}()
446446
interval = _compute_interval(variables, func, variable_info, cons)
447-
if interval === nothing
448-
continue
449-
end
450447
set = MOI.get(infeasible_model, MOI.ConstraintSet(), con)::S
451448
if !_valid_range(set, interval)
452449
push!(cons, con)
@@ -460,18 +457,14 @@ end
460457
_supports_interval(::Type{MOI.ScalarAffineFunction{T}}) where {T} = true
461458

462459
function _compute_interval(
463-
map::AbstractDict{MOI.VariableIndex,U},
460+
variables::Dict{MOI.VariableIndex,Interval{T}},
464461
f::MOI.ScalarAffineFunction,
465462
variable_info::Dict{MOI.VariableIndex,_VariableInfo{T}},
466463
cons::Set{MOI.ConstraintIndex},
467-
) where {T,U}
468-
out = convert(U, f.constant)
464+
) where {T}
465+
out = convert(Interval{T}, f.constant)
469466
for t in f.terms
470-
v = get(map, t.variable, nothing)
471-
if v === nothing
472-
return
473-
end
474-
out += t.coefficient * v
467+
out += t.coefficient * variables[t.variable]
475468
if (s = variable_info[t.variable].lower) !== nothing
476469
push!(cons, _ci(t.variable, s))
477470
end
@@ -525,11 +518,11 @@ end
525518

526519
function _elastic_filter(optimizer::Optimizer, ::Type{T} = Float64) where {T}
527520
if optimizer.verbose
528-
println("Starting elastic filter solver.")
521+
println("[MathOptIIS] starting elastic filter")
529522
end
530523
if optimizer.inner_optimizer === nothing
531524
println(
532-
"IIS resolver cannot continue because no optimizer was provided",
525+
"[MathOptIIS] elastic filter cannot continue because no optimizer was provided",
533526
)
534527
return
535528
end
@@ -549,6 +542,11 @@ function _elastic_filter(optimizer::Optimizer, ::Type{T} = Float64) where {T}
549542
# The relaxed problem is infeasible. This is great news, because we can
550543
# continue to find an IIS from the continuous relaxation.
551544
if MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE
545+
if optimizer.verbose
546+
println(
547+
"[MathOptIIS] using INFEASIBILITY_CERTIFICATE to construct infeasible subset",
548+
)
549+
end
552550
# If there is an infeasibility certificate, the non-zero rows an IIS
553551
_iis_from_certificate(optimizer, model, new_to_old_index_map)
554552
return
@@ -657,12 +655,7 @@ function _iis_from_certificate(optimizer, model::MOI.ModelLike, index_map)
657655
end
658656
end
659657
end
660-
maybe_constraints =
661-
_get_variables_in_constraints(optimizer.infeasible_model, iis)
662-
push!(
663-
optimizer.results,
664-
InfeasibilityData(iis, true, NoData(); maybe_constraints),
665-
)
658+
push!(optimizer.results, InfeasibilityData(iis, true, NoData()))
666659
return
667660
end
668661

@@ -686,23 +679,32 @@ function _add_bound_if_necessary(model, x, lb::T, ub::T) where {T}
686679
# We don't need to consider the cases in this function when
687680
# `set ∩ [lb, ub] = ∅` because this would have been picked up in an earlier
688681
# pass.
689-
ci = MOI.ConstraintIndex{MOI.VariableIndex,MOI.GreaterThan{T}}(x.value)
682+
ci = MOI.ConstraintIndex{MOI.VariableIndex,MOI.EqualTo{T}}(x.value)
683+
if MOI.is_valid(model, ci)
684+
return # If x == value, we don't need to add bounds
685+
end
686+
ci = MOI.ConstraintIndex{MOI.VariableIndex,MOI.Interval{T}}(x.value)
690687
if MOI.is_valid(model, ci)
691688
set = MOI.get(model, MOI.ConstraintSet(), ci)
692-
new_set = MOI.GreaterThan(max(lb, set.lower))
689+
new_set = MOI.Interval(max(lb, set.lower), min(ub, set.upper))
693690
MOI.set(model, MOI.ConstraintSet(), ci, new_set)
691+
return # x now has finite bounds. We don't need to check others
694692
end
695-
ci = MOI.ConstraintIndex{MOI.VariableIndex,MOI.LessThan{T}}(x.value)
693+
ci = MOI.ConstraintIndex{MOI.VariableIndex,MOI.GreaterThan{T}}(x.value)
696694
if MOI.is_valid(model, ci)
697695
set = MOI.get(model, MOI.ConstraintSet(), ci)
698-
new_set = MOI.LessThan(min(ub, set.upper))
696+
new_set = MOI.GreaterThan(max(lb, set.lower))
699697
MOI.set(model, MOI.ConstraintSet(), ci, new_set)
698+
else
699+
MOI.add_constraint(model, x, MOI.GreaterThan(lb))
700700
end
701-
ci = MOI.ConstraintIndex{MOI.VariableIndex,MOI.Interval{T}}(x.value)
701+
ci = MOI.ConstraintIndex{MOI.VariableIndex,MOI.LessThan{T}}(x.value)
702702
if MOI.is_valid(model, ci)
703703
set = MOI.get(model, MOI.ConstraintSet(), ci)
704-
new_set = MOI.Interval(max(lb, set.lower), min(ub, set.upper))
704+
new_set = MOI.LessThan(min(ub, set.upper))
705705
MOI.set(model, MOI.ConstraintSet(), ci, new_set)
706+
else
707+
MOI.add_constraint(model, x, MOI.LessThan(ub))
706708
end
707709
return
708710
end

src/interval.jl

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,14 @@ end
1818

1919
Base.convert(::Type{Interval{T}}, x::T) where {T<:Real} = Interval(x, x)
2020

21-
Base.zero(::Type{Interval{T}}) where {T<:Real} = Interval(zero(T), zero(T))
22-
2321
Base.iszero(a::Interval) = iszero(a.hi) && iszero(a.lo)
2422

2523
function Base.:+(a::Interval{T}, b::Interval{T}) where {T<:Real}
2624
return Interval(a.lo + b.lo, a.hi + b.hi)
2725
end
2826

2927
function Base.:*(x::T, a::Interval{T}) where {T<:Real}
30-
if iszero(a) || iszero(x)
31-
return Interval(zero(T), zero(T))
32-
elseif x >= zero(T)
28+
if x >= zero(T)
3329
return Interval(a.lo * x, a.hi * x)
3430
end
3531
return Interval(a.hi * x, a.lo * x)

test/runtests.jl

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -584,22 +584,22 @@ function test_iis_spare()
584584
solver,
585585
MOI.ConstraintConflictStatus(),
586586
index(LowerBoundRef(x)),
587-
) == MOI.MAYBE_IN_CONFLICT
587+
) == MOI.NOT_IN_CONFLICT
588588
@test MOI.get(
589589
solver,
590590
MOI.ConstraintConflictStatus(),
591591
index(LowerBoundRef(y)),
592-
) == MOI.MAYBE_IN_CONFLICT
592+
) == MOI.NOT_IN_CONFLICT
593593
@test MOI.get(
594594
solver,
595595
MOI.ConstraintConflictStatus(),
596596
index(UpperBoundRef(x)),
597-
) == MOI.MAYBE_IN_CONFLICT
597+
) == MOI.NOT_IN_CONFLICT
598598
@test MOI.get(
599599
solver,
600600
MOI.ConstraintConflictStatus(),
601601
index(UpperBoundRef(y)),
602-
) == MOI.MAYBE_IN_CONFLICT
602+
) == MOI.NOT_IN_CONFLICT
603603
@test MOI.get(
604604
solver,
605605
MOI.ConstraintConflictStatus(),
@@ -619,8 +619,6 @@ function test_iis_binary()
619619
@variable(model, x, Bin)
620620
@constraint(model, c1, x == 1 / 2)
621621
optimize!(model)
622-
@show termination_status(model)
623-
@show primal_status(model)
624622
solver = MOIIS.Optimizer()
625623
MOI.set(solver, MOIIS.InfeasibleModel(), backend(model))
626624
MOI.set(solver, MOIIS.InnerOptimizer(), HiGHS.Optimizer)
@@ -640,6 +638,91 @@ function test_iis_binary()
640638
return
641639
end
642640

641+
function test_verbose()
642+
model = Model(HiGHS.Optimizer)
643+
@variable(model, 0 <= x <= 10)
644+
@variable(model, 0 <= y)
645+
@constraint(model, 2 * y <= 40)
646+
@constraint(model, c, x + y >= 35)
647+
@objective(model, Max, x + y)
648+
optimize!(model)
649+
solver = MOIIS.Optimizer()
650+
MOI.set(solver, MOI.Silent(), false)
651+
MOI.set(solver, MOIIS.InfeasibleModel(), backend(model))
652+
MOI.set(solver, MOIIS.InnerOptimizer(), HiGHS.Optimizer)
653+
dir = mktempdir()
654+
open(joinpath(dir, "log.stdout"), "w") do io
655+
return redirect_stdout(io) do
656+
return MOI.compute_conflict!(solver)
657+
end
658+
end
659+
contents = """
660+
[MathOptIIS] starting compute_conflict!
661+
[MathOptIIS] model termination status: INFEASIBLE
662+
[MathOptIIS] model primal status: NO_SOLUTION
663+
[MathOptIIS] starting bound analysis
664+
[MathOptIIS] bound analysis found 0 infeasible subsets
665+
[MathOptIIS] starting range analysis
666+
[MathOptIIS] range analysis found 0 infeasible subsets
667+
[MathOptIIS] starting elastic filter
668+
[MathOptIIS] using INFEASIBILITY_CERTIFICATE to construct infeasible subset
669+
[MathOptIIS] elastic filter found 1 infeasible subsets
670+
"""
671+
@test read(joinpath(dir, "log.stdout"), String) == contents
672+
return
673+
end
674+
675+
function _copy_conflict(model::MOI.ModelLike, solver::MOIIS.Optimizer)
676+
filter_fn(::Any) = true
677+
function filter_fn(cref::MOI.ConstraintIndex)
678+
status = MOI.get(solver, MOI.ConstraintConflictStatus(), cref)
679+
return status != MOI.NOT_IN_CONFLICT
680+
end
681+
new_model = MOI.Utilities.Model{Float64}()
682+
filtered_src = MOI.Utilities.ModelFilter(filter_fn, model)
683+
MOI.copy_to(new_model, filtered_src)
684+
MOI.set(new_model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE)
685+
return new_model
686+
end
687+
688+
function _test_compute_conflict(input, output)
689+
model = HiGHS.Optimizer()
690+
MOI.Utilities.loadfromstring!(model, input)
691+
MOI.optimize!(model)
692+
solver = MOIIS.Optimizer()
693+
MOI.set(solver, MOI.Silent(), false)
694+
MOI.set(solver, MOIIS.InfeasibleModel(), model)
695+
MOI.set(solver, MOIIS.InnerOptimizer(), HiGHS.Optimizer)
696+
MOI.compute_conflict!(solver)
697+
iis = _copy_conflict(model, solver)
698+
target = MOI.Utilities.Model{Float64}()
699+
MOI.Utilities.loadfromstring!(target, output)
700+
@test print(iis) == print(target)
701+
return
702+
end
703+
704+
function test_relax_integrality_integer()
705+
_test_compute_conflict(
706+
"""
707+
variables: x, y
708+
maxobjective: 1.0 * x + y
709+
2.0 * y <= 40.0
710+
1.0 * x + y >= 35.0
711+
x >= 0.0
712+
x <= 10.0
713+
x in Integer()
714+
y >= 0.0
715+
""",
716+
"""
717+
variables: x, y
718+
2.0 * y <= 40.0
719+
1.0 * x + y >= 35.0
720+
x <= 10.0
721+
""",
722+
)
723+
return
724+
end
725+
643726
end # module
644727

645728
TestMathOptIIS.runtests()

0 commit comments

Comments
 (0)