Skip to content

Commit 2e39b0c

Browse files
committed
MOI IIS
1 parent 38ae419 commit 2e39b0c

File tree

2 files changed

+91
-73
lines changed

2 files changed

+91
-73
lines changed

src/infeasibility.jl

Lines changed: 89 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -364,137 +364,157 @@ function ModelAnalyzer.analyze(
364364
println("iis resolver cannot continue because no optimizer is provided")
365365
return out
366366
end
367-
# iis = iis_elastic_filter(model, optimizer)
368-
# # for now, only one iis is computed
369-
# if iis !== nothing
370-
# push!(out.iis, IrreducibleInfeasibleSubset(iis))
371-
# end
367+
iis = iis_elastic_filter(model, optimizer)
368+
# for now, only one iis is computed
369+
if iis !== nothing
370+
push!(out.iis, IrreducibleInfeasibleSubset(iis))
371+
end
372372

373373
return out
374374
end
375375

376-
#=
376+
function _fix_to_zero(model, variable::MOI.VariableIndex, ::Type{T}) where {T}
377+
ub_idx =
378+
MOI.ConstraintIndex{MOI.VariableIndex,MOI.LessThan{T}}(variable.value)
379+
lb_idx = MOI.ConstraintIndex{MOI.VariableIndex,MOI.GreaterThan{T}}(
380+
variable.value,
381+
)
382+
has_lower = false
383+
if MOI.is_valid(model, lb_idx)
384+
MOI.delete(model, lb_idx)
385+
has_lower = true
386+
elseif MOI.is_valid(model, ub_idx)
387+
MOI.delete(model, ub_idx)
388+
else
389+
error("Variable is not bounded")
390+
end
391+
MOI.add_constraint(model, variable, MOI.EqualTo{T}(zero(T)))
392+
return has_lower
393+
end
377394

378-
function iis_elastic_filter(original_model::JuMP.GenericModel, optimizer)
395+
function _set_bound_zero(
396+
model,
397+
variable::MOI.VariableIndex,
398+
has_lower::Bool,
399+
::Type{T},
400+
) where {T}
401+
eq_idx =
402+
MOI.ConstraintIndex{MOI.VariableIndex,MOI.EqualTo{T}}(variable.value)
403+
@assert MOI.is_valid(model, eq_idx)
404+
MOI.delete(model, eq_idx)
405+
if has_lower
406+
MOI.add_constraint(model, variable, MOI.GreaterThan{T}(zero(T)))
407+
else
408+
MOI.add_constraint(model, variable, MOI.LessThan{T}(zero(T)))
409+
end
410+
return
411+
end
379412

380-
# if JuMP.termination_status(original_model) == MOI.OPTIMIZE_NOT_CALLED
381-
# println("iis resolver cannot continue because model is not optimized")
382-
# # JuMP.optimize!(original_model)
383-
# end
413+
function iis_elastic_filter(original_model::MOI.ModelLike, optimizer)
414+
T = Float64
384415

385-
status = JuMP.termination_status(original_model)
416+
# handle optimize not called
417+
status = MOI.get(original_model, MOI.TerminationStatus())
386418
if status != MOI.INFEASIBLE
387419
println(
388420
"iis resolver cannot continue because model is found to be $(status) by the solver",
389421
)
390422
return nothing
391423
end
392424

393-
model, reference_map = JuMP.copy_model(original_model)
394-
JuMP.set_optimizer(model, optimizer)
395-
JuMP.set_silent(model)
396-
# TODO handle ".ext" to avoid warning
425+
model = MOI.instantiate(optimizer)
426+
reference_map = MOI.copy_to(model, original_model)
427+
MOI.set(model, MOI.Silent(), true)
397428

398-
constraint_to_affine = JuMP.relax_with_penalty!(model, default = 1.0)
429+
constraint_to_affine =
430+
MOI.modify(model, MOI.Utilities.PenaltyRelaxation(default = 1.0))
399431
# might need to do something related to integers / binary
400432

401-
JuMP.optimize!(model)
402-
403433
max_iterations = length(constraint_to_affine)
404434

405435
tolerance = 1e-5
406436

407437
de_elastisized = []
408438

409439
for i in 1:max_iterations
410-
if JuMP.termination_status(model) == MOI.INFEASIBLE
440+
MOI.optimize!(model)
441+
status = MOI.get(model, MOI.TerminationStatus())
442+
if status == MOI.INFEASIBLE
411443
break
412444
end
413445
for (con, func) in constraint_to_affine
414446
if length(func.terms) == 1
415-
var = collect(keys(func.terms))[1]
416-
if JuMP.value(var) > tolerance
417-
has_lower = JuMP.has_lower_bound(var)
418-
JuMP.fix(var, 0.0; force = true)
419-
# or delete(model, var)
447+
var = func.terms[1].variable
448+
value = MOI.get(model, MOI.VariablePrimal(), var)
449+
if value > tolerance
450+
has_lower = _fix_to_zero(model, var, T)
420451
delete!(constraint_to_affine, con)
421452
push!(de_elastisized, (con, var, has_lower))
422453
end
423454
elseif length(func.terms) == 2
424-
var = collect(keys(func.terms))
425-
coef1 = func.terms[var[1]]
426-
coef2 = func.terms[var[2]]
427-
if JuMP.value(var[1]) > tolerance &&
428-
JuMP.value(var[2]) > tolerance
455+
var1 = func.terms[1].variable
456+
coef1 = func.terms[1].coefficient
457+
var2 = func.terms[2].variable
458+
coef2 = func.terms[2].coefficient
459+
value1 = MOI.get(model, MOI.VariablePrimal(), var1)
460+
value2 = MOI.get(model, MOI.VariablePrimal(), var2)
461+
if value1 > tolerance && value2 > tolerance
429462
error("IIS failed due numerical instability")
430-
elseif JuMP.value(var[1]) > tolerance
431-
has_lower = JuMP.has_lower_bound(var[1])
432-
JuMP.fix(var[1], 0.0; force = true)
433-
# or delete(model, var[1])
463+
elseif value1 > tolerance
464+
has_lower = _fix_to_zero(model, var1, T)
434465
delete!(constraint_to_affine, con)
435-
constraint_to_affine[con] = coef2 * var[2]
436-
push!(de_elastisized, (con, var[1], has_lower))
437-
elseif JuMP.value(var[2]) > tolerance
438-
has_lower = JuMP.has_lower_bound(var[2])
439-
JuMP.fix(var[2], 0.0; force = true)
440-
# or delete(model, var[2])
466+
constraint_to_affine[con] = coef2 * var2
467+
push!(de_elastisized, (con, var1, has_lower))
468+
elseif value2 > tolerance
469+
has_lower = _fix_to_zero(model, var2, T)
441470
delete!(constraint_to_affine, con)
442-
constraint_to_affine[con] = coef1 * var[1]
443-
push!(de_elastisized, (con, var[2], has_lower))
471+
constraint_to_affine[con] = coef1 * var1
472+
push!(de_elastisized, (con, var2, has_lower))
444473
end
445474
else
446475
println(
447476
"$con and relaxing function with more than two terms: $func",
448477
)
449478
end
450-
JuMP.optimize!(model)
451479
end
452480
end
453481

454482
# consider deleting all no iis constraints
455483
# be careful with intervals
456484

457485
# deletion filter
458-
cadidates = JuMP.ConstraintRef[]
486+
cadidates = MOI.ConstraintIndex[]
459487
for (con, var, has_lower) in de_elastisized
460-
JuMP.unfix(var)
461-
if has_lower
462-
JuMP.set_lower_bound(var, 0.0)
463-
else
464-
JuMP.set_upper_bound(var, 0.0)
465-
end
466-
JuMP.optimize!(model)
467-
if JuMP.termination_status(model) in
468-
(MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE)
488+
_set_bound_zero(model, var, has_lower, T)
489+
MOI.optimize!(model)
490+
status = MOI.get(model, MOI.TerminationStatus())
491+
if status in (MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE)
469492
# this constraint is not in IIS
470-
elseif JuMP.termination_status(model) in
471-
(MOI.OPTIMAL, MOI.ALMOST_OPTIMAL)
493+
elseif status in (MOI.OPTIMAL, MOI.ALMOST_OPTIMAL)
472494
push!(cadidates, con)
473-
JuMP.fix(var, 0.0, force = true)
495+
_fix_to_zero(model, var, T)
474496
else
475-
error(
476-
"IIS failed due numerical instability, got status $(JuMP.termination_status(model))",
477-
)
497+
error("IIS failed due numerical instability, got status $status")
478498
end
479499
end
480500

481501
pre_iis = Set(cadidates)
482-
iis = JuMP.ConstraintRef[]
483-
for con in JuMP.all_constraints(
484-
original_model,
485-
include_variable_in_set_constraints = false,
486-
)
487-
new_con = reference_map[con]
488-
if new_con in pre_iis
489-
push!(iis, con)
502+
iis = MOI.ConstraintIndex[]
503+
for (F, S) in MOI.get(original_model, MOI.ListOfConstraintTypesPresent())
504+
if F == MOI.VariableIndex
505+
continue
506+
end
507+
for con in MOI.get(original_model, MOI.ListOfConstraintIndices{F,S}())
508+
new_con = reference_map[con]
509+
if new_con in pre_iis
510+
push!(iis, con)
511+
end
490512
end
491513
end
492514

493515
return iis
494516
end
495517

496-
=#
497-
498518
# API
499519

500520
function ModelAnalyzer._summarize(io::IO, ::Type{InfeasibleBounds{T}}) where {T}

test/infeasibility.jl

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,6 @@ function test_range_equalto_3()
285285
return
286286
end
287287

288-
#=
289288

290289
function test_iis_feasible()
291290
model = Model(HiGHS.Optimizer)
@@ -363,6 +362,7 @@ function test_iis()
363362
return
364363
end
365364

365+
366366
function test_iis_multiple()
367367
model = Model(HiGHS.Optimizer)
368368
set_silent(model)
@@ -386,7 +386,7 @@ function test_iis_multiple()
386386
ret = ModelAnalyzer.list_of_issues(data, list[1])
387387
@test length(ret) == 1
388388
@test length(ret[].constraint) == 2
389-
@test c2 in Set([ret[].constraint[1], ret[].constraint[2]])
389+
@test JuMP.index(c2) in Set([ret[].constraint[1], ret[].constraint[2]])
390390
@test Set([ret[].constraint[1], ret[].constraint[2]]) Set(JuMP.index.([c3, c2, c1]))
391391
return
392392
end
@@ -472,8 +472,6 @@ function test_iis_spare()
472472
return
473473
end
474474

475-
=#
476-
477475
end # module
478476

479477
TestInfeasibilityChecker.runtests()

0 commit comments

Comments
 (0)