diff --git a/src/Bridges/Constraint/Constraint.jl b/src/Bridges/Constraint/Constraint.jl index 6f6e7bec07..65af0f4d02 100644 --- a/src/Bridges/Constraint/Constraint.jl +++ b/src/Bridges/Constraint/Constraint.jl @@ -59,6 +59,7 @@ include("bridges/split_complex_zeros.jl") include("bridges/split_hyperrectangle.jl") include("bridges/hermitian.jl") include("bridges/square.jl") +include("bridges/set_conversion.jl") include("bridges/set_dot_scaling.jl") include("bridges/table.jl") include("bridges/vectorize.jl") diff --git a/src/Bridges/Constraint/bridges/functionize.jl b/src/Bridges/Constraint/bridges/functionize.jl index 38d5ae9bf7..d0b9b1ef78 100644 --- a/src/Bridges/Constraint/bridges/functionize.jl +++ b/src/Bridges/Constraint/bridges/functionize.jl @@ -187,6 +187,8 @@ for these pairs of functions: * [`MOI.ScalarQuadraticFunction`](@ref) to [`MOI.ScalarNonlinearFunction`](@ref) * [`MOI.VectorAffineFunction`](@ref) to [`MOI.VectorQuadraticFunction`](@ref) +See also [`SetConversionBridge`](@ref). + ## Source node `FunctionConversionBridge` supports: diff --git a/src/Bridges/Constraint/bridges/set_conversion.jl b/src/Bridges/Constraint/bridges/set_conversion.jl new file mode 100644 index 0000000000..61bc6ddd3e --- /dev/null +++ b/src/Bridges/Constraint/bridges/set_conversion.jl @@ -0,0 +1,105 @@ +# Copyright (c) 2017: Miles Lubin and contributors +# Copyright (c) 2017: Google Inc. +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +""" + SetConversionBridge{T,S2,S1,F} <: + MOI.Bridges.Constraint.SetMapBridge{T,S2,S1,F,F} + +`SetConversionBridge` implements the following reformulations: + + * ``f(x) \\in S1`` into ``f(x) \\in S2`` + +In order to add this bridge, you need to create a bridge specific +for a given type `T` and set `S2`: +```julia +MOI.Bridges.add_bridge(model, MOI.Bridges.Constraint.SetConversionBridge{T,S2}) +``` +In order to define a bridge with `S2` specified but `T` unspecified, for example +for `JuMP.add_bridge`, you can use +```julia +const MyBridge{T,S1,F} = MOI.Bridges.Constraint.SetConversionBridge{T,S2,S1,F} +``` + +See also [`FunctionConversionBridge`](@ref). + +## Source node + +`SetConversionBridge` supports: + + * `F` in `S1` + +## Target nodes + +`SetConversionBridge` creates: + + * `F` in `S2` +""" +struct SetConversionBridge{T,S2,S1,F} <: + MOI.Bridges.Constraint.SetMapBridge{T,S2,S1,F,F} + constraint::MOI.ConstraintIndex{F,S2} +end + +function MOI.supports_constraint( + ::Type{SetConversionBridge{T,S2}}, + ::Type{F}, + ::Type{S1}, +) where {T,F<:MOI.AbstractFunction,S1<:MOI.AbstractSet,S2} + return isfinite(MOI.Bridges.Constraint.conversion_cost(S2, S1)) +end + +function MOI.Bridges.Constraint.concrete_bridge_type( + ::Type{SetConversionBridge{T,S2}}, + ::Type{F}, + ::Type{S1}, +) where {T,F<:MOI.AbstractFunction,S1<:MOI.AbstractSet,S2} + return SetConversionBridge{T,S2,S1,F} +end + +function MOI.Bridges.Constraint.conversion_cost( + ::Type{<:MOI.AbstractSet}, + ::Type{<:MOI.AbstractSet}, +) + return Inf +end + +function MOI.Bridges.bridging_cost( + ::Type{<:SetConversionBridge{T,S2,S1}}, +) where {T,S2,S1} + return MOI.Bridges.Constraint.conversion_cost(S2, S1) +end + +function MOI.Bridges.map_set( + ::Type{<:SetConversionBridge{T,S2,S1}}, + set::S1, +) where {T,S2,S1} + return convert(S2, set) +end + +function MOI.Bridges.inverse_map_set( + ::Type{<:SetConversionBridge{T,S2,S1}}, + set::S2, +) where {T,S2,S1} + return convert(S1, set) +end + +function MOI.Bridges.map_function(::Type{<:SetConversionBridge}, func) + return func +end + +function MOI.Bridges.inverse_map_function(::Type{<:SetConversionBridge}, func) + return func +end + +function MOI.Bridges.adjoint_map_function(::Type{<:SetConversionBridge}, func) + return func +end + +function MOI.Bridges.inverse_adjoint_map_function( + ::Type{<:SetConversionBridge}, + func, +) + return func +end diff --git a/test/Bridges/Constraint/set_conversion.jl b/test/Bridges/Constraint/set_conversion.jl new file mode 100644 index 0000000000..47d9dc408b --- /dev/null +++ b/test/Bridges/Constraint/set_conversion.jl @@ -0,0 +1,98 @@ +# Copyright (c) 2017: Miles Lubin and contributors +# Copyright (c) 2017: Google Inc. +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +module TestConstraintSetConversion + +using Test + +import MathOptInterface as MOI + +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end + end + return +end + +struct Zero <: MOI.AbstractScalarSet end + +function MOI.Bridges.Constraint.conversion_cost( + ::Type{MOI.EqualTo{Float64}}, + ::Type{Zero}, +) + return 1.0 +end + +Base.convert(::Type{MOI.EqualTo{Float64}}, ::Zero) = MOI.EqualTo(0.0) + +function Base.convert(::Type{Zero}, s::MOI.EqualTo) + if !iszero(s.value) + throw(InexactError(convert, (Zero, s))) + end + return Zero() +end + +# Does not make sense that this is convertible but it's +# just to test `conversion_cost` +function MOI.Bridges.Constraint.conversion_cost( + ::Type{MOI.LessThan{Float64}}, + ::Type{Zero}, +) + return 10.0 +end + +const EqualToBridge{T,S1,F} = + MOI.Bridges.Constraint.SetConversionBridge{T,MOI.EqualTo{T},S1,F} + +function test_runtests() + MOI.Bridges.runtests( + EqualToBridge, + model -> begin + x = MOI.add_variable(model) + MOI.add_constraint(model, x, Zero()) + end, + model -> begin + x = MOI.add_variable(model) + MOI.add_constraint(model, x, MOI.EqualTo(0.0)) + end, + ) + return +end + +function test_conversion_cost(T = Float64) + model = MOI.Utilities.Model{T}() + bridged = MOI.Bridges.LazyBridgeOptimizer(model) + MOI.Bridges.add_bridge( + bridged, + MOI.Bridges.Constraint.SetConversionBridge{T,MOI.LessThan{T}}, + ) + @test MOI.Bridges.bridge_type(bridged, MOI.VariableIndex, Zero) == + MOI.Bridges.Constraint.SetConversionBridge{ + T, + MOI.LessThan{T}, + Zero, + MOI.VariableIndex, + } + MOI.Bridges.add_bridge( + bridged, + MOI.Bridges.Constraint.SetConversionBridge{T,MOI.EqualTo{T}}, + ) + @test MOI.Bridges.bridge_type(bridged, MOI.VariableIndex, Zero) == + MOI.Bridges.Constraint.SetConversionBridge{ + T, + MOI.EqualTo{T}, + Zero, + MOI.VariableIndex, + } +end + +end # module + +TestConstraintSetConversion.runtests()