From 6fd70718b1c3ce50d121ad88c785c88235be06cf Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 10 Sep 2025 11:04:32 +1200 Subject: [PATCH 1/4] [Bridges] add support for querying ConstraintConflictStatus --- src/Bridges/bridge_optimizer.jl | 21 ++++++++++++++ test/Bridges/lazy_bridge_optimizer.jl | 40 +++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/src/Bridges/bridge_optimizer.jl b/src/Bridges/bridge_optimizer.jl index 5e59dc391c..f7f82bca70 100644 --- a/src/Bridges/bridge_optimizer.jl +++ b/src/Bridges/bridge_optimizer.jl @@ -1744,6 +1744,27 @@ function MOI.set( return throw(MOI.SettingVariableIndexNotAllowed()) end +function MOI.get( + model::AbstractBridgeOptimizer, + attr::MOI.ConstraintConflictStatus, + bridge::AbstractBridge, +) + ret = MOI.NOT_IN_CONFLICT + for (F, S) in MOI.Bridges.added_constraint_types(typeof(bridge)) + for ci in MOI.get(bridge, MOI.ListOfConstraintIndices{F,S}()) + status = MOI.get(model, attr, ci) + if status == MOI.IN_CONFLICT + return status + elseif status == MOI.MAYBE_IN_CONFLICT + ret = status + # elseif status == MOI.NOT_IN_CONFLICT + # Nothing to do here. + end + end + end + return ret +end + ## Getting and Setting names function MOI.get( b::AbstractBridgeOptimizer, diff --git a/test/Bridges/lazy_bridge_optimizer.jl b/test/Bridges/lazy_bridge_optimizer.jl index d3054927b1..e9a56e7531 100644 --- a/test/Bridges/lazy_bridge_optimizer.jl +++ b/test/Bridges/lazy_bridge_optimizer.jl @@ -2288,6 +2288,46 @@ function test_wrong_coefficient_2() return end +MOI.Utilities.@model( + Model2838, + (), + (MOI.GreaterThan,), + (), + (), + (), + (MOI.ScalarAffineFunction,), + (), + () +) + +function test_issue_2838() + inner = MOI.Utilities.MockOptimizer(Model2838{Float64}()) + model = MOI.Bridges.full_bridge_optimizer(inner, Float64) + x = MOI.add_variables(model, 2) + f = MOI.Utilities.operate(vcat, Float64, (1.0 * x)...) + c = MOI.add_constraint(model, f, MOI.Nonnegatives(2)) + F, S = MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64} + ci = MOI.get(inner, MOI.ListOfConstraintIndices{F,S}()) + function cmp(a, b) + if a == MOI.IN_CONFLICT || b == MOI.IN_CONFLICT + return MOI.IN_CONFLICT + elseif a == MOI.MAYBE_IN_CONFLICT || b == MOI.MAYBE_IN_CONFLICT + return MOI.MAYBE_IN_CONFLICT + else + return MOI.NOT_IN_CONFLICT + end + end + list = (MOI.NOT_IN_CONFLICT, MOI.IN_CONFLICT, MOI.MAYBE_IN_CONFLICT) + for a in list, b in list + MOI.set(inner, MOI.ConflictCount(), 1) + MOI.set(inner, MOI.ConstraintConflictStatus(), ci[1], a) + MOI.set(inner, MOI.ConstraintConflictStatus(), ci[2], b) + MOI.compute_conflict!(model) + @test MOI.get(model, MOI.ConstraintConflictStatus(), c) == cmp(a, b) + end + return +end + end # module TestBridgesLazyBridgeOptimizer.runtests() From 6eb9bcd58355d1a01e0d125edfbbffd30850a7b9 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 10 Sep 2025 11:14:39 +1200 Subject: [PATCH 2/4] Update --- .../AbstractFunctionConversionBridge.jl | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl b/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl index aff3fad8a2..b71e75d97b 100644 --- a/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl +++ b/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl @@ -51,6 +51,28 @@ function MOI.get( return bridge end +# Needed to avoid an ambiguity with the getter for MOI.Constraint.AbstractBridge +function MOI.get( + model::MOI.Bridges.AbstractBridgeOptimizer, + attr::MOI.ConstraintConflictStatus, + bridge::AbstractFunctionConversionBridge, +) + ret = MOI.NOT_IN_CONFLICT + for (F, S) in MOI.Bridges.added_constraint_types(typeof(bridge)) + for ci in MOI.get(bridge, MOI.ListOfConstraintIndices{F,S}()) + status = MOI.get(model, attr, ci) + if status == MOI.IN_CONFLICT + return status + elseif status == MOI.MAYBE_IN_CONFLICT + ret = status + # elseif status == MOI.NOT_IN_CONFLICT + # Nothing to do here. + end + end + end + return ret +end + function MOI.supports( model::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute, From 49e0e936a00438ddf2b9c92ae89578386a107663 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 10 Sep 2025 11:31:03 +1200 Subject: [PATCH 3/4] Update --- test/General/attributes.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/General/attributes.jl b/test/General/attributes.jl index 134476263d..68e6685df7 100644 --- a/test/General/attributes.jl +++ b/test/General/attributes.jl @@ -146,7 +146,7 @@ function test_attributes_integration_compute_conflict_2() MOI.compute_conflict!(model) @test MOI.get(model, MOI.ConflictStatus()) == MOI.CONFLICT_FOUND @test MOI.get(model, MOI.ConflictCount()) == 1 - @test_throws ArgumentError MOI.get(model, MOI.ConstraintConflictStatus(), c) + return end struct _NoConstraintName <: MOI.AbstractOptimizer end From 3f453e349b3057f1030c0faeccca415d02487fba Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 10 Sep 2025 12:17:42 +1200 Subject: [PATCH 4/4] Update --- .../bridges/AbstractFunctionConversionBridge.jl | 5 ++--- src/Bridges/bridge_optimizer.jl | 9 ++++++--- src/Bridges/lazy_bridge_optimizer.jl | 4 ---- .../Constraint/ScalarFunctionizeBridge.jl | 16 ++++++++++++++++ 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl b/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl index b71e75d97b..80ea8a852d 100644 --- a/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl +++ b/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl @@ -53,7 +53,7 @@ end # Needed to avoid an ambiguity with the getter for MOI.Constraint.AbstractBridge function MOI.get( - model::MOI.Bridges.AbstractBridgeOptimizer, + model::MOI.ModelLike, attr::MOI.ConstraintConflictStatus, bridge::AbstractFunctionConversionBridge, ) @@ -65,8 +65,6 @@ function MOI.get( return status elseif status == MOI.MAYBE_IN_CONFLICT ret = status - # elseif status == MOI.NOT_IN_CONFLICT - # Nothing to do here. end end end @@ -191,6 +189,7 @@ function invariant_under_function_conversion( MOI.ConstraintPrimalStart, MOI.ConstraintDual, MOI.ConstraintDualStart, + MOI.ConstraintConflictStatus, }, ) return true diff --git a/src/Bridges/bridge_optimizer.jl b/src/Bridges/bridge_optimizer.jl index f7f82bca70..76fbae02d1 100644 --- a/src/Bridges/bridge_optimizer.jl +++ b/src/Bridges/bridge_optimizer.jl @@ -368,6 +368,11 @@ function MOI.optimize!(b::AbstractBridgeOptimizer) return end +function MOI.compute_conflict!(b::AbstractBridgeOptimizer) + MOI.compute_conflict!(b.model) + return +end + function MOI.is_empty(b::AbstractBridgeOptimizer) return isempty(Variable.bridges(b)) && isempty(Constraint.bridges(b)) && @@ -1745,7 +1750,7 @@ function MOI.set( end function MOI.get( - model::AbstractBridgeOptimizer, + model::MOI.ModelLike, attr::MOI.ConstraintConflictStatus, bridge::AbstractBridge, ) @@ -1757,8 +1762,6 @@ function MOI.get( return status elseif status == MOI.MAYBE_IN_CONFLICT ret = status - # elseif status == MOI.NOT_IN_CONFLICT - # Nothing to do here. end end end diff --git a/src/Bridges/lazy_bridge_optimizer.jl b/src/Bridges/lazy_bridge_optimizer.jl index cc4387467f..83870fc32c 100644 --- a/src/Bridges/lazy_bridge_optimizer.jl +++ b/src/Bridges/lazy_bridge_optimizer.jl @@ -607,7 +607,3 @@ function bridging_cost(b::LazyBridgeOptimizer, args...) end recursive_model(b::LazyBridgeOptimizer) = b - -function MOI.compute_conflict!(model::LazyBridgeOptimizer) - return MOI.compute_conflict!(model.model) -end diff --git a/test/Bridges/Constraint/ScalarFunctionizeBridge.jl b/test/Bridges/Constraint/ScalarFunctionizeBridge.jl index 04a9e2f363..6f5e5fbf7a 100644 --- a/test/Bridges/Constraint/ScalarFunctionizeBridge.jl +++ b/test/Bridges/Constraint/ScalarFunctionizeBridge.jl @@ -378,6 +378,22 @@ function test_supports_ScalarNonlinearFunction() return end +function test_issue_2838() + inner = MOI.Utilities.MockOptimizer(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Constraint.ScalarFunctionize{Float64}(inner) + x = MOI.add_variable(model) + c = MOI.add_constraint(model, x, MOI.GreaterThan(1.0)) + F, S = MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64} + ci = only(MOI.get(inner, MOI.ListOfConstraintIndices{F,S}())) + for ret in (MOI.NOT_IN_CONFLICT, MOI.IN_CONFLICT, MOI.MAYBE_IN_CONFLICT) + MOI.set(inner, MOI.ConflictCount(), 1) + MOI.set(inner, MOI.ConstraintConflictStatus(), ci, ret) + MOI.compute_conflict!(model) + @test MOI.get(model, MOI.ConstraintConflictStatus(), c) == ret + end + return +end + end # module TestConstraintFunctionize.runtests()