From 2d57b73b1234898946019b768e4d1950076ed12c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 28 May 2024 10:42:51 +0200 Subject: [PATCH 01/15] Allow SetMapBridge to use bridge value --- src/Bridges/Constraint/set_map.jl | 8 ++++---- src/Bridges/set_map.jl | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Bridges/Constraint/set_map.jl b/src/Bridges/Constraint/set_map.jl index 0232d8cdd5..3f0504f0ab 100644 --- a/src/Bridges/Constraint/set_map.jl +++ b/src/Bridges/Constraint/set_map.jl @@ -102,7 +102,7 @@ function MOI.get( bridge::MultiSetMapBridge{T,S1,G}, ) where {T,S1,G} mapped_func = MOI.get(model, attr, bridge.constraint) - func = MOI.Bridges.inverse_map_function(typeof(bridge), mapped_func) + func = MOI.Bridges.inverse_map_function(bridge, mapped_func) return MOI.Utilities.convert_approx(G, func) end @@ -146,7 +146,7 @@ function MOI.get( if value === nothing return nothing end - return MOI.Bridges.inverse_map_function(typeof(bridge), value) + return MOI.Bridges.inverse_map_function(bridge, value) end function MOI.set( @@ -173,7 +173,7 @@ function MOI.get( if value === nothing return nothing end - return MOI.Bridges.adjoint_map_function(typeof(bridge), value) + return MOI.Bridges.adjoint_map_function(bridge, value) end function MOI.set( @@ -185,7 +185,7 @@ function MOI.set( if value === nothing MOI.set(model, attr, bridge.constraint, nothing) else - mapped_value = MOI.Bridges.inverse_adjoint_map_function(BT, value) + mapped_value = MOI.Bridges.inverse_adjoint_map_function(bridge, value) MOI.set(model, attr, bridge.constraint, mapped_value) end return diff --git a/src/Bridges/set_map.jl b/src/Bridges/set_map.jl index d43796105b..b0c95c57b9 100644 --- a/src/Bridges/set_map.jl +++ b/src/Bridges/set_map.jl @@ -61,6 +61,10 @@ the [`MOI.ConstraintPrimal`](@ref) and the """ function inverse_map_function end +function inverse_map_function(bridge::AbstractBridge, func) + return inverse_map_function(typeof(bridge), func) +end + """ adjoint_map_function(::Type{BT}, func) where {BT} @@ -71,6 +75,10 @@ used for getting the [`MOI.ConstraintDual`](@ref) and """ function adjoint_map_function end +function adjoint_map_function(bridge::AbstractBridge, func) + return adjoint_map_function(typeof(bridge), func) +end + """ inverse_adjoint_map_function(::Type{BT}, func) where {BT} @@ -81,3 +89,7 @@ Return the image of `func` through the inverse of the adjoint of the linear map [`MOI.ConstraintDualStart`](@ref) of constraint bridges. """ function inverse_adjoint_map_function end + +function inverse_adjoint_map_function(bridge::AbstractBridge, func) + return inverse_adjoint_map_function(typeof(bridge), func) +end From 78c7f6ce937bd11918c00794c7077a0832414d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 28 May 2024 18:08:10 +0200 Subject: [PATCH 02/15] Add MapNotInvertible --- src/Bridges/Constraint/set_map.jl | 21 +++++++++++++++++++-- src/Bridges/set_map.jl | 6 ++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Bridges/Constraint/set_map.jl b/src/Bridges/Constraint/set_map.jl index 3f0504f0ab..5e55a6f5b9 100644 --- a/src/Bridges/Constraint/set_map.jl +++ b/src/Bridges/Constraint/set_map.jl @@ -102,7 +102,15 @@ function MOI.get( bridge::MultiSetMapBridge{T,S1,G}, ) where {T,S1,G} mapped_func = MOI.get(model, attr, bridge.constraint) - func = MOI.Bridges.inverse_map_function(bridge, mapped_func) + func = try + MOI.Bridges.inverse_map_function(bridge, mapped_func) + catch err + if err isa MOI.Bridges.MapNotInvertible + throw(MOI.GetAttributeNotAllowed(attr)) + else + rethrow(err) + end + end return MOI.Utilities.convert_approx(G, func) end @@ -146,7 +154,16 @@ function MOI.get( if value === nothing return nothing end - return MOI.Bridges.inverse_map_function(bridge, value) + return try + MOI.Bridges.inverse_map_function(bridge, value) + catch err + if err isa MOI.Bridges.MapNotInvertible + throw(MOI.GetAttributeNotAllowed(attr)) + else + rethrow(err) + end + end + end function MOI.set( diff --git a/src/Bridges/set_map.jl b/src/Bridges/set_map.jl index b0c95c57b9..2ed9ba41fe 100644 --- a/src/Bridges/set_map.jl +++ b/src/Bridges/set_map.jl @@ -4,6 +4,8 @@ # 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. +struct MapNotInvertible <: Exception end + """ map_set(::Type{BT}, set) where {BT} @@ -44,6 +46,10 @@ the [`MOI.VariablePrimal`](@ref) and """ function map_function end +function map_function(bridge::AbstractBridge, func) + return map_function(typeof(bridge), func) +end + function map_function(::Type{BT}, func, i::IndexInVector) where {BT} return MOI.Utilities.eachscalar(map_function(BT, func))[i.value] end From f9f53bdc4eb10204ae788159443d31f57dc14b50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 12 Jun 2024 17:49:25 +0200 Subject: [PATCH 03/15] Fix format --- src/Bridges/Constraint/set_map.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Bridges/Constraint/set_map.jl b/src/Bridges/Constraint/set_map.jl index 5e55a6f5b9..d6eb9cc82d 100644 --- a/src/Bridges/Constraint/set_map.jl +++ b/src/Bridges/Constraint/set_map.jl @@ -163,7 +163,6 @@ function MOI.get( rethrow(err) end end - end function MOI.set( From 9e6a5e329725a8c14486a9a3210f5511cb7f2a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 12 Jun 2024 18:02:28 +0200 Subject: [PATCH 04/15] Add docs --- src/Bridges/set_map.jl | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/Bridges/set_map.jl b/src/Bridges/set_map.jl index 2ed9ba41fe..094ef03d4f 100644 --- a/src/Bridges/set_map.jl +++ b/src/Bridges/set_map.jl @@ -4,6 +4,14 @@ # 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. +""" + struct MapNotInvertible <: Exception end + +An error thrown by [`inverse_map_function`](@ref) or +[`inverse_adjoint_map_function`](@ref) indicating that the linear map `A` +defined in [`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref) +is not invertible. +""" struct MapNotInvertible <: Exception end """ @@ -55,7 +63,7 @@ function map_function(::Type{BT}, func, i::IndexInVector) where {BT} end """ - inverse_map_function(::Type{BT}, func) where {BT} + inverse_map_function(bridge::MOI.Bridges.AbstractBridge, func) Return the image of `func` through the inverse of the linear map `A` defined in [`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). This is @@ -64,6 +72,13 @@ used by [`Variable.unbridged_map`](@ref) and for setting the and for getting the [`MOI.ConstraintFunction`](@ref), the [`MOI.ConstraintPrimal`](@ref) and the [`MOI.ConstraintPrimalStart`](@ref) of constraint bridges. +If the linear map `A` is not invertible, the error [`MapNotInvertible`](@ref) is +thrown. + + inverse_map_function(::Type{BT}, func) where {BT} + +The method can alternatively be defined on the bridge type. This legacy +interface is kept for backward compatibility. """ function inverse_map_function end @@ -72,12 +87,17 @@ function inverse_map_function(bridge::AbstractBridge, func) end """ - adjoint_map_function(::Type{BT}, func) where {BT} + adjoint_map_function(bridge::MOI.Bridges.AbstractBridge, func) Return the image of `func` through the adjoint of the linear map `A` defined in [`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). This is used for getting the [`MOI.ConstraintDual`](@ref) and [`MOI.ConstraintDualStart`](@ref) of constraint bridges. + + adjoint_map_function(::Type{BT}, func) where {BT} + +The method can alternatively be defined on the bridge type. This legacy +interface is kept for backward compatibility. """ function adjoint_map_function end @@ -86,13 +106,20 @@ function adjoint_map_function(bridge::AbstractBridge, func) end """ - inverse_adjoint_map_function(::Type{BT}, func) where {BT} + inverse_adjoint_map_function(bridge::MOI.Bridges.AbstractBridge, func) Return the image of `func` through the inverse of the adjoint of the linear map `A` defined in [`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). This is used for getting the [`MOI.ConstraintDual`](@ref) of variable bridges and setting the [`MOI.ConstraintDualStart`](@ref) of constraint bridges. +If the linear map `A` is not invertible, the error [`MapNotInvertible`](@ref) is +thrown. + + inverse_adjoint_map_function(::Type{BT}, func) where {BT} + +The method can alternatively be defined on the bridge type. This legacy +interface is kept for backward compatibility. """ function inverse_adjoint_map_function end From d101f91a2054b6ce0e987effad2139b5e68008ed Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 13 Jun 2024 09:42:35 +1200 Subject: [PATCH 05/15] Update --- docs/src/submodules/Bridges/reference.md | 1 + src/Bridges/Constraint/set_map.jl | 16 +++--- src/Bridges/set_map.jl | 65 +++++++++++------------- 3 files changed, 42 insertions(+), 40 deletions(-) diff --git a/docs/src/submodules/Bridges/reference.md b/docs/src/submodules/Bridges/reference.md index 753c60919f..25047bf95f 100644 --- a/docs/src/submodules/Bridges/reference.md +++ b/docs/src/submodules/Bridges/reference.md @@ -90,6 +90,7 @@ Bridges.debug_supports ## [SetMap API](@id constraint_set_map) ```@docs +Bridges.MapNotInvertible Bridges.map_set Bridges.inverse_map_set Bridges.map_function diff --git a/src/Bridges/Constraint/set_map.jl b/src/Bridges/Constraint/set_map.jl index d6eb9cc82d..97dd605adb 100644 --- a/src/Bridges/Constraint/set_map.jl +++ b/src/Bridges/Constraint/set_map.jl @@ -105,11 +105,13 @@ function MOI.get( func = try MOI.Bridges.inverse_map_function(bridge, mapped_func) catch err + # MapNotInvertible is thrown if the bridge does not support inverting + # the function. The user doesn't need to know this, only that they + # cannot get the attribute. if err isa MOI.Bridges.MapNotInvertible throw(MOI.GetAttributeNotAllowed(attr)) - else - rethrow(err) end + rethrow(err) end return MOI.Utilities.convert_approx(G, func) end @@ -154,14 +156,16 @@ function MOI.get( if value === nothing return nothing end - return try - MOI.Bridges.inverse_map_function(bridge, value) + try + return MOI.Bridges.inverse_map_function(bridge, value) catch err + # MapNotInvertible is thrown if the bridge does not support inverting + # the function. The user doesn't need to know this, only that they + # cannot get the attribute. if err isa MOI.Bridges.MapNotInvertible throw(MOI.GetAttributeNotAllowed(attr)) - else - rethrow(err) end + rethrow(err) end end diff --git a/src/Bridges/set_map.jl b/src/Bridges/set_map.jl index 094ef03d4f..03583ee8e3 100644 --- a/src/Bridges/set_map.jl +++ b/src/Bridges/set_map.jl @@ -34,16 +34,25 @@ used for getting the [`MOI.ConstraintSet`](@ref). function inverse_map_set end """ + map_function(bridge::MOI.Bridges.AbstractBridge, func) map_function(::Type{BT}, func) where {BT} Return the image of `func` through the linear map `A` defined in -[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). This is -used for getting the [`MOI.ConstraintPrimal`](@ref) of variable +[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). + +This function is used for getting the [`MOI.ConstraintPrimal`](@ref) of variable bridges. For constraint bridges, this is used for bridging the constraint, -setting the [`MOI.ConstraintFunction`](@ref) and -[`MOI.ConstraintPrimalStart`](@ref) and -modifying the function with [`MOI.modify`](@ref). +setting the [`MOI.ConstraintFunction`](@ref) and [`MOI.ConstraintPrimalStart`](@ref) +and modifying the function with [`MOI.modify`](@ref). + +The method can alternatively be defined on the bridge type. This legacy +interface is kept for backward compatibility. +""" +function map_function(bridge::AbstractBridge, func) + return map_function(typeof(bridge), func) +end +""" map_function(::Type{BT}, func, i::IndexInVector) where {BT} Return the scalar function at the `i`th index of the vector function that @@ -52,77 +61,65 @@ would be returned by `map_function(BT, func)` except that it may compute the the [`MOI.VariablePrimal`](@ref) and [`MOI.VariablePrimalStart`](@ref) of variable bridges. """ -function map_function end - -function map_function(bridge::AbstractBridge, func) - return map_function(typeof(bridge), func) -end - function map_function(::Type{BT}, func, i::IndexInVector) where {BT} return MOI.Utilities.eachscalar(map_function(BT, func))[i.value] end """ inverse_map_function(bridge::MOI.Bridges.AbstractBridge, func) + inverse_map_function(::Type{BT}, func) where {BT} Return the image of `func` through the inverse of the linear map `A` defined in -[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). This is -used by [`Variable.unbridged_map`](@ref) and for setting the -[`MOI.VariablePrimalStart`](@ref) of variable bridges -and for getting the [`MOI.ConstraintFunction`](@ref), -the [`MOI.ConstraintPrimal`](@ref) and the +[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). + +This function is used by [`Variable.unbridged_map`](@ref) and for setting the +[`MOI.VariablePrimalStart`](@ref) of variable bridges and for getting the +[`MOI.ConstraintFunction`](@ref), the [`MOI.ConstraintPrimal`](@ref) and the [`MOI.ConstraintPrimalStart`](@ref) of constraint bridges. + If the linear map `A` is not invertible, the error [`MapNotInvertible`](@ref) is thrown. - inverse_map_function(::Type{BT}, func) where {BT} - The method can alternatively be defined on the bridge type. This legacy interface is kept for backward compatibility. """ -function inverse_map_function end - function inverse_map_function(bridge::AbstractBridge, func) return inverse_map_function(typeof(bridge), func) end """ adjoint_map_function(bridge::MOI.Bridges.AbstractBridge, func) + adjoint_map_function(::Type{BT}, func) where {BT} Return the image of `func` through the adjoint of the linear map `A` defined in -[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). This is -used for getting the [`MOI.ConstraintDual`](@ref) and -[`MOI.ConstraintDualStart`](@ref) of constraint bridges. +[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). - adjoint_map_function(::Type{BT}, func) where {BT} +This function is used for getting the [`MOI.ConstraintDual`](@ref) and +[`MOI.ConstraintDualStart`](@ref) of constraint bridges. The method can alternatively be defined on the bridge type. This legacy interface is kept for backward compatibility. """ -function adjoint_map_function end - function adjoint_map_function(bridge::AbstractBridge, func) return adjoint_map_function(typeof(bridge), func) end """ inverse_adjoint_map_function(bridge::MOI.Bridges.AbstractBridge, func) + inverse_adjoint_map_function(::Type{BT}, func) where {BT} Return the image of `func` through the inverse of the adjoint of the linear map -`A` defined in [`Variable.SetMapBridge`](@ref) and -[`Constraint.SetMapBridge`](@ref). This is used for getting the -[`MOI.ConstraintDual`](@ref) of variable bridges and setting the -[`MOI.ConstraintDualStart`](@ref) of constraint bridges. +`A` defined in [`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). + +This function is used for getting the [`MOI.ConstraintDual`](@ref) of variable +bridges and setting the [`MOI.ConstraintDualStart`](@ref) of constraint bridges. + If the linear map `A` is not invertible, the error [`MapNotInvertible`](@ref) is thrown. - inverse_adjoint_map_function(::Type{BT}, func) where {BT} - The method can alternatively be defined on the bridge type. This legacy interface is kept for backward compatibility. """ -function inverse_adjoint_map_function end - function inverse_adjoint_map_function(bridge::AbstractBridge, func) return inverse_adjoint_map_function(typeof(bridge), func) end From 74c0a40ca8c3e07383aee85636d0c3ae0071cc11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 20 Jun 2024 20:06:47 +0200 Subject: [PATCH 06/15] Add tests --- src/Bridges/Constraint/set_map.jl | 24 +++-- src/Bridges/set_map.jl | 8 +- test/Bridges/set_map.jl | 148 ++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 9 deletions(-) create mode 100644 test/Bridges/set_map.jl diff --git a/src/Bridges/Constraint/set_map.jl b/src/Bridges/Constraint/set_map.jl index 97dd605adb..428af4cec7 100644 --- a/src/Bridges/Constraint/set_map.jl +++ b/src/Bridges/Constraint/set_map.jl @@ -96,6 +96,19 @@ end # Attributes, Bridge acting as a constraint +# MapNotInvertible is thrown if the bridge does not support inverting +# the function. The user doesn't need to know this, only that they +# cannot get the attribute. Throwing `GetAttributeNotAllowed` allows +# `CachingOptimizer` to fall back to using the cache +function _not_invertible_error(attr, message) + s = "Cannot get $attr as the constraint is reformulated through" * + " a linear transformation that is not invertible." * message + if !isempty(message) + s *= " " * message + end + throw(MOI.GetAttributeNotAllowed(attr, s)) +end + function MOI.get( model::MOI.ModelLike, attr::MOI.ConstraintFunction, @@ -105,11 +118,8 @@ function MOI.get( func = try MOI.Bridges.inverse_map_function(bridge, mapped_func) catch err - # MapNotInvertible is thrown if the bridge does not support inverting - # the function. The user doesn't need to know this, only that they - # cannot get the attribute. if err isa MOI.Bridges.MapNotInvertible - throw(MOI.GetAttributeNotAllowed(attr)) + _not_invertible_error(attr, err.message) end rethrow(err) end @@ -133,7 +143,7 @@ function MOI.get( bridge::MultiSetMapBridge, ) set = MOI.get(model, attr, bridge.constraint) - return MOI.Bridges.inverse_map_set(typeof(bridge), set) + return MOI.Bridges.inverse_map_set(bridge, set) end function MOI.set( @@ -142,7 +152,7 @@ function MOI.set( bridge::MultiSetMapBridge{T,S1}, set::S1, ) where {T,S1} - new_set = MOI.Bridges.map_set(typeof(bridge), set) + new_set = MOI.Bridges.map_set(bridge, set) MOI.set(model, attr, bridge.constraint, new_set) return end @@ -178,7 +188,7 @@ function MOI.set( if value === nothing MOI.set(model, attr, bridge.constraint, nothing) else - mapped_value = MOI.Bridges.map_function(typeof(bridge), value) + mapped_value = MOI.Bridges.map_function(bridge, value) MOI.set(model, attr, bridge.constraint, mapped_value) end return diff --git a/src/Bridges/set_map.jl b/src/Bridges/set_map.jl index 03583ee8e3..d905c3b57a 100644 --- a/src/Bridges/set_map.jl +++ b/src/Bridges/set_map.jl @@ -5,14 +5,18 @@ # in the LICENSE.md file or at https://opensource.org/licenses/MIT. """ - struct MapNotInvertible <: Exception end + struct MapNotInvertible <: Exception + message::String + end An error thrown by [`inverse_map_function`](@ref) or [`inverse_adjoint_map_function`](@ref) indicating that the linear map `A` defined in [`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref) is not invertible. """ -struct MapNotInvertible <: Exception end +struct MapNotInvertible <: Exception + message::String +end """ map_set(::Type{BT}, set) where {BT} diff --git a/test/Bridges/set_map.jl b/test/Bridges/set_map.jl new file mode 100644 index 0000000000..feb09a0f8d --- /dev/null +++ b/test/Bridges/set_map.jl @@ -0,0 +1,148 @@ +# 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. + +# Test with a bridge for which the map is defined on the bridge +# value and not the bridge type +module TestSetMapBridge + +import MathOptInterface as MOI + + +# Constraints `[f[2], f[1]]` if `swap` and otherwise +# `f` to `Nonnegatives` +struct SwapSet <: MOI.AbstractVectorSet + swap::Bool + invertible::Bool +end + +MOI.dimension(::SwapSet) = 2 + +struct SwapBridge{T} <: MOI.Bridges.Constraint.SetMapBridge{ + T, + MOI.Nonnegatives, + SwapSet, + MOI.VectorOfVariables, + MOI.VectorOfVariables, +} + constraint::MOI.ConstraintIndex{ + MOI.VectorOfVariables, + MOI.Nonnegatives, + } + set::SwapSet +end + +function MOI.Bridges.Constraint.bridge_constraint( + ::Type{SwapBridge{T}}, + model::MOI.ModelLike, + func::MOI.VectorOfVariables, + set::SwapSet, +) where {T} + ci = MOI.add_constraint( + model, + MOI.VectorOfVariables(swap(func.variables, set.swap)), + MOI.Nonnegatives(2), + ) + return SwapBridge{T}(ci, set) +end + +function MOI.Bridges.map_set(bridge::SwapBridge, set::SwapSet) + if set.swap != bridge.set.swap + error("Cannot change swap set") + else + return MOI.Nonnegatives(2) + end +end + +function MOI.Bridges.inverse_map_set(bridge::SwapBridge, ::MOI.Nonnegatives) + return bridge.set +end + +function MOI.Bridges.map_function(bridge::SwapBridge, func) + return swap(func, bridge.set.swap) +end + +function MOI.Bridges.inverse_map_function(bridge::SwapBridge, func) + if !bridge.set.invertible + throw(MOI.Bridges.MapNotInvertible("no luck")) + end + return swap(func, bridge.set.swap) +end + +function MOI.Bridges.adjoint_map_function(bridge::SwapBridge, func) + return swap(func, bridge.set.swap) +end + +function MOI.Bridges.inverse_adjoint_map_function(bridge::SwapBridge, func) + return swap(func, bridge.set.swap) +end + +using Test + +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end + end + return +end + +function swap(x, swap::Bool) + if swap + return [x[2], x[1]] + else + return x + end +end + +function swap(f::MOI.VectorOfVariables, do_swap::Bool) + return MOI.VectorOfVariables(swap(f.variables, do_swap)) +end + +function test_set_set() + model = MOI.Bridges.Constraint.SingleBridgeOptimizer{SwapBridge{Float64}}( + MOI.Utilities.Model{Float64}() + ) + x = MOI.add_variables(model, 2) + func = MOI.VectorOfVariables(x) + set = SwapSet(true, false) + ci = MOI.add_constraint(model, func, set) + err = ErrorException("Cannot change swap set") + @test_throws err MOI.set(model, MOI.ConstraintSet(), ci, SwapSet(false, false)) + attr = MOI.ConstraintFunction() + err = MOI.GetAttributeNotAllowed( + attr, + "Cannot get MathOptInterface.ConstraintFunction() as the constraint is reformulated through a linear transformation that is not invertible.no luck no luck", + ) + @test_throws err MOI.get(model, attr, ci) +end + +function test_runtests() + for do_swap in [false, true] + MOI.Bridges.runtests( + SwapBridge, + model -> begin + x = MOI.add_variables(model, 2) + func = MOI.VectorOfVariables(x) + set = SwapSet(do_swap, true) + MOI.add_constraint(model, func, set) + end, + model -> begin + x = MOI.add_variables(model, 2) + func = MOI.VectorOfVariables(swap(x, do_swap)) + set = MOI.Nonnegatives(2) + MOI.add_constraint(model, func, set) + end, + ) + end + return +end + +end # module + +TestSetMapBridge.runtests() From 98fbb1d6c9c6db99e35e36411b59f6507e80d4a8 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Fri, 21 Jun 2024 10:36:34 +1200 Subject: [PATCH 07/15] Update set_map.jl --- test/Bridges/set_map.jl | 63 +++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/test/Bridges/set_map.jl b/test/Bridges/set_map.jl index feb09a0f8d..da4a356c5e 100644 --- a/test/Bridges/set_map.jl +++ b/test/Bridges/set_map.jl @@ -4,15 +4,15 @@ # 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. -# Test with a bridge for which the map is defined on the bridge -# value and not the bridge type +# Test with a bridge for which the map is defined on the bridge value and not +# the bridge type module TestSetMapBridge -import MathOptInterface as MOI +using Test +import MathOptInterface as MOI -# Constraints `[f[2], f[1]]` if `swap` and otherwise -# `f` to `Nonnegatives` +# Constraints `[f[2], f[1]]` if `swap` and otherwise `f` to `Nonnegatives` struct SwapSet <: MOI.AbstractVectorSet swap::Bool invertible::Bool @@ -27,10 +27,7 @@ struct SwapBridge{T} <: MOI.Bridges.Constraint.SetMapBridge{ MOI.VectorOfVariables, MOI.VectorOfVariables, } - constraint::MOI.ConstraintIndex{ - MOI.VectorOfVariables, - MOI.Nonnegatives, - } + constraint::MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.Nonnegatives} set::SwapSet end @@ -51,14 +48,11 @@ end function MOI.Bridges.map_set(bridge::SwapBridge, set::SwapSet) if set.swap != bridge.set.swap error("Cannot change swap set") - else - return MOI.Nonnegatives(2) end + return MOI.Nonnegatives(2) end -function MOI.Bridges.inverse_map_set(bridge::SwapBridge, ::MOI.Nonnegatives) - return bridge.set -end +MOI.Bridges.inverse_map_set(bridge::SwapBridge, ::MOI.Nonnegatives) = bridge.set function MOI.Bridges.map_function(bridge::SwapBridge, func) return swap(func, bridge.set.swap) @@ -79,7 +73,11 @@ function MOI.Bridges.inverse_adjoint_map_function(bridge::SwapBridge, func) return swap(func, bridge.set.swap) end -using Test +swap(x, swap::Bool) = swap ? [x[2], x[1]] : x + +function swap(f::MOI.VectorOfVariables, do_swap::Bool) + return MOI.VectorOfVariables(swap(f.variables, do_swap)) +end function runtests() for name in names(@__MODULE__; all = true) @@ -92,34 +90,25 @@ function runtests() return end -function swap(x, swap::Bool) - if swap - return [x[2], x[1]] - else - return x - end -end - -function swap(f::MOI.VectorOfVariables, do_swap::Bool) - return MOI.VectorOfVariables(swap(f.variables, do_swap)) -end - function test_set_set() model = MOI.Bridges.Constraint.SingleBridgeOptimizer{SwapBridge{Float64}}( - MOI.Utilities.Model{Float64}() + MOI.Utilities.Model{Float64}(), ) x = MOI.add_variables(model, 2) func = MOI.VectorOfVariables(x) - set = SwapSet(true, false) - ci = MOI.add_constraint(model, func, set) - err = ErrorException("Cannot change swap set") - @test_throws err MOI.set(model, MOI.ConstraintSet(), ci, SwapSet(false, false)) - attr = MOI.ConstraintFunction() - err = MOI.GetAttributeNotAllowed( - attr, - "Cannot get MathOptInterface.ConstraintFunction() as the constraint is reformulated through a linear transformation that is not invertible.no luck no luck", + ci = MOI.add_constraint(model, func, SwapSet(true, false)) + @test_throws( + ErrorException("Cannot change swap set"), + MOI.set(model, MOI.ConstraintSet(), ci, SwapSet(false, false)), ) - @test_throws err MOI.get(model, attr, ci) + @test_throws( + MOI.GetAttributeNotAllowed( + MOI.ConstraintFunction(), + "Cannot get MathOptInterface.ConstraintFunction() as the constraint is reformulated through a linear transformation that is not invertible.no luck no luck", + ), + MOI.get(model, MOI.ConstraintFunction(), ci), + ) + return end function test_runtests() From 6cf064637176c8a48d3a3014ef37c56a617d58d3 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Fri, 21 Jun 2024 10:41:08 +1200 Subject: [PATCH 08/15] Update set_map.jl --- src/Bridges/Constraint/set_map.jl | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Bridges/Constraint/set_map.jl b/src/Bridges/Constraint/set_map.jl index 428af4cec7..63fe8c727f 100644 --- a/src/Bridges/Constraint/set_map.jl +++ b/src/Bridges/Constraint/set_map.jl @@ -96,17 +96,16 @@ end # Attributes, Bridge acting as a constraint -# MapNotInvertible is thrown if the bridge does not support inverting -# the function. The user doesn't need to know this, only that they -# cannot get the attribute. Throwing `GetAttributeNotAllowed` allows -# `CachingOptimizer` to fall back to using the cache +# MapNotInvertible is thrown if the bridge does not support inverting the +# function. The user doesn't need to know this, only that they cannot get the +# attribute. Throwing `GetAttributeNotAllowed` allows `CachingOptimizer` to fall +# back to using the cache. function _not_invertible_error(attr, message) - s = "Cannot get $attr as the constraint is reformulated through" * - " a linear transformation that is not invertible." * message + s = "Cannot get $attr as the constraint is reformulated through a linear transformation that is not invertible." if !isempty(message) s *= " " * message end - throw(MOI.GetAttributeNotAllowed(attr, s)) + return MOI.GetAttributeNotAllowed(attr, s) end function MOI.get( @@ -119,7 +118,7 @@ function MOI.get( MOI.Bridges.inverse_map_function(bridge, mapped_func) catch err if err isa MOI.Bridges.MapNotInvertible - _not_invertible_error(attr, err.message) + throw(_not_invertible_error(attr, err.message)) end rethrow(err) end From ed11d1d2b4a2897ed06e379670654934cfca1266 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Fri, 21 Jun 2024 10:42:49 +1200 Subject: [PATCH 09/15] Update set_map.jl --- src/Bridges/set_map.jl | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Bridges/set_map.jl b/src/Bridges/set_map.jl index d905c3b57a..3eed5303cc 100644 --- a/src/Bridges/set_map.jl +++ b/src/Bridges/set_map.jl @@ -29,13 +29,21 @@ the [`MOI.ConstraintSet`](@ref). function map_set end """ + inverse_map_set(bridge::MOI.Bridges.AbstractBridge, set) inverse_map_set(::Type{BT}, set) where {BT} Return the preimage of `set` through the linear map `A` defined in -[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). This is -used for getting the [`MOI.ConstraintSet`](@ref). +[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). + +This function is used for getting the [`MOI.ConstraintSet`](@ref). + +The method can alternatively be defined on the bridge type. This legacy +interface is kept for backward compatibility. """ -function inverse_map_set end +function inverse_map_set(bridge::AbstractBridge, set) + return map_function(typeof(bridge), set) +end + """ map_function(bridge::MOI.Bridges.AbstractBridge, func) From 5bcabc270d8a21df772437c0ac7d45f2600185c5 Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 21 Jun 2024 11:16:05 +1200 Subject: [PATCH 10/15] Update --- src/Bridges/set_map.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Bridges/set_map.jl b/src/Bridges/set_map.jl index 3eed5303cc..42f1af7c12 100644 --- a/src/Bridges/set_map.jl +++ b/src/Bridges/set_map.jl @@ -41,10 +41,9 @@ The method can alternatively be defined on the bridge type. This legacy interface is kept for backward compatibility. """ function inverse_map_set(bridge::AbstractBridge, set) - return map_function(typeof(bridge), set) + return inverse_map_set(typeof(bridge), set) end - """ map_function(bridge::MOI.Bridges.AbstractBridge, func) map_function(::Type{BT}, func) where {BT} From 1a03bb13532479ddbaa767cf39428b5fb99ad7b8 Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 21 Jun 2024 11:58:22 +1200 Subject: [PATCH 11/15] Update --- src/Bridges/set_map.jl | 13 +++++++++---- test/Bridges/set_map.jl | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Bridges/set_map.jl b/src/Bridges/set_map.jl index 42f1af7c12..8c9885a478 100644 --- a/src/Bridges/set_map.jl +++ b/src/Bridges/set_map.jl @@ -19,14 +19,19 @@ struct MapNotInvertible <: Exception end """ + map_set(bridge::MOI.Bridges.AbstractBridge, set) map_set(::Type{BT}, set) where {BT} Return the image of `set` through the linear map `A` defined in -[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). This is -used for bridging the constraint and setting -the [`MOI.ConstraintSet`](@ref). +[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). + +This function is used for bridging the constraint and setting the +[`MOI.ConstraintSet`](@ref). + +The method can alternatively be defined on the bridge type. This legacy +interface is kept for backward compatibility. """ -function map_set end +map_set(bridge::AbstractBridge, set) = map_set(typeof(bridge), set) """ inverse_map_set(bridge::MOI.Bridges.AbstractBridge, set) diff --git a/test/Bridges/set_map.jl b/test/Bridges/set_map.jl index da4a356c5e..426834a05d 100644 --- a/test/Bridges/set_map.jl +++ b/test/Bridges/set_map.jl @@ -104,7 +104,7 @@ function test_set_set() @test_throws( MOI.GetAttributeNotAllowed( MOI.ConstraintFunction(), - "Cannot get MathOptInterface.ConstraintFunction() as the constraint is reformulated through a linear transformation that is not invertible.no luck no luck", + "Cannot get MathOptInterface.ConstraintFunction() as the constraint is reformulated through a linear transformation that is not invertible. no luck", ), MOI.get(model, MOI.ConstraintFunction(), ci), ) From 1ac071d0c233af637b6a11e55dce11267ce09962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Fri, 21 Jun 2024 09:58:20 +0200 Subject: [PATCH 12/15] Should cover everything with new tests --- src/Bridges/Constraint/set_map.jl | 19 ++++++--- src/Bridges/set_map.jl | 12 ++++-- test/Bridges/set_map.jl | 68 ++++++++++++++++++++++++++----- 3 files changed, 79 insertions(+), 20 deletions(-) diff --git a/src/Bridges/Constraint/set_map.jl b/src/Bridges/Constraint/set_map.jl index 63fe8c727f..4c84f20154 100644 --- a/src/Bridges/Constraint/set_map.jl +++ b/src/Bridges/Constraint/set_map.jl @@ -100,12 +100,12 @@ end # function. The user doesn't need to know this, only that they cannot get the # attribute. Throwing `GetAttributeNotAllowed` allows `CachingOptimizer` to fall # back to using the cache. -function _not_invertible_error(attr, message) - s = "Cannot get $attr as the constraint is reformulated through a linear transformation that is not invertible." +function _not_invertible_error_message(attr, message) + s = "Cannot get `$attr` as the constraint is reformulated through a linear transformation that is not invertible." if !isempty(message) s *= " " * message end - return MOI.GetAttributeNotAllowed(attr, s) + return s end function MOI.get( @@ -118,7 +118,7 @@ function MOI.get( MOI.Bridges.inverse_map_function(bridge, mapped_func) catch err if err isa MOI.Bridges.MapNotInvertible - throw(_not_invertible_error(attr, err.message)) + throw(MOI.GetAttributeNotAllowed(attr, _not_invertible_error_message(attr, err.message))) end rethrow(err) end @@ -172,7 +172,7 @@ function MOI.get( # the function. The user doesn't need to know this, only that they # cannot get the attribute. if err isa MOI.Bridges.MapNotInvertible - throw(MOI.GetAttributeNotAllowed(attr)) + throw(MOI.GetAttributeNotAllowed(attr, _not_invertible_error_message(attr, err.message))) end rethrow(err) end @@ -214,7 +214,14 @@ function MOI.set( if value === nothing MOI.set(model, attr, bridge.constraint, nothing) else - mapped_value = MOI.Bridges.inverse_adjoint_map_function(bridge, value) + mapped_value = try + MOI.Bridges.inverse_adjoint_map_function(bridge, value) + catch err + if err isa MOI.Bridges.MapNotInvertible + throw(MOI.SetAttributeNotAllowed(attr, _not_invertible_error_message(attr, err.message))) + end + rethrow(err) + end MOI.set(model, attr, bridge.constraint, mapped_value) end return diff --git a/src/Bridges/set_map.jl b/src/Bridges/set_map.jl index 8c9885a478..41be9a50b2 100644 --- a/src/Bridges/set_map.jl +++ b/src/Bridges/set_map.jl @@ -28,8 +28,10 @@ Return the image of `set` through the linear map `A` defined in This function is used for bridging the constraint and setting the [`MOI.ConstraintSet`](@ref). -The method can alternatively be defined on the bridge type. This legacy -interface is kept for backward compatibility. +The default implementation of [`Variable.bridge_constraint`](@ref) uses +[`map_set`](@ref) with the bridge type so if this function is defined +on the bridge type, [`Variable.bridge_constraint`](@ref) does not need +to be implemented. """ map_set(bridge::AbstractBridge, set) = map_set(typeof(bridge), set) @@ -61,8 +63,10 @@ bridges. For constraint bridges, this is used for bridging the constraint, setting the [`MOI.ConstraintFunction`](@ref) and [`MOI.ConstraintPrimalStart`](@ref) and modifying the function with [`MOI.modify`](@ref). -The method can alternatively be defined on the bridge type. This legacy -interface is kept for backward compatibility. +The default implementation of [`Constraint.bridge_constraint`](@ref) uses +[`map_function`](@ref) with the bridge type so if this function is defined +on the bridge type, [`Constraint.bridge_constraint`](@ref) does not need +to be implemented. """ function map_function(bridge::AbstractBridge, func) return map_function(typeof(bridge), func) diff --git a/test/Bridges/set_map.jl b/test/Bridges/set_map.jl index 426834a05d..f4f600c60d 100644 --- a/test/Bridges/set_map.jl +++ b/test/Bridges/set_map.jl @@ -12,10 +12,12 @@ using Test import MathOptInterface as MOI +@enum(ErrType, NONE, NOT_INVERTIBLE, OTHER) + # Constraints `[f[2], f[1]]` if `swap` and otherwise `f` to `Nonnegatives` struct SwapSet <: MOI.AbstractVectorSet swap::Bool - invertible::Bool + err::ErrType end MOI.dimension(::SwapSet) = 2 @@ -59,10 +61,13 @@ function MOI.Bridges.map_function(bridge::SwapBridge, func) end function MOI.Bridges.inverse_map_function(bridge::SwapBridge, func) - if !bridge.set.invertible + if bridge.set.err == NONE + return swap(func, bridge.set.swap) + elseif bridge.set.err == NOT_INVERTIBLE throw(MOI.Bridges.MapNotInvertible("no luck")) + else + error() end - return swap(func, bridge.set.swap) end function MOI.Bridges.adjoint_map_function(bridge::SwapBridge, func) @@ -70,7 +75,13 @@ function MOI.Bridges.adjoint_map_function(bridge::SwapBridge, func) end function MOI.Bridges.inverse_adjoint_map_function(bridge::SwapBridge, func) - return swap(func, bridge.set.swap) + if bridge.set.err == NONE + return swap(func, bridge.set.swap) + elseif bridge.set.err == NOT_INVERTIBLE + throw(MOI.Bridges.MapNotInvertible("no luck")) + else + error() + end end swap(x, swap::Bool) = swap ? [x[2], x[1]] : x @@ -90,24 +101,61 @@ function runtests() return end -function test_set_set() +function test_other_error() model = MOI.Bridges.Constraint.SingleBridgeOptimizer{SwapBridge{Float64}}( - MOI.Utilities.Model{Float64}(), + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), ) x = MOI.add_variables(model, 2) func = MOI.VectorOfVariables(x) - ci = MOI.add_constraint(model, func, SwapSet(true, false)) + ci = MOI.add_constraint(model, func, SwapSet(true, OTHER)) + @test_throws( + ErrorException(""), + MOI.get(model, MOI.ConstraintFunction(), ci), + ) + MOI.set(model, MOI.ConstraintPrimalStart(), ci, ones(2)) + @test_throws( + ErrorException(""), + MOI.get(model, MOI.ConstraintPrimalStart(), ci), + ) + @test_throws( + ErrorException(""), + MOI.set(model, MOI.ConstraintDualStart(), ci, ones(2)), + ) + return +end +function test_not_invertible() + model = MOI.Bridges.Constraint.SingleBridgeOptimizer{SwapBridge{Float64}}( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + ) + x = MOI.add_variables(model, 2) + func = MOI.VectorOfVariables(x) + ci = MOI.add_constraint(model, func, SwapSet(true, NOT_INVERTIBLE)) @test_throws( ErrorException("Cannot change swap set"), - MOI.set(model, MOI.ConstraintSet(), ci, SwapSet(false, false)), + MOI.set(model, MOI.ConstraintSet(), ci, SwapSet(false, NOT_INVERTIBLE)), ) @test_throws( MOI.GetAttributeNotAllowed( MOI.ConstraintFunction(), - "Cannot get MathOptInterface.ConstraintFunction() as the constraint is reformulated through a linear transformation that is not invertible. no luck", + "Cannot get `MathOptInterface.ConstraintFunction()` as the constraint is reformulated through a linear transformation that is not invertible. no luck", ), MOI.get(model, MOI.ConstraintFunction(), ci), ) + MOI.set(model, MOI.ConstraintPrimalStart(), ci, ones(2)) + @test_throws( + MOI.GetAttributeNotAllowed( + MOI.ConstraintPrimalStart(), + "Cannot get `MathOptInterface.ConstraintPrimalStart()` as the constraint is reformulated through a linear transformation that is not invertible. no luck", + ), + MOI.get(model, MOI.ConstraintPrimalStart(), ci), + ) + @test_throws( + MOI.SetAttributeNotAllowed( + MOI.ConstraintDualStart(), + "Cannot get `MathOptInterface.ConstraintDualStart()` as the constraint is reformulated through a linear transformation that is not invertible. no luck", + ), + MOI.set(model, MOI.ConstraintDualStart(), ci, ones(2)), + ) return end @@ -118,7 +166,7 @@ function test_runtests() model -> begin x = MOI.add_variables(model, 2) func = MOI.VectorOfVariables(x) - set = SwapSet(do_swap, true) + set = SwapSet(do_swap, NONE) MOI.add_constraint(model, func, set) end, model -> begin From 5c1701ea00fad93378ea81bc5f18f58385597096 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Sat, 22 Jun 2024 10:31:45 +1200 Subject: [PATCH 13/15] Update set_map.jl --- src/Bridges/Constraint/set_map.jl | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Bridges/Constraint/set_map.jl b/src/Bridges/Constraint/set_map.jl index 4c84f20154..9bb54ddab9 100644 --- a/src/Bridges/Constraint/set_map.jl +++ b/src/Bridges/Constraint/set_map.jl @@ -102,10 +102,10 @@ end # back to using the cache. function _not_invertible_error_message(attr, message) s = "Cannot get `$attr` as the constraint is reformulated through a linear transformation that is not invertible." - if !isempty(message) - s *= " " * message + if isempty(message) + return s end - return s + return s * " " * message end function MOI.get( @@ -118,7 +118,8 @@ function MOI.get( MOI.Bridges.inverse_map_function(bridge, mapped_func) catch err if err isa MOI.Bridges.MapNotInvertible - throw(MOI.GetAttributeNotAllowed(attr, _not_invertible_error_message(attr, err.message))) + msg = _not_invertible_error_message(attr, err.message) + throw(MOI.GetAttributeNotAllowed(attr, msg)) end rethrow(err) end @@ -172,7 +173,8 @@ function MOI.get( # the function. The user doesn't need to know this, only that they # cannot get the attribute. if err isa MOI.Bridges.MapNotInvertible - throw(MOI.GetAttributeNotAllowed(attr, _not_invertible_error_message(attr, err.message))) + msg = _not_invertible_error_message(attr, err.message) + throw(MOI.GetAttributeNotAllowed(attr, msg)) end rethrow(err) end @@ -218,7 +220,8 @@ function MOI.set( MOI.Bridges.inverse_adjoint_map_function(bridge, value) catch err if err isa MOI.Bridges.MapNotInvertible - throw(MOI.SetAttributeNotAllowed(attr, _not_invertible_error_message(attr, err.message))) + msg = _not_invertible_error_message(attr, err.message) + throw(MOI.SetAttributeNotAllowed(attr, msg)) end rethrow(err) end From 919119e5ae4751aaf68b9b959432cdd2b256891e Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Sat, 22 Jun 2024 10:37:52 +1200 Subject: [PATCH 14/15] Update set_map.jl --- src/Bridges/set_map.jl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Bridges/set_map.jl b/src/Bridges/set_map.jl index 41be9a50b2..c665c1dd00 100644 --- a/src/Bridges/set_map.jl +++ b/src/Bridges/set_map.jl @@ -27,11 +27,6 @@ Return the image of `set` through the linear map `A` defined in This function is used for bridging the constraint and setting the [`MOI.ConstraintSet`](@ref). - -The default implementation of [`Variable.bridge_constraint`](@ref) uses -[`map_set`](@ref) with the bridge type so if this function is defined -on the bridge type, [`Variable.bridge_constraint`](@ref) does not need -to be implemented. """ map_set(bridge::AbstractBridge, set) = map_set(typeof(bridge), set) From 49d6d5e0335f5b70208ff9ae1f605c203a24b4a4 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Sat, 22 Jun 2024 11:36:02 +1200 Subject: [PATCH 15/15] Update set_map.jl --- src/Bridges/Constraint/set_map.jl | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Bridges/Constraint/set_map.jl b/src/Bridges/Constraint/set_map.jl index 9bb54ddab9..d132b78a0f 100644 --- a/src/Bridges/Constraint/set_map.jl +++ b/src/Bridges/Constraint/set_map.jl @@ -101,11 +101,7 @@ end # attribute. Throwing `GetAttributeNotAllowed` allows `CachingOptimizer` to fall # back to using the cache. function _not_invertible_error_message(attr, message) - s = "Cannot get `$attr` as the constraint is reformulated through a linear transformation that is not invertible." - if isempty(message) - return s - end - return s * " " * message + return "Cannot get `$attr` as the constraint is reformulated through a linear transformation that is not invertible. $message" end function MOI.get(