From e6e906da0b7e1c44c67f62f481c9b69538bf7e62 Mon Sep 17 00:00:00 2001 From: joaquimg Date: Tue, 27 May 2025 20:38:08 -0700 Subject: [PATCH 1/6] Fix print of Infeasibility analysis --- src/infeasibility.jl | 50 +++++++++++++++++++++---------------------- test/infeasibility.jl | 11 ++++++++++ 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/infeasibility.jl b/src/infeasibility.jl index 2dab25d..ba3eba3 100644 --- a/src/infeasibility.jl +++ b/src/infeasibility.jl @@ -541,32 +541,32 @@ end # API -function ModelAnalyzer._summarize(io::IO, ::Type{InfeasibleBounds{T}}) where {T} +function ModelAnalyzer._summarize(io::IO, ::Type{<:InfeasibleBounds}) return print(io, "# InfeasibleBounds") end function ModelAnalyzer._summarize( io::IO, - ::Type{InfeasibleIntegrality{T}}, -) where {T} + ::Type{<:InfeasibleIntegrality}, +) return print(io, "# InfeasibleIntegrality") end function ModelAnalyzer._summarize( io::IO, - ::Type{InfeasibleConstraintRange{T}}, -) where {T} + ::Type{<:InfeasibleConstraintRange}, +) return print(io, "# InfeasibleConstraintRange") end -function ModelAnalyzer._summarize(io::IO, ::Type{IrreducibleInfeasibleSubset}) +function ModelAnalyzer._summarize(io::IO, ::Type{<:IrreducibleInfeasibleSubset}) return print(io, "# IrreducibleInfeasibleSubset") end function ModelAnalyzer._verbose_summarize( io::IO, - ::Type{InfeasibleBounds{T}}, -) where {T} + ::Type{<:InfeasibleBounds}, +) return print( io, """ @@ -595,8 +595,8 @@ end function ModelAnalyzer._verbose_summarize( io::IO, - ::Type{InfeasibleIntegrality{T}}, -) where {T} + ::Type{<:InfeasibleIntegrality}, +) return print( io, """ @@ -626,8 +626,8 @@ end function ModelAnalyzer._verbose_summarize( io::IO, - ::Type{InfeasibleConstraintRange{T}}, -) where {T} + ::Type{<:InfeasibleConstraintRange}, +) return print( io, """ @@ -657,7 +657,7 @@ end function ModelAnalyzer._verbose_summarize( io::IO, - ::Type{IrreducibleInfeasibleSubset}, + ::Type{<:IrreducibleInfeasibleSubset}, ) return print( io, @@ -687,9 +687,9 @@ end function ModelAnalyzer._summarize( io::IO, - issue::InfeasibleBounds{T}, + issue::InfeasibleBounds, model, -) where {T} +) return print( io, ModelAnalyzer._name(issue.variable, model), @@ -702,9 +702,9 @@ end function ModelAnalyzer._summarize( io::IO, - issue::InfeasibleIntegrality{T}, + issue::InfeasibleIntegrality, model, -) where {T} +) return print( io, ModelAnalyzer._name(issue.variable, model), @@ -719,9 +719,9 @@ end function ModelAnalyzer._summarize( io::IO, - issue::InfeasibleConstraintRange{T}, + issue::InfeasibleConstraintRange, model, -) where {T} +) return print( io, ModelAnalyzer._name(issue.constraint, model), @@ -748,9 +748,9 @@ end function ModelAnalyzer._verbose_summarize( io::IO, - issue::InfeasibleBounds{T}, + issue::InfeasibleBounds, model, -) where {T} +) return print( io, "Variable: ", @@ -764,9 +764,9 @@ end function ModelAnalyzer._verbose_summarize( io::IO, - issue::InfeasibleIntegrality{T}, + issue::InfeasibleIntegrality, model, -) where {T} +) return print( io, "Variable: ", @@ -782,9 +782,9 @@ end function ModelAnalyzer._verbose_summarize( io::IO, - issue::InfeasibleConstraintRange{T}, + issue::InfeasibleConstraintRange, model, -) where {T} +) return print( io, "Constraint: ", diff --git a/test/infeasibility.jl b/test/infeasibility.jl index cde2582..4d5b7c0 100644 --- a/test/infeasibility.jl +++ b/test/infeasibility.jl @@ -64,6 +64,8 @@ function test_bounds() str = String(take!(buf)) @test contains(str, " : ") @test contains(str, " !<= ") + ModelAnalyzer.summarize(buf, data, verbose = false) + ModelAnalyzer.summarize(buf, data, verbose = true) return end @@ -114,6 +116,8 @@ function test_integrality() @test contains(str, " : [") @test contains(str, "; ") @test contains(str, "], ") + ModelAnalyzer.summarize(buf, data, verbose = false) + ModelAnalyzer.summarize(buf, data, verbose = true) return end @@ -137,6 +141,9 @@ function test_binary() @test ModelAnalyzer.variable(ret[], model) == x @test ModelAnalyzer.values(ret[]) == [0.5, 0.8] @test ModelAnalyzer.set(ret[]) == MOI.ZeroOne() + buf = IOBuffer() + ModelAnalyzer.summarize(buf, data, verbose = false) + ModelAnalyzer.summarize(buf, data, verbose = true) return end @@ -188,6 +195,8 @@ function test_range() @test contains(str, " : [") @test contains(str, "; ") @test contains(str, "], !in ") + ModelAnalyzer.summarize(buf, data, verbose = false) + ModelAnalyzer.summarize(buf, data, verbose = true) return end @@ -405,6 +414,8 @@ function test_iis() ModelAnalyzer.summarize(buf, data, verbose = true) str = String(take!(buf)) @test startswith(str, "## Infeasibility Analysis\n\n") + ModelAnalyzer.summarize(buf, data, verbose = false) + ModelAnalyzer.summarize(buf, data, verbose = true) return end From 242737c83f5aea6fd747ba74357844846b1408b9 Mon Sep 17 00:00:00 2001 From: joaquimg Date: Tue, 27 May 2025 20:45:28 -0700 Subject: [PATCH 2/6] format --- src/infeasibility.jl | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/src/infeasibility.jl b/src/infeasibility.jl index ba3eba3..dcb13ad 100644 --- a/src/infeasibility.jl +++ b/src/infeasibility.jl @@ -545,17 +545,11 @@ function ModelAnalyzer._summarize(io::IO, ::Type{<:InfeasibleBounds}) return print(io, "# InfeasibleBounds") end -function ModelAnalyzer._summarize( - io::IO, - ::Type{<:InfeasibleIntegrality}, -) +function ModelAnalyzer._summarize(io::IO, ::Type{<:InfeasibleIntegrality}) return print(io, "# InfeasibleIntegrality") end -function ModelAnalyzer._summarize( - io::IO, - ::Type{<:InfeasibleConstraintRange}, -) +function ModelAnalyzer._summarize(io::IO, ::Type{<:InfeasibleConstraintRange}) return print(io, "# InfeasibleConstraintRange") end @@ -563,10 +557,7 @@ function ModelAnalyzer._summarize(io::IO, ::Type{<:IrreducibleInfeasibleSubset}) return print(io, "# IrreducibleInfeasibleSubset") end -function ModelAnalyzer._verbose_summarize( - io::IO, - ::Type{<:InfeasibleBounds}, -) +function ModelAnalyzer._verbose_summarize(io::IO, ::Type{<:InfeasibleBounds}) return print( io, """ @@ -685,11 +676,7 @@ function ModelAnalyzer._verbose_summarize( ) end -function ModelAnalyzer._summarize( - io::IO, - issue::InfeasibleBounds, - model, -) +function ModelAnalyzer._summarize(io::IO, issue::InfeasibleBounds, model) return print( io, ModelAnalyzer._name(issue.variable, model), @@ -700,11 +687,7 @@ function ModelAnalyzer._summarize( ) end -function ModelAnalyzer._summarize( - io::IO, - issue::InfeasibleIntegrality, - model, -) +function ModelAnalyzer._summarize(io::IO, issue::InfeasibleIntegrality, model) return print( io, ModelAnalyzer._name(issue.variable, model), From d7bb58de3a67d6bdc29c5888957b2be5e14f9ef0 Mon Sep 17 00:00:00 2001 From: joaquimg Date: Tue, 27 May 2025 21:54:18 -0700 Subject: [PATCH 3/6] Handle unboundedness in Infeasibility analysis --- src/infeasibility.jl | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/infeasibility.jl b/src/infeasibility.jl index dcb13ad..f4bd47b 100644 --- a/src/infeasibility.jl +++ b/src/infeasibility.jl @@ -438,7 +438,7 @@ function iis_elastic_filter(original_model::MOI.ModelLike, optimizer) # handle optimize not called status = MOI.get(original_model, MOI.TerminationStatus()) - if status != MOI.INFEASIBLE + if !(status in (MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE, MOI.ALMOST_INFEASIBLE)) println( "iis resolver cannot continue because model is found to be $(status) by the solver", ) @@ -462,7 +462,7 @@ function iis_elastic_filter(original_model::MOI.ModelLike, optimizer) for i in 1:max_iterations MOI.optimize!(model) status = MOI.get(model, MOI.TerminationStatus()) - if status == MOI.INFEASIBLE + if status in (MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE, MOI.ALMOST_INFEASIBLE) break end for (con, func) in constraint_to_affine @@ -506,17 +506,46 @@ function iis_elastic_filter(original_model::MOI.ModelLike, optimizer) # consider deleting all no iis constraints # be careful with intervals + obj_type = MOI.get(model, MOI.ObjectiveFunctionType()) + obj_func = MOI.get(model, MOI.ObjectiveFunction{obj_type}()) + obj_sense = MOI.get(model, MOI.ObjectiveSense()) + # deletion filter cadidates = MOI.ConstraintIndex[] for (con, var, has_lower) in de_elastisized _set_bound_zero(model, var, has_lower, T) MOI.optimize!(model) status = MOI.get(model, MOI.TerminationStatus()) - if status in (MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE) + if status in (MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE, MOI.ALMOST_INFEASIBLE) # this constraint is not in IIS - elseif status in (MOI.OPTIMAL, MOI.ALMOST_OPTIMAL) + elseif status in ( + MOI.OPTIMAL, MOI.ALMOST_OPTIMAL, MOI.LOCALLY_SOLVED, MOI.ALMOST_LOCALLY_SOLVED, + ) push!(cadidates, con) _fix_to_zero(model, var, T) + elseif status in ( + MOI.INFEASIBLE_OR_UNBOUNDED, + MOI.DUAL_INFEASIBLE, MOI.ALMOST_DUAL_INFEASIBLE, # possibily primal unbounded + ) + MOI.set(model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE) + MOI.optimize!(model) + primal_status = MOI.get(model, MOI.PrimalStatus()) + if primal_status in (MOI.FEASIBLE_POINT, MOI.NEARLY_FEASIBLE_POINT) + # this constraint is not in IIS + push!(cadidates, con) + _fix_to_zero(model, var, T) + MOI.set(model, MOI.ObjectiveSense(), obj_sense) + MOI.set( + model, + MOI.ObjectiveFunction{obj_type}(), + obj_func, + ) + else + error( + "IIS failed due numerical instability, got status $status,", + "then, for MOI.FEASIBILITY_SENSE objective, got primal status $primal_status", + ) + end else error("IIS failed due numerical instability, got status $status") end From a3913725b339668d556c9dc4ebd20ffd96f2f3b3 Mon Sep 17 00:00:00 2001 From: joaquimg Date: Tue, 27 May 2025 21:56:52 -0700 Subject: [PATCH 4/6] format --- src/infeasibility.jl | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/infeasibility.jl b/src/infeasibility.jl index f4bd47b..97d5f3f 100644 --- a/src/infeasibility.jl +++ b/src/infeasibility.jl @@ -438,7 +438,10 @@ function iis_elastic_filter(original_model::MOI.ModelLike, optimizer) # handle optimize not called status = MOI.get(original_model, MOI.TerminationStatus()) - if !(status in (MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE, MOI.ALMOST_INFEASIBLE)) + if !( + status in + (MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE, MOI.ALMOST_INFEASIBLE) + ) println( "iis resolver cannot continue because model is found to be $(status) by the solver", ) @@ -462,7 +465,8 @@ function iis_elastic_filter(original_model::MOI.ModelLike, optimizer) for i in 1:max_iterations MOI.optimize!(model) status = MOI.get(model, MOI.TerminationStatus()) - if status in (MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE, MOI.ALMOST_INFEASIBLE) + if status in + (MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE, MOI.ALMOST_INFEASIBLE) break end for (con, func) in constraint_to_affine @@ -516,17 +520,22 @@ function iis_elastic_filter(original_model::MOI.ModelLike, optimizer) _set_bound_zero(model, var, has_lower, T) MOI.optimize!(model) status = MOI.get(model, MOI.TerminationStatus()) - if status in (MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE, MOI.ALMOST_INFEASIBLE) + if status in + (MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE, MOI.ALMOST_INFEASIBLE) # this constraint is not in IIS elseif status in ( - MOI.OPTIMAL, MOI.ALMOST_OPTIMAL, MOI.LOCALLY_SOLVED, MOI.ALMOST_LOCALLY_SOLVED, - ) + MOI.OPTIMAL, + MOI.ALMOST_OPTIMAL, + MOI.LOCALLY_SOLVED, + MOI.ALMOST_LOCALLY_SOLVED, + ) push!(cadidates, con) _fix_to_zero(model, var, T) elseif status in ( MOI.INFEASIBLE_OR_UNBOUNDED, - MOI.DUAL_INFEASIBLE, MOI.ALMOST_DUAL_INFEASIBLE, # possibily primal unbounded - ) + MOI.DUAL_INFEASIBLE, + MOI.ALMOST_DUAL_INFEASIBLE, # possibily primal unbounded + ) MOI.set(model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE) MOI.optimize!(model) primal_status = MOI.get(model, MOI.PrimalStatus()) @@ -535,11 +544,7 @@ function iis_elastic_filter(original_model::MOI.ModelLike, optimizer) push!(cadidates, con) _fix_to_zero(model, var, T) MOI.set(model, MOI.ObjectiveSense(), obj_sense) - MOI.set( - model, - MOI.ObjectiveFunction{obj_type}(), - obj_func, - ) + MOI.set(model, MOI.ObjectiveFunction{obj_type}(), obj_func) else error( "IIS failed due numerical instability, got status $status,", From 5cae94e2ae2a09c08e79b11f033ce8c89b7c21df Mon Sep 17 00:00:00 2001 From: joaquimg Date: Tue, 27 May 2025 22:46:49 -0700 Subject: [PATCH 5/6] tweaks and tests --- src/infeasibility.jl | 24 ++++++++++++++++++++++++ test/infeasibility.jl | 27 +++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/infeasibility.jl b/src/infeasibility.jl index 97d5f3f..9ecc6f8 100644 --- a/src/infeasibility.jl +++ b/src/infeasibility.jl @@ -452,9 +452,17 @@ function iis_elastic_filter(original_model::MOI.ModelLike, optimizer) reference_map = MOI.copy_to(model, original_model) MOI.set(model, MOI.Silent(), true) + obj_sense = MOI.get(model, MOI.ObjectiveSense()) + base_obj_type = MOI.get(model, MOI.ObjectiveFunctionType()) + base_obj_func = MOI.get(model, MOI.ObjectiveFunction{base_obj_type}()) + constraint_to_affine = MOI.modify(model, MOI.Utilities.PenaltyRelaxation(default = 1.0)) # might need to do something related to integers / binary + relaxed_obj_type = MOI.get(model, MOI.ObjectiveFunctionType()) + relaxed_obj_func = MOI.get(model, MOI.ObjectiveFunction{relaxed_obj_type}()) + + pure_relaxed_obj_func = relaxed_obj_func - base_obj_func max_iterations = length(constraint_to_affine) @@ -462,9 +470,21 @@ function iis_elastic_filter(original_model::MOI.ModelLike, optimizer) de_elastisized = [] + changed_obj = false + for i in 1:max_iterations MOI.optimize!(model) status = MOI.get(model, MOI.TerminationStatus()) + if status in ( # possibily primal unbounded + MOI.INFEASIBLE_OR_UNBOUNDED, + MOI.DUAL_INFEASIBLE, + MOI.ALMOST_DUAL_INFEASIBLE, + ) + #try with a pure relaxation objective + MOI.set(model, MOI.ObjectiveFunction{relaxed_obj_type}(), pure_relaxed_obj_func) + changed_obj = true + MOI.optimize!(model) + end if status in (MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE, MOI.ALMOST_INFEASIBLE) break @@ -507,6 +527,10 @@ function iis_elastic_filter(original_model::MOI.ModelLike, optimizer) end end + if changed_obj + MOI.set(model, MOI.ObjectiveFunction{relaxed_obj_type}(), relaxed_obj_func) + end + # consider deleting all no iis constraints # be careful with intervals diff --git a/test/infeasibility.jl b/test/infeasibility.jl index 4d5b7c0..d9d7dee 100644 --- a/test/infeasibility.jl +++ b/test/infeasibility.jl @@ -419,6 +419,33 @@ function test_iis() return end +function test_iis_free_var() + model = Model(HiGHS.Optimizer) + set_silent(model) + @variable(model, x) + @variable(model, y) + @constraint(model, c1, x + y <= 1) + @constraint(model, c2, x + y >= 2) + @objective(model, Max, -2x + y) + optimize!(model) + data = ModelAnalyzer.analyze( + ModelAnalyzer.Infeasibility.Analyzer(), + model, + optimizer = HiGHS.Optimizer, + ) + list = ModelAnalyzer.list_of_issue_types(data) + @test length(list) == 1 + ret = ModelAnalyzer.list_of_issues(data, list[1]) + @test length(ret) == 1 + @test length(ret[].constraint) == 2 + @test Set([ret[].constraint[1], ret[].constraint[2]]) == + Set(JuMP.index.([c2, c1])) + iis = ModelAnalyzer.constraints(ret[], model) + @test length(iis) == 2 + @test Set(iis) == Set([c2, c1]) + return +end + function test_iis_multiple() model = Model(HiGHS.Optimizer) set_silent(model) From 5cc6dd354a62f578c64cd1f97eef10a208ba1d53 Mon Sep 17 00:00:00 2001 From: joaquimg Date: Tue, 27 May 2025 22:47:50 -0700 Subject: [PATCH 6/6] format --- src/infeasibility.jl | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/infeasibility.jl b/src/infeasibility.jl index 9ecc6f8..a93edf6 100644 --- a/src/infeasibility.jl +++ b/src/infeasibility.jl @@ -481,7 +481,11 @@ function iis_elastic_filter(original_model::MOI.ModelLike, optimizer) MOI.ALMOST_DUAL_INFEASIBLE, ) #try with a pure relaxation objective - MOI.set(model, MOI.ObjectiveFunction{relaxed_obj_type}(), pure_relaxed_obj_func) + MOI.set( + model, + MOI.ObjectiveFunction{relaxed_obj_type}(), + pure_relaxed_obj_func, + ) changed_obj = true MOI.optimize!(model) end @@ -528,7 +532,11 @@ function iis_elastic_filter(original_model::MOI.ModelLike, optimizer) end if changed_obj - MOI.set(model, MOI.ObjectiveFunction{relaxed_obj_type}(), relaxed_obj_func) + MOI.set( + model, + MOI.ObjectiveFunction{relaxed_obj_type}(), + relaxed_obj_func, + ) end # consider deleting all no iis constraints