Skip to content

Commit a3d2a5a

Browse files
authored
Handle unboundedness in Infeasibility analysis (#37)
* Fix print of Infeasibility analysis * format * Handle unboundedness in Infeasibility analysis * format * tweaks and tests * format
1 parent 9e71fb5 commit a3d2a5a

File tree

2 files changed

+97
-4
lines changed

2 files changed

+97
-4
lines changed

src/infeasibility.jl

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,10 @@ function iis_elastic_filter(original_model::MOI.ModelLike, optimizer)
438438

439439
# handle optimize not called
440440
status = MOI.get(original_model, MOI.TerminationStatus())
441-
if status != MOI.INFEASIBLE
441+
if !(
442+
status in
443+
(MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE, MOI.ALMOST_INFEASIBLE)
444+
)
442445
println(
443446
"iis resolver cannot continue because model is found to be $(status) by the solver",
444447
)
@@ -449,20 +452,45 @@ function iis_elastic_filter(original_model::MOI.ModelLike, optimizer)
449452
reference_map = MOI.copy_to(model, original_model)
450453
MOI.set(model, MOI.Silent(), true)
451454

455+
obj_sense = MOI.get(model, MOI.ObjectiveSense())
456+
base_obj_type = MOI.get(model, MOI.ObjectiveFunctionType())
457+
base_obj_func = MOI.get(model, MOI.ObjectiveFunction{base_obj_type}())
458+
452459
constraint_to_affine =
453460
MOI.modify(model, MOI.Utilities.PenaltyRelaxation(default = 1.0))
454461
# might need to do something related to integers / binary
462+
relaxed_obj_type = MOI.get(model, MOI.ObjectiveFunctionType())
463+
relaxed_obj_func = MOI.get(model, MOI.ObjectiveFunction{relaxed_obj_type}())
464+
465+
pure_relaxed_obj_func = relaxed_obj_func - base_obj_func
455466

456467
max_iterations = length(constraint_to_affine)
457468

458469
tolerance = 1e-5
459470

460471
de_elastisized = []
461472

473+
changed_obj = false
474+
462475
for i in 1:max_iterations
463476
MOI.optimize!(model)
464477
status = MOI.get(model, MOI.TerminationStatus())
465-
if status == MOI.INFEASIBLE
478+
if status in ( # possibily primal unbounded
479+
MOI.INFEASIBLE_OR_UNBOUNDED,
480+
MOI.DUAL_INFEASIBLE,
481+
MOI.ALMOST_DUAL_INFEASIBLE,
482+
)
483+
#try with a pure relaxation objective
484+
MOI.set(
485+
model,
486+
MOI.ObjectiveFunction{relaxed_obj_type}(),
487+
pure_relaxed_obj_func,
488+
)
489+
changed_obj = true
490+
MOI.optimize!(model)
491+
end
492+
if status in
493+
(MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE, MOI.ALMOST_INFEASIBLE)
466494
break
467495
end
468496
for (con, func) in constraint_to_affine
@@ -503,20 +531,58 @@ function iis_elastic_filter(original_model::MOI.ModelLike, optimizer)
503531
end
504532
end
505533

534+
if changed_obj
535+
MOI.set(
536+
model,
537+
MOI.ObjectiveFunction{relaxed_obj_type}(),
538+
relaxed_obj_func,
539+
)
540+
end
541+
506542
# consider deleting all no iis constraints
507543
# be careful with intervals
508544

545+
obj_type = MOI.get(model, MOI.ObjectiveFunctionType())
546+
obj_func = MOI.get(model, MOI.ObjectiveFunction{obj_type}())
547+
obj_sense = MOI.get(model, MOI.ObjectiveSense())
548+
509549
# deletion filter
510550
cadidates = MOI.ConstraintIndex[]
511551
for (con, var, has_lower) in de_elastisized
512552
_set_bound_zero(model, var, has_lower, T)
513553
MOI.optimize!(model)
514554
status = MOI.get(model, MOI.TerminationStatus())
515-
if status in (MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE)
555+
if status in
556+
(MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE, MOI.ALMOST_INFEASIBLE)
516557
# this constraint is not in IIS
517-
elseif status in (MOI.OPTIMAL, MOI.ALMOST_OPTIMAL)
558+
elseif status in (
559+
MOI.OPTIMAL,
560+
MOI.ALMOST_OPTIMAL,
561+
MOI.LOCALLY_SOLVED,
562+
MOI.ALMOST_LOCALLY_SOLVED,
563+
)
518564
push!(cadidates, con)
519565
_fix_to_zero(model, var, T)
566+
elseif status in (
567+
MOI.INFEASIBLE_OR_UNBOUNDED,
568+
MOI.DUAL_INFEASIBLE,
569+
MOI.ALMOST_DUAL_INFEASIBLE, # possibily primal unbounded
570+
)
571+
MOI.set(model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE)
572+
MOI.optimize!(model)
573+
primal_status = MOI.get(model, MOI.PrimalStatus())
574+
if primal_status in (MOI.FEASIBLE_POINT, MOI.NEARLY_FEASIBLE_POINT)
575+
# this constraint is not in IIS
576+
push!(cadidates, con)
577+
_fix_to_zero(model, var, T)
578+
MOI.set(model, MOI.ObjectiveSense(), obj_sense)
579+
MOI.set(model, MOI.ObjectiveFunction{obj_type}(), obj_func)
580+
else
581+
error(
582+
"IIS failed due numerical instability, got status $status,",
583+
"then, for MOI.FEASIBILITY_SENSE objective, got primal status $primal_status",
584+
)
585+
end
520586
else
521587
error("IIS failed due numerical instability, got status $status")
522588
end

test/infeasibility.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,33 @@ function test_iis()
419419
return
420420
end
421421

422+
function test_iis_free_var()
423+
model = Model(HiGHS.Optimizer)
424+
set_silent(model)
425+
@variable(model, x)
426+
@variable(model, y)
427+
@constraint(model, c1, x + y <= 1)
428+
@constraint(model, c2, x + y >= 2)
429+
@objective(model, Max, -2x + y)
430+
optimize!(model)
431+
data = ModelAnalyzer.analyze(
432+
ModelAnalyzer.Infeasibility.Analyzer(),
433+
model,
434+
optimizer = HiGHS.Optimizer,
435+
)
436+
list = ModelAnalyzer.list_of_issue_types(data)
437+
@test length(list) == 1
438+
ret = ModelAnalyzer.list_of_issues(data, list[1])
439+
@test length(ret) == 1
440+
@test length(ret[].constraint) == 2
441+
@test Set([ret[].constraint[1], ret[].constraint[2]]) ==
442+
Set(JuMP.index.([c2, c1]))
443+
iis = ModelAnalyzer.constraints(ret[], model)
444+
@test length(iis) == 2
445+
@test Set(iis) == Set([c2, c1])
446+
return
447+
end
448+
422449
function test_iis_multiple()
423450
model = Model(HiGHS.Optimizer)
424451
set_silent(model)

0 commit comments

Comments
 (0)