diff --git a/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl b/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl index aff3fad8a2..80ea8a852d 100644 --- a/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl +++ b/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl @@ -51,6 +51,26 @@ function MOI.get( return bridge end +# Needed to avoid an ambiguity with the getter for MOI.Constraint.AbstractBridge +function MOI.get( + model::MOI.ModelLike, + 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 + end + end + end + return ret +end + function MOI.supports( model::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute, @@ -169,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 5e59dc391c..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)) && @@ -1744,6 +1749,25 @@ function MOI.set( return throw(MOI.SettingVariableIndexNotAllowed()) end +function MOI.get( + model::MOI.ModelLike, + 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 + end + end + end + return ret +end + ## Getting and Setting names function MOI.get( b::AbstractBridgeOptimizer, 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() 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() 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