diff --git a/src/constraint.jl b/src/constraint.jl index 7908896..47f2e42 100644 --- a/src/constraint.jl +++ b/src/constraint.jl @@ -128,11 +128,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) @@ -162,6 +185,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 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 bb6e841..d4627aa 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("model.jl") include("to_polynomial.jl") 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}, )