From c2a40ed8281e1853f11a3cbfb0742f86b0eae746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 24 Aug 2024 08:49:39 +0200 Subject: [PATCH 01/11] Add set conversion bridge --- .../Constraint/bridges/set_conversion.jl | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/Bridges/Constraint/bridges/set_conversion.jl diff --git a/src/Bridges/Constraint/bridges/set_conversion.jl b/src/Bridges/Constraint/bridges/set_conversion.jl new file mode 100644 index 0000000000..94ba9627a6 --- /dev/null +++ b/src/Bridges/Constraint/bridges/set_conversion.jl @@ -0,0 +1,69 @@ +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} + if S1 == MOI.AllDifferent + @show isfinite(MOI.Bridges.Constraint.conversion_cost(S2, S1)) + end + 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 From 88e9cf6453b950a79e238b8479446edcfe5bf55c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 4 Sep 2024 09:35:53 +0200 Subject: [PATCH 02/11] Remove debug --- src/Bridges/Constraint/bridges/set_conversion.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Bridges/Constraint/bridges/set_conversion.jl b/src/Bridges/Constraint/bridges/set_conversion.jl index 94ba9627a6..fcd601bbaf 100644 --- a/src/Bridges/Constraint/bridges/set_conversion.jl +++ b/src/Bridges/Constraint/bridges/set_conversion.jl @@ -8,9 +8,6 @@ function MOI.supports_constraint( ::Type{F}, ::Type{S1}, ) where {T,F<:MOI.AbstractFunction,S1<:MOI.AbstractSet,S2} - if S1 == MOI.AllDifferent - @show isfinite(MOI.Bridges.Constraint.conversion_cost(S2, S1)) - end return isfinite(MOI.Bridges.Constraint.conversion_cost(S2, S1)) end From 6e4b9c204013e4bc05ae6c55f621463fc498500b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 9 Oct 2024 22:33:10 +0200 Subject: [PATCH 03/11] Add doc and tests --- src/Bridges/Constraint/bridges/functionize.jl | 2 + .../Constraint/bridges/set_conversion.jl | 23 +++++++++ test/Bridges/Constraint/set_conversion.jl | 48 +++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 test/Bridges/Constraint/set_conversion.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 index fcd601bbaf..3dcd5b351c 100644 --- a/src/Bridges/Constraint/bridges/set_conversion.jl +++ b/src/Bridges/Constraint/bridges/set_conversion.jl @@ -1,3 +1,26 @@ +""" + 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`` + +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} diff --git a/test/Bridges/Constraint/set_conversion.jl b/test/Bridges/Constraint/set_conversion.jl new file mode 100644 index 0000000000..bdd7842ab1 --- /dev/null +++ b/test/Bridges/Constraint/set_conversion.jl @@ -0,0 +1,48 @@ +# 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 + +MOI.Bridges.Constraint.conversion_cost(::Type{MOI.EqualTo{Float64}}, ::Type{Zero}) = 1.0 + +Base.convert(::Type{MOI.EqualTo{Float64}}, ::Zero) = MOI.EqualTo(0.0) + +function test_runtests() + MOI.Bridges.runtests( + MOI.Bridges.Constraint.ScalarFunctionizeBridge, + model -> begin + x = MOI.add_variable(model) + MOI.add_constraint(model, Zero()) + end, + model -> begin + x = MOI.add_variable(model) + MOI.add_constraint(model, MOI.EqualTo(0.0)) + end, + ) + return +end + +end # module + +TestConstraintSetConversion.runtests() From 3a4f4cb74414fa5f357fcb9b82a7fb13aa6d3480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 9 Oct 2024 22:33:59 +0200 Subject: [PATCH 04/11] fix format --- test/Bridges/Constraint/set_conversion.jl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/Bridges/Constraint/set_conversion.jl b/test/Bridges/Constraint/set_conversion.jl index bdd7842ab1..66501a3772 100644 --- a/test/Bridges/Constraint/set_conversion.jl +++ b/test/Bridges/Constraint/set_conversion.jl @@ -21,10 +21,14 @@ function runtests() return end -struct Zero <: MOI.AbstractScalarSet -end +struct Zero <: MOI.AbstractScalarSet end -MOI.Bridges.Constraint.conversion_cost(::Type{MOI.EqualTo{Float64}}, ::Type{Zero}) = 1.0 +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) From 30e439c2ff27ff3310ab64e99722aa6ca395763d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 9 Oct 2024 22:36:13 +0200 Subject: [PATCH 05/11] Fixes --- src/Bridges/Constraint/Constraint.jl | 1 + test/Bridges/Constraint/set_conversion.jl | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) 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/test/Bridges/Constraint/set_conversion.jl b/test/Bridges/Constraint/set_conversion.jl index 66501a3772..c9d9a78b64 100644 --- a/test/Bridges/Constraint/set_conversion.jl +++ b/test/Bridges/Constraint/set_conversion.jl @@ -34,14 +34,14 @@ Base.convert(::Type{MOI.EqualTo{Float64}}, ::Zero) = MOI.EqualTo(0.0) function test_runtests() MOI.Bridges.runtests( - MOI.Bridges.Constraint.ScalarFunctionizeBridge, + MOI.Bridges.Constraint.SetConversionBridge{Float64,MOI.EqualTo{Float64},Zero}, model -> begin x = MOI.add_variable(model) - MOI.add_constraint(model, Zero()) + MOI.add_constraint(model, x, Zero()) end, model -> begin x = MOI.add_variable(model) - MOI.add_constraint(model, MOI.EqualTo(0.0)) + MOI.add_constraint(model, x, MOI.EqualTo(0.0)) end, ) return From 40c870c278148dd783feeae101e2bdcf5159b7c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 10 Oct 2024 13:25:26 +0200 Subject: [PATCH 06/11] Fixes --- src/Bridges/Constraint/bridges/set_conversion.jl | 11 +++++++++++ test/Bridges/Constraint/set_conversion.jl | 11 ++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Bridges/Constraint/bridges/set_conversion.jl b/src/Bridges/Constraint/bridges/set_conversion.jl index 3dcd5b351c..570b11f16b 100644 --- a/src/Bridges/Constraint/bridges/set_conversion.jl +++ b/src/Bridges/Constraint/bridges/set_conversion.jl @@ -6,6 +6,17 @@ * ``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, e.g., +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 diff --git a/test/Bridges/Constraint/set_conversion.jl b/test/Bridges/Constraint/set_conversion.jl index c9d9a78b64..0221f4d507 100644 --- a/test/Bridges/Constraint/set_conversion.jl +++ b/test/Bridges/Constraint/set_conversion.jl @@ -32,9 +32,18 @@ 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 + +const EqualToBridge{T,S1,F} = MOI.Bridges.Constraint.SetConversionBridge{T,MOI.EqualTo{T},S1,F} + function test_runtests() MOI.Bridges.runtests( - MOI.Bridges.Constraint.SetConversionBridge{Float64,MOI.EqualTo{Float64},Zero}, + EqualToBridge, model -> begin x = MOI.add_variable(model) MOI.add_constraint(model, x, Zero()) From 889b40b2dcad7fb553884cec99ffd0bbd465eaa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 10 Oct 2024 13:25:36 +0200 Subject: [PATCH 07/11] Fix format --- test/Bridges/Constraint/set_conversion.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Bridges/Constraint/set_conversion.jl b/test/Bridges/Constraint/set_conversion.jl index 0221f4d507..fc288bd5ba 100644 --- a/test/Bridges/Constraint/set_conversion.jl +++ b/test/Bridges/Constraint/set_conversion.jl @@ -39,7 +39,8 @@ function Base.convert(::Type{Zero}, s::MOI.EqualTo) return Zero() end -const EqualToBridge{T,S1,F} = MOI.Bridges.Constraint.SetConversionBridge{T,MOI.EqualTo{T},S1,F} +const EqualToBridge{T,S1,F} = + MOI.Bridges.Constraint.SetConversionBridge{T,MOI.EqualTo{T},S1,F} function test_runtests() MOI.Bridges.runtests( From 1c8f2bb9fda3f7cf727645b5ccfd520e85187995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 10 Oct 2024 13:41:43 +0200 Subject: [PATCH 08/11] Fix --- src/Bridges/Constraint/bridges/set_conversion.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Bridges/Constraint/bridges/set_conversion.jl b/src/Bridges/Constraint/bridges/set_conversion.jl index 570b11f16b..c905e1d00f 100644 --- a/src/Bridges/Constraint/bridges/set_conversion.jl +++ b/src/Bridges/Constraint/bridges/set_conversion.jl @@ -31,7 +31,6 @@ See also [`FunctionConversionBridge`](@ref). * `F` in `S2` """ - struct SetConversionBridge{T,S2,S1,F} <: MOI.Bridges.Constraint.SetMapBridge{T,S2,S1,F,F} constraint::MOI.ConstraintIndex{F,S2} From a32e2e87d94383adb0c1f2d63e5c11c0cbd29034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Fri, 11 Oct 2024 09:22:34 +0200 Subject: [PATCH 09/11] Update src/Bridges/Constraint/bridges/set_conversion.jl --- src/Bridges/Constraint/bridges/set_conversion.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/Constraint/bridges/set_conversion.jl b/src/Bridges/Constraint/bridges/set_conversion.jl index c905e1d00f..9652fc9d97 100644 --- a/src/Bridges/Constraint/bridges/set_conversion.jl +++ b/src/Bridges/Constraint/bridges/set_conversion.jl @@ -11,7 +11,7 @@ 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, e.g., +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} From 1f60a36c70e9646135bc334558afca4f56cc9a19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Fri, 18 Oct 2024 13:44:07 +0200 Subject: [PATCH 10/11] Add test --- test/Bridges/Constraint/set_conversion.jl | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/Bridges/Constraint/set_conversion.jl b/test/Bridges/Constraint/set_conversion.jl index fc288bd5ba..47d9dc408b 100644 --- a/test/Bridges/Constraint/set_conversion.jl +++ b/test/Bridges/Constraint/set_conversion.jl @@ -39,6 +39,15 @@ function Base.convert(::Type{Zero}, s::MOI.EqualTo) 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} @@ -57,6 +66,33 @@ function test_runtests() 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() From 77a5de046d1faa01a848ef91bff5ece0b2753111 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Fri, 1 Nov 2024 10:03:21 +1300 Subject: [PATCH 11/11] Update src/Bridges/Constraint/bridges/set_conversion.jl --- src/Bridges/Constraint/bridges/set_conversion.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Bridges/Constraint/bridges/set_conversion.jl b/src/Bridges/Constraint/bridges/set_conversion.jl index 9652fc9d97..61bc6ddd3e 100644 --- a/src/Bridges/Constraint/bridges/set_conversion.jl +++ b/src/Bridges/Constraint/bridges/set_conversion.jl @@ -1,3 +1,9 @@ +# 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}