From ee5bd5330bac8609950336f02a98ee65c9745c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 26 May 2026 18:16:52 +0200 Subject: [PATCH 1/4] Add assert to make bridgeable easier to debug --- src/constraint.jl | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/constraint.jl b/src/constraint.jl index d5d7112..6c98197 100644 --- a/src/constraint.jl +++ b/src/constraint.jl @@ -135,11 +135,34 @@ function _concrete( S, ) end + +# Dispatched assertion that mirrors `_concrete`: a bridge listed in +# `bridges(S)` must, per MOI's contract on `concrete_bridge_type`, support its +# source set before `concrete_bridge_type` (and the downstream +# `added_*_types`) can be called. We dispatch on the bridge supertype so the +# right `supports_*` method is queried. +function _supports( + bridge_type::Type{<:MOI.Bridges.Variable.AbstractBridge}, + S::Type{<:MOI.AbstractSet}, +) + return MOI.Bridges.Variable.supports_constrained_variable(bridge_type, S) +end +function _supports( + bridge_type::Type{<:MOI.Bridges.Constraint.AbstractBridge}, + S::Type{<:MOI.AbstractSet}, +) + return MOI.supports_constraint(bridge_type, MOI.VectorOfVariables, S) +end function bridgeable(c::JuMP.AbstractConstraint, S::Type{<:MOI.AbstractSet}) bridge_types = bridges(S) for bridge_type in bridge_types BT, T = bridge_type c = BridgeableConstraint(c, BT; coefficient_type = T) + # `concrete_bridge_type` documents that it may only be called when + # the bridge's `supports_*` predicate is `true`. + # Otherwise, it just return `BT{T}` for which `added_constrained_variable_types` + # is not implemented and that gives cryptic error. + @assert _supports(BT{T}, S) concrete_bridge_type = _concrete(BT{T}, S) for (ST,) in MOI.Bridges.added_constrained_variable_types(concrete_bridge_type) @@ -169,6 +192,12 @@ function bridgeable( for bridge_type in bridge_types BT, T = bridge_type c = BridgeableConstraint(c, BT, coefficient_type = T) + # Same invariant as the variable version above: a bridge listed in + # `bridges(F, S)` must actually support `F`-in-`S` per MOI's contract + # on `concrete_bridge_type`. Failing this assertion means the call + # site listed a bridge in `bridges(F, S)` that does not bridge `F` in + # `S`. + @assert MOI.supports_constraint(BT{T}, F, S) concrete_bridge_type = MOI.Bridges.Constraint.concrete_bridge_type(BT{T}, F, S) for (ST,) in From 7b07c7e5bcbda7f85f2e23b6e2fe0fb44cdcb142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 27 May 2026 21:43:48 +0200 Subject: [PATCH 2/4] Fix --- test/testpolymodule.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/testpolymodule.jl b/test/testpolymodule.jl index 74011f7..041c121 100644 --- a/test/testpolymodule.jl +++ b/test/testpolymodule.jl @@ -36,6 +36,13 @@ function PolyJuMP.bridges( PolyJuMP.coefficient_type_or_float(F), )] end +function MOI.supports_constraint( + ::Type{<:DummyNonNegBridge}, + ::Type{<:MOI.AbstractVectorFunction}, + ::Type{<:NonNeg}, +) + return true +end function MOI.Bridges.added_constrained_variable_types( ::Type{<:DummyNonNegBridge}, ) From d83ea30a09737b9223ad4c418efc2cd7517c23c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 27 May 2026 21:55:14 +0200 Subject: [PATCH 3/4] Fix format --- test/variable.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/variable.jl b/test/variable.jl index 82cf032..bdb1a3e 100644 --- a/test/variable.jl +++ b/test/variable.jl @@ -80,7 +80,7 @@ function test_MonomialBasis(var) @variable(m, p3[2:3], Poly(X)) @test isa(p3, JuMP.Containers.DenseAxisArray{PT,1,Tuple{UnitRange{Int}}}) _test_variable(m, p3[2], X) - @variable(m, p4[i=2:3, j=i:4], Poly(X), binary = true) + @variable(m, p4[i = 2:3, j = i:4], Poly(X), binary = true) _test_variable(m, p4[2, 3], X, true) X = [x^2, y^2] From e0cff93a9d3b5d90f55b7381649d35d010d92e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 3 Nov 2026 16:08:57 +0100 Subject: [PATCH 4/4] Test bridge support checks and unsupported bridge assertions Cover both variable and constraint support dispatches with supported and unsupported sets. Verify that bridgeable rejects unsupported variable and constraint bridges with AssertionError, and include the standalone regression tests in runtests.jl. Apply JuliaFormatter 2 formatting to the branch. Validation: all 7 bridge tests and 86 variable tests pass (4 existing broken tests). Temporary @show line tracing and Julia coverage confirm both previously missed variable-support lines execute. JuliaFormatter 2.14.7 and git diff --check pass. --- src/nl_to_polynomial.jl | 4 ++-- test/bridgeable.jl | 49 +++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 1 + test/variable.jl | 2 +- 4 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 test/bridgeable.jl diff --git a/src/nl_to_polynomial.jl b/src/nl_to_polynomial.jl index bd8a942..1b57dc7 100644 --- a/src/nl_to_polynomial.jl +++ b/src/nl_to_polynomial.jl @@ -24,7 +24,7 @@ struct InvalidNLExpression <: Exception end function _to_polynomial!(d, ::Type, expr) - throw( + return throw( InvalidNLExpression( "Unexpected expression type `$(typeof(expr))` of `$expr`", ), @@ -230,7 +230,7 @@ function _invalid_value( ::NLToPolynomial, attr::Union{MOI.VariablePrimal,MOI.ConstraintDual,MOI.ConstraintPrimal}, ) - throw(MOI.ResultIndexBoundsError(attr, 0)) + return throw(MOI.ResultIndexBoundsError(attr, 0)) end function MOI.get(model::NLToPolynomial, attr::MOI.AbstractModelAttribute) diff --git a/test/bridgeable.jl b/test/bridgeable.jl new file mode 100644 index 0000000..ea0e5a7 --- /dev/null +++ b/test/bridgeable.jl @@ -0,0 +1,49 @@ +module TestBridgeable + +using Test +using JuMP +using PolyJuMP +import MathOptInterface as MOI + +# These sets deliberately list bridges that do not support them. +struct UnsupportedSet{B} <: MOI.AbstractVectorSet end + +function PolyJuMP.bridges(::Type{UnsupportedSet{B}}) where {B} + return Tuple{Type,Type}[(B, Float64)] +end + +function PolyJuMP.bridges( + ::Type{<:MOI.AbstractVectorFunction}, + S::Type{<:UnsupportedSet}, +) + return PolyJuMP.bridges(S) +end + +function test_supports() + VB = MOI.Bridges.Variable.ZerosBridge{Float64} + @test PolyJuMP._supports(VB, MOI.Zeros) + @test !PolyJuMP._supports(VB, MOI.Nonnegatives) + CB = MOI.Bridges.Constraint.ScalarizeBridge{Float64} + @test PolyJuMP._supports(CB, MOI.Zeros) + @test !PolyJuMP._supports(CB, MOI.SecondOrderCone) +end + +function test_unsupported_bridges() + c = JuMP.VectorConstraint([1.0], MOI.Zeros(1)) + VS = UnsupportedSet{MOI.Bridges.Variable.ZerosBridge} + @test_throws AssertionError PolyJuMP.bridgeable(c, VS) + CS = UnsupportedSet{MOI.Bridges.Constraint.ScalarizeBridge} + @test_throws AssertionError PolyJuMP.bridgeable(c, CS) + @test_throws AssertionError PolyJuMP.bridgeable( + c, + MOI.VectorOfVariables, + CS, + ) +end + +@testset "Bridge support" begin + test_supports() + test_unsupported_bridges() +end + +end diff --git a/test/runtests.jl b/test/runtests.jl index a5da875..dcc0403 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,7 @@ include("polymodule.jl") include("variable.jl") include("constraint.jl") +include("bridgeable.jl") include("functions.jl") include("Mock/mock_tests.jl") diff --git a/test/variable.jl b/test/variable.jl index bdb1a3e..82cf032 100644 --- a/test/variable.jl +++ b/test/variable.jl @@ -80,7 +80,7 @@ function test_MonomialBasis(var) @variable(m, p3[2:3], Poly(X)) @test isa(p3, JuMP.Containers.DenseAxisArray{PT,1,Tuple{UnitRange{Int}}}) _test_variable(m, p3[2], X) - @variable(m, p4[i = 2:3, j = i:4], Poly(X), binary = true) + @variable(m, p4[i=2:3, j=i:4], Poly(X), binary = true) _test_variable(m, p4[2, 3], X, true) X = [x^2, y^2]