From 96259e8af05090f73d58edec456e283d0263e273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 19 Mar 2026 10:14:11 +0100 Subject: [PATCH 1/3] Fix deletion before final_touch --- .../Constraint/bridges/IntegerToZeroOneBridge.jl | 4 ++++ .../Constraint/test_IntegerToZeroOneBridge.jl | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/Bridges/Constraint/bridges/IntegerToZeroOneBridge.jl b/src/Bridges/Constraint/bridges/IntegerToZeroOneBridge.jl index 911b28f62a..03eea4e38c 100644 --- a/src/Bridges/Constraint/bridges/IntegerToZeroOneBridge.jl +++ b/src/Bridges/Constraint/bridges/IntegerToZeroOneBridge.jl @@ -103,6 +103,10 @@ function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet, ::IntegerToZeroOneBridge) end function MOI.delete(model::MOI.ModelLike, bridge::IntegerToZeroOneBridge) + if isnothing(bridge.last_bounds) + # Final touch not called yet, so we don't need to delete anything. + return + end MOI.delete(model, bridge.ci) MOI.delete(model, bridge.y) return diff --git a/test/Bridges/Constraint/test_IntegerToZeroOneBridge.jl b/test/Bridges/Constraint/test_IntegerToZeroOneBridge.jl index 936cec85ae..5c9553d7bb 100644 --- a/test/Bridges/Constraint/test_IntegerToZeroOneBridge.jl +++ b/test/Bridges/Constraint/test_IntegerToZeroOneBridge.jl @@ -94,6 +94,18 @@ function test_final_touch_twice() return end +function test_delete_before_final_touch() + inner = MOI.Utilities.Model{Int}() + model = MOI.Bridges.Constraint.IntegerToZeroOne{Int}(inner) + x, cx = MOI.add_constrained_variable(model, MOI.Integer()) + MOI.add_constraint(model, x, MOI.Interval(1, 3)) + MOI.delete(model, x) + @test !MOI.is_valid(model, x) + @test !MOI.is_valid(model, cx) + MOI.Bridges.final_touch(model) + return +end + end # module TestConstraintIntegerToZeroOne.runtests() From 7e63be9b73c9eceb82a51ea7ee28608981bdecb8 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Fri, 20 Mar 2026 10:11:34 +1300 Subject: [PATCH 2/3] Add more tests --- .../Constraint/bridges/BinPackingToMILPBridge.jl | 1 + .../Constraint/bridges/CountBelongsToMILPBridge.jl | 1 + .../Constraint/bridges/CountDistinctToMILPBridge.jl | 1 + .../bridges/CountGreaterThanToMILPBridge.jl | 1 + .../Constraint/bridges/IntegerToZeroOneBridge.jl | 5 ++--- .../bridges/ReifiedCountDistinctToMILPBridge.jl | 1 + .../Constraint/bridges/SemiToBinaryBridge.jl | 1 + .../Constraint/test_BinPackingToMILPBridge.jl | 13 +++++++++++++ .../Constraint/test_CountBelongsToMILPBridge.jl | 13 +++++++++++++ .../Constraint/test_CountDistinctToMILPBridge.jl | 13 +++++++++++++ .../Constraint/test_CountGreaterThanToMILPBridge.jl | 13 +++++++++++++ .../Constraint/test_IndicatorToMILPBridge.jl | 1 + .../test_ReifiedCountDistinctToMILPBridge.jl | 13 +++++++++++++ test/Bridges/Constraint/test_SOS1ToMILPBridge.jl | 1 + test/Bridges/Constraint/test_SOS2ToMILPBridge.jl | 1 + test/Bridges/Constraint/test_SemiToBinaryBridge.jl | 12 ++++++++++++ 16 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/Bridges/Constraint/bridges/BinPackingToMILPBridge.jl b/src/Bridges/Constraint/bridges/BinPackingToMILPBridge.jl index 73141f1365..6e0acac4f0 100644 --- a/src/Bridges/Constraint/bridges/BinPackingToMILPBridge.jl +++ b/src/Bridges/Constraint/bridges/BinPackingToMILPBridge.jl @@ -129,6 +129,7 @@ function MOI.get( end function MOI.delete(model::MOI.ModelLike, bridge::BinPackingToMILPBridge) + # Delete is okay before `final_touch` MOI.delete.(model, bridge.less_than) empty!(bridge.less_than) MOI.delete.(model, bridge.equal_to) diff --git a/src/Bridges/Constraint/bridges/CountBelongsToMILPBridge.jl b/src/Bridges/Constraint/bridges/CountBelongsToMILPBridge.jl index 45eb08fec6..23a767e9bd 100644 --- a/src/Bridges/Constraint/bridges/CountBelongsToMILPBridge.jl +++ b/src/Bridges/Constraint/bridges/CountBelongsToMILPBridge.jl @@ -133,6 +133,7 @@ function MOI.get( end function MOI.delete(model::MOI.ModelLike, bridge::CountBelongsToMILPBridge) + # Delete is okay before `final_touch` for ci in bridge.equal_to MOI.delete(model, ci) end diff --git a/src/Bridges/Constraint/bridges/CountDistinctToMILPBridge.jl b/src/Bridges/Constraint/bridges/CountDistinctToMILPBridge.jl index 72c8a998bd..8f7d997113 100644 --- a/src/Bridges/Constraint/bridges/CountDistinctToMILPBridge.jl +++ b/src/Bridges/Constraint/bridges/CountDistinctToMILPBridge.jl @@ -172,6 +172,7 @@ function MOI.get( end function MOI.delete(model::MOI.ModelLike, bridge::CountDistinctToMILPBridge) + # Delete is okay before `final_touch` for ci in bridge.equal_to MOI.delete(model, ci) end diff --git a/src/Bridges/Constraint/bridges/CountGreaterThanToMILPBridge.jl b/src/Bridges/Constraint/bridges/CountGreaterThanToMILPBridge.jl index c49282b3d3..31997d9781 100644 --- a/src/Bridges/Constraint/bridges/CountGreaterThanToMILPBridge.jl +++ b/src/Bridges/Constraint/bridges/CountGreaterThanToMILPBridge.jl @@ -108,6 +108,7 @@ function MOI.get( end function MOI.delete(model::MOI.ModelLike, bridge::CountGreaterThanToMILPBridge) + # Delete is okay before `final_touch` MOI.delete.(model, bridge.greater_than) empty!(bridge.greater_than) MOI.delete.(model, bridge.equal_to) diff --git a/src/Bridges/Constraint/bridges/IntegerToZeroOneBridge.jl b/src/Bridges/Constraint/bridges/IntegerToZeroOneBridge.jl index 03eea4e38c..cc09bda4f5 100644 --- a/src/Bridges/Constraint/bridges/IntegerToZeroOneBridge.jl +++ b/src/Bridges/Constraint/bridges/IntegerToZeroOneBridge.jl @@ -103,9 +103,8 @@ function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet, ::IntegerToZeroOneBridge) end function MOI.delete(model::MOI.ModelLike, bridge::IntegerToZeroOneBridge) - if isnothing(bridge.last_bounds) - # Final touch not called yet, so we don't need to delete anything. - return + if bridge.last_bounds !== nothing + return # We're deleting the bridge before final_touch end MOI.delete(model, bridge.ci) MOI.delete(model, bridge.y) diff --git a/src/Bridges/Constraint/bridges/ReifiedCountDistinctToMILPBridge.jl b/src/Bridges/Constraint/bridges/ReifiedCountDistinctToMILPBridge.jl index dbdb81fd6b..b823b156cf 100644 --- a/src/Bridges/Constraint/bridges/ReifiedCountDistinctToMILPBridge.jl +++ b/src/Bridges/Constraint/bridges/ReifiedCountDistinctToMILPBridge.jl @@ -169,6 +169,7 @@ function MOI.delete( model::MOI.ModelLike, bridge::ReifiedCountDistinctToMILPBridge, ) + # Delete is okay before `final_touch` for ci in bridge.equal_to MOI.delete(model, ci) end diff --git a/src/Bridges/Constraint/bridges/SemiToBinaryBridge.jl b/src/Bridges/Constraint/bridges/SemiToBinaryBridge.jl index 055122163f..3652dce1fc 100644 --- a/src/Bridges/Constraint/bridges/SemiToBinaryBridge.jl +++ b/src/Bridges/Constraint/bridges/SemiToBinaryBridge.jl @@ -186,6 +186,7 @@ function MOI.get( end function MOI.delete(model::MOI.ModelLike, bridge::SemiToBinaryBridge) + # Delete is okay, even though we call `final_touch` if bridge.integer_index !== nothing MOI.delete(model, bridge.integer_index) end diff --git a/test/Bridges/Constraint/test_BinPackingToMILPBridge.jl b/test/Bridges/Constraint/test_BinPackingToMILPBridge.jl index d97ca34a16..e20eea2d97 100644 --- a/test/Bridges/Constraint/test_BinPackingToMILPBridge.jl +++ b/test/Bridges/Constraint/test_BinPackingToMILPBridge.jl @@ -142,6 +142,19 @@ function test_runtests_error_affine() return end +function test_delete_before_final_touch() + inner = MOI.Utilities.Model{Int}() + model = MOI.Bridges.Constraint.BinPackingToMILP{Int}(inner) + x = MOI.add_variables(model, 2) + f = MOI.Utilities.operate(vcat, Int, 2, 1 * x[1], x[2]) + c = MOI.add_constraint(model, f, MOI.BinPacking(3, [1, 2, 3])) + MOI.delete(model, c) + @test MOI.is_valid(model, x[1]) + @test !MOI.is_valid(model, c) + MOI.Bridges.final_touch(model) + return +end + end # module TestConstraintBinPacking.runtests() diff --git a/test/Bridges/Constraint/test_CountBelongsToMILPBridge.jl b/test/Bridges/Constraint/test_CountBelongsToMILPBridge.jl index cc7d0a2871..b60755ad8e 100644 --- a/test/Bridges/Constraint/test_CountBelongsToMILPBridge.jl +++ b/test/Bridges/Constraint/test_CountBelongsToMILPBridge.jl @@ -127,6 +127,19 @@ function test_runtests_error_affine() return end +function test_delete_before_final_touch() + inner = MOI.Utilities.Model{Int}() + model = MOI.Bridges.Constraint.CountBelongsToMILP{Int}(inner) + x = MOI.add_variables(model, 2) + f = MOI.Utilities.operate(vcat, Int, 2, 1 * x[1], x[2]) + c = MOI.add_constraint(model, f, MOI.CountBelongs(3, Set([2, 4]))) + MOI.delete(model, c) + @test MOI.is_valid(model, x[1]) + @test !MOI.is_valid(model, c) + MOI.Bridges.final_touch(model) + return +end + end # module TestConstraintCountBelongs.runtests() diff --git a/test/Bridges/Constraint/test_CountDistinctToMILPBridge.jl b/test/Bridges/Constraint/test_CountDistinctToMILPBridge.jl index 60163ae6b3..8c24edc784 100644 --- a/test/Bridges/Constraint/test_CountDistinctToMILPBridge.jl +++ b/test/Bridges/Constraint/test_CountDistinctToMILPBridge.jl @@ -185,6 +185,19 @@ function test_resolve_with_modified_not_equal_to() return end +function test_delete_before_final_touch() + inner = MOI.Utilities.Model{Int}() + model = MOI.Bridges.Constraint.CountDistinctToMILP{Int}(inner) + x = MOI.add_variables(model, 2) + f = MOI.Utilities.operate(vcat, Int, 2, 1 * x[1], x[2]) + c = MOI.add_constraint(model, f, MOI.CountDistinct(3)) + MOI.delete(model, c) + @test MOI.is_valid(model, x[1]) + @test !MOI.is_valid(model, c) + MOI.Bridges.final_touch(model) + return +end + end # module TestConstraintCountDistinct.runtests() diff --git a/test/Bridges/Constraint/test_CountGreaterThanToMILPBridge.jl b/test/Bridges/Constraint/test_CountGreaterThanToMILPBridge.jl index 4ae4599e8c..827822cadb 100644 --- a/test/Bridges/Constraint/test_CountGreaterThanToMILPBridge.jl +++ b/test/Bridges/Constraint/test_CountGreaterThanToMILPBridge.jl @@ -144,6 +144,19 @@ function test_runtests_error_affine() return end +function test_delete_before_final_touch() + inner = MOI.Utilities.Model{Int}() + model = MOI.Bridges.Constraint.CountGreaterThanToMILP{Int}(inner) + x = MOI.add_variables(model, 2) + f = MOI.Utilities.operate(vcat, Int, 2, x[1], 1 * x[1], x[2]) + c = MOI.add_constraint(model, f, MOI.CountGreaterThan(3)) + MOI.delete(model, c) + @test MOI.is_valid(model, x[1]) + @test !MOI.is_valid(model, c) + MOI.Bridges.final_touch(model) + return +end + end # module TestConstraintCountGreaterThan.runtests() diff --git a/test/Bridges/Constraint/test_IndicatorToMILPBridge.jl b/test/Bridges/Constraint/test_IndicatorToMILPBridge.jl index 2f4a34668f..063bd6472b 100644 --- a/test/Bridges/Constraint/test_IndicatorToMILPBridge.jl +++ b/test/Bridges/Constraint/test_IndicatorToMILPBridge.jl @@ -298,6 +298,7 @@ function test_delete_before_final_touch() ) MOI.delete(model, c) @test !MOI.is_valid(model, c) + MOI.Bridges.final_touch(model) return end diff --git a/test/Bridges/Constraint/test_ReifiedCountDistinctToMILPBridge.jl b/test/Bridges/Constraint/test_ReifiedCountDistinctToMILPBridge.jl index 361ae9a730..aa0cf40d72 100644 --- a/test/Bridges/Constraint/test_ReifiedCountDistinctToMILPBridge.jl +++ b/test/Bridges/Constraint/test_ReifiedCountDistinctToMILPBridge.jl @@ -164,6 +164,19 @@ function test_runtests_error_affine() return end +function test_delete_before_final_touch() + inner = MOI.Utilities.Model{Int}() + model = MOI.Bridges.Constraint.ReifiedCountDistinctToMILP{Int}(inner) + x = MOI.add_variables(model, 3) + f = MOI.Utilities.operate(vcat, Int, x[1], 1, x[2], x[3]) + c = MOI.add_constraint(model, f, MOI.Reified(MOI.CountDistinct(3))) + MOI.delete(model, c) + @test MOI.is_valid(model, x[1]) + @test !MOI.is_valid(model, c) + MOI.Bridges.final_touch(model) + return +end + end # module TestConstraintReifiedCountDistinct.runtests() diff --git a/test/Bridges/Constraint/test_SOS1ToMILPBridge.jl b/test/Bridges/Constraint/test_SOS1ToMILPBridge.jl index b25b91086d..5de4658a87 100644 --- a/test/Bridges/Constraint/test_SOS1ToMILPBridge.jl +++ b/test/Bridges/Constraint/test_SOS1ToMILPBridge.jl @@ -128,6 +128,7 @@ function test_delete_before_final_touch() ) MOI.delete(model, c) @test !MOI.is_valid(model, c) + MOI.Bridges.final_touch(model) return end diff --git a/test/Bridges/Constraint/test_SOS2ToMILPBridge.jl b/test/Bridges/Constraint/test_SOS2ToMILPBridge.jl index 3d1ce6273b..6352483b3e 100644 --- a/test/Bridges/Constraint/test_SOS2ToMILPBridge.jl +++ b/test/Bridges/Constraint/test_SOS2ToMILPBridge.jl @@ -138,6 +138,7 @@ function test_delete_before_final_touch() ) MOI.delete(model, c) @test !MOI.is_valid(model, c) + MOI.Bridges.final_touch(model) return end diff --git a/test/Bridges/Constraint/test_SemiToBinaryBridge.jl b/test/Bridges/Constraint/test_SemiToBinaryBridge.jl index 6d70c65819..5e132e77f1 100644 --- a/test/Bridges/Constraint/test_SemiToBinaryBridge.jl +++ b/test/Bridges/Constraint/test_SemiToBinaryBridge.jl @@ -334,6 +334,18 @@ function test_open_interval() return end +function test_delete_before_final_touch() + inner = MOI.Utilities.Model{Float64}() + model = MOI.Bridges.Constraint.SemiToBinary{Float64}(inner) + x = MOI.add_variable(model) + c = MOI.add_constraint(model, 1.0 * x, MOI.Semicontinuous(1.0, 2.0)) + MOI.delete(model, c) + @test MOI.is_valid(model, x) + @test !MOI.is_valid(model, c) + MOI.Bridges.final_touch(model) + return +end + end # module TestConstraintSemiToBinary.runtests() From a476654b157581b5b0be8c983a2ca3f62df3f749 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Fri, 20 Mar 2026 10:29:49 +1300 Subject: [PATCH 3/3] Update --- src/Bridges/Constraint/bridges/IntegerToZeroOneBridge.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/Constraint/bridges/IntegerToZeroOneBridge.jl b/src/Bridges/Constraint/bridges/IntegerToZeroOneBridge.jl index cc09bda4f5..438ac7312f 100644 --- a/src/Bridges/Constraint/bridges/IntegerToZeroOneBridge.jl +++ b/src/Bridges/Constraint/bridges/IntegerToZeroOneBridge.jl @@ -103,7 +103,7 @@ function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet, ::IntegerToZeroOneBridge) end function MOI.delete(model::MOI.ModelLike, bridge::IntegerToZeroOneBridge) - if bridge.last_bounds !== nothing + if bridge.last_bounds === nothing return # We're deleting the bridge before final_touch end MOI.delete(model, bridge.ci)