From ad275bb851e88c748d311d371b3f3339a268a206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 10 May 2025 09:29:38 +0200 Subject: [PATCH 1/3] [Bridges] Add Interval to HyperRectangle constraint bridge --- .../bridges/IntervalToHyperRectangleBridge.jl | 214 ++++++++++++++++++ .../IntervalToHyperRectangleBridge.jl | 114 ++++++++++ 2 files changed, 328 insertions(+) create mode 100644 src/Bridges/Constraint/bridges/IntervalToHyperRectangleBridge.jl create mode 100644 test/Bridges/Constraint/IntervalToHyperRectangleBridge.jl diff --git a/src/Bridges/Constraint/bridges/IntervalToHyperRectangleBridge.jl b/src/Bridges/Constraint/bridges/IntervalToHyperRectangleBridge.jl new file mode 100644 index 0000000000..5a2fd8d258 --- /dev/null +++ b/src/Bridges/Constraint/bridges/IntervalToHyperRectangleBridge.jl @@ -0,0 +1,214 @@ +# 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. + +""" + IntervalToHyperRectangleBridge{T,F,G} <: Bridges.Constraint.AbstractBridge + +`IntervalToHyperRectangleBridge` implements the following reformulations: + + * ``l \\le g(x) \\le u`` into ``[g(x)] \\in `` `MOI.HyperRectangle([l], [u])` + +where `T` is the coefficient type of `g(x)` and the type of `l` and `u`. + +See also [`VectorizeBridge`](@ref) for equality and single-sided bound +constraints. + +## Source node + +`IntervalToHyperRectangleBridge` supports: + + * `G` in [`MOI.Interval{T}`](@ref) + +## Target nodes + +`IntervalToHyperRectangleBridge` creates: + + * `F` in [`MOI.HyperRectangle{T}`](@ref). +""" +mutable struct IntervalToHyperRectangleBridge{T,F,G} <: AbstractBridge + vector_constraint::MOI.ConstraintIndex{F,MOI.HyperRectangle{T}} +end + +const IntervalToHyperRectangle{T,OT<:MOI.ModelLike} = + SingleBridgeOptimizer{IntervalToHyperRectangleBridge{T},OT} + +function bridge_constraint( + ::Type{IntervalToHyperRectangleBridge{T,F,G}}, + model::MOI.ModelLike, + scalar_f::G, + set::MOI.Interval{T}, +) where {T,F,G} + MOI.throw_if_scalar_and_constant_not_zero(scalar_f, typeof(set)) + vector_f = MOI.Utilities.operate(vcat, T, scalar_f) + rect = MOI.HyperRectangle([set.lower], [set.upper]) + vector_constraint = MOI.add_constraint(model, vector_f, rect) + return IntervalToHyperRectangleBridge{T,F,G}(vector_constraint) +end + +function MOI.supports_constraint( + ::Type{IntervalToHyperRectangleBridge{T}}, + ::Type{F}, + ::Type{MOI.Interval{T}}, +) where {T,F<:MOI.AbstractScalarFunction} + return MOI.Utilities.is_coefficient_type(F, T) +end + +function MOI.Bridges.added_constrained_variable_types(::Type{<:IntervalToHyperRectangleBridge}) + return Tuple{Type}[] +end + +function MOI.Bridges.added_constraint_types( + ::Type{<:IntervalToHyperRectangleBridge{T,F}}, +) where {T,F} + return Tuple{Type,Type}[(F, MOI.HyperRectangle{T})] +end + +function concrete_bridge_type( + ::Type{<:IntervalToHyperRectangleBridge{T}}, + G::Type{<:MOI.AbstractScalarFunction}, + S::Type{MOI.Interval{T}}, +) where {T} + F = MOI.Utilities.promote_operation(vcat, T, G) + return IntervalToHyperRectangleBridge{T,F,G} +end + +function MOI.get( + ::IntervalToHyperRectangleBridge{T,F}, + ::MOI.NumberOfConstraints{F,MOI.HyperRectangle{T}}, +)::Int64 where {T,F} + return 1 +end + +function MOI.get( + bridge::IntervalToHyperRectangleBridge{T,F}, + ::MOI.ListOfConstraintIndices{F,MOI.HyperRectangle{T}}, +) where {T,F} + return [bridge.vector_constraint] +end + +function MOI.delete(model::MOI.ModelLike, bridge::IntervalToHyperRectangleBridge) + MOI.delete(model, bridge.vector_constraint) + return +end + +function MOI.supports( + model::MOI.ModelLike, + attr::Union{MOI.ConstraintPrimalStart,MOI.ConstraintDualStart}, + ::Type{IntervalToHyperRectangleBridge{T,F,G}}, +) where {T,F,G} + return MOI.supports(model, attr, MOI.ConstraintIndex{F,MOI.HyperRectangle{T}}) +end + +function MOI.get( + model::MOI.ModelLike, + attr::Union{MOI.ConstraintPrimal,MOI.ConstraintPrimalStart}, + bridge::IntervalToHyperRectangleBridge, +) + x = MOI.get(model, attr, bridge.vector_constraint) + if isnothing(x) + return nothing + end + return only(x) +end + +function MOI.set( + model::MOI.ModelLike, + attr::MOI.ConstraintPrimalStart, + bridge::IntervalToHyperRectangleBridge, + value, +) + MOI.set( + model, + attr, + bridge.vector_constraint, + [value], + ) + return +end + +function MOI.set( + model::MOI.ModelLike, + attr::MOI.ConstraintPrimalStart, + bridge::IntervalToHyperRectangleBridge, + ::Nothing, +) + MOI.set(model, attr, bridge.vector_constraint, nothing) + return +end + +function MOI.get( + model::MOI.ModelLike, + attr::Union{MOI.ConstraintDual,MOI.ConstraintDualStart}, + bridge::IntervalToHyperRectangleBridge, +) + x = MOI.get(model, attr, bridge.vector_constraint) + if isnothing(x) + return nothing + end + return only(x) +end + +function MOI.set( + model::MOI.ModelLike, + attr::MOI.ConstraintDualStart, + bridge::IntervalToHyperRectangleBridge, + value, +) + if isnothing(value) + MOI.set(model, attr, bridge.vector_constraint, nothing) + else + MOI.set(model, attr, bridge.vector_constraint, [value]) + end + return +end + +function MOI.modify( + model::MOI.ModelLike, + bridge::IntervalToHyperRectangleBridge, + change::MOI.ScalarCoefficientChange, +) + MOI.modify( + model, + bridge.vector_constraint, + MOI.MultirowChange(change.variable, [(1, change.new_coefficient)]), + ) + @show @__LINE__ + return +end + +function MOI.set( + model::MOI.ModelLike, + attr::MOI.ConstraintSet, + bridge::IntervalToHyperRectangleBridge, + new_set::MOI.Interval, +) + MOI.set( + model, + attr, + bridge.vector_constraint, + MOI.HyperRectangle([new_set.lower], [new_set.upper]), + ) + return +end + +function MOI.get( + model::MOI.ModelLike, + attr::MOI.ConstraintFunction, + bridge::IntervalToHyperRectangleBridge{T,F,G}, +) where {T,F,G} + return convert(G, only(MOI.Utilities.scalarize( + MOI.get(model, attr, bridge.vector_constraint), + ))) +end + +function MOI.get( + model::MOI.ModelLike, + ::MOI.ConstraintSet, + bridge::IntervalToHyperRectangleBridge, +) + rect = MOI.get(model, MOI.ConstraintSet(), bridge.vector_constraint) + return MOI.Interval(only(rect.lower), only(rect.upper)) +end diff --git a/test/Bridges/Constraint/IntervalToHyperRectangleBridge.jl b/test/Bridges/Constraint/IntervalToHyperRectangleBridge.jl new file mode 100644 index 0000000000..eeb2d0d26c --- /dev/null +++ b/test/Bridges/Constraint/IntervalToHyperRectangleBridge.jl @@ -0,0 +1,114 @@ +# 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 TestConstraintIntervalToHyperRectangle + +using Test + +import MathOptInterface as MOI + +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name) $T" for T in [Float32, Float64] + getfield(@__MODULE__, name)(T) + end + end + end + return +end + +include("../utilities.jl") + +function test_ScalarFunctionConstantNotZero(T) + mock = MOI.Utilities.MockOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}()), + ) + bridged_mock = MOI.Bridges.Constraint.IntervalToHyperRectangle{T}(mock) + config = MOI.Test.Config(T) + MOI.Test.test_model_ScalarFunctionConstantNotZero(bridged_mock, config) + return +end + +function test_basic(T) + mock = MOI.Utilities.MockOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}()), + ) + bridged_mock = MOI.Bridges.Constraint.IntervalToHyperRectangle{T}(mock) + config = MOI.Test.Config() + MOI.Test.runtests( + bridged_mock, + config, + include = ["test_basic_ScalarAffineFunction_Interval"], + ) + return +end + +function test_linear_integration(T) + mock = MOI.Utilities.MockOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}()), + ) + bridged_mock = MOI.Bridges.Constraint.IntervalToHyperRectangle{T}(mock) + config = MOI.Test.Config(T, exclude = Any[MOI.ConstraintBasisStatus]) + MOI.Utilities.set_mock_optimize!( + mock, + (mock::MOI.Utilities.MockOptimizer) -> MOI.Utilities.mock_optimize!( + mock, + T[5, 5], + (MOI.VectorAffineFunction{T}, MOI.HyperRectangle{T}) => [T[-1]], + ), + (mock::MOI.Utilities.MockOptimizer) -> MOI.Utilities.mock_optimize!( + mock, + T[5//2, 5//2], + (MOI.VectorAffineFunction{T}, MOI.HyperRectangle{T}) => [T[1]], + ), + (mock::MOI.Utilities.MockOptimizer) -> MOI.Utilities.mock_optimize!( + mock, + T[1, 1], + (MOI.VectorAffineFunction{T}, MOI.HyperRectangle{T}) => [T[1]], + ), + (mock::MOI.Utilities.MockOptimizer) -> MOI.Utilities.mock_optimize!( + mock, + T[6, 6], + (MOI.VectorAffineFunction{T}, MOI.HyperRectangle{T}) => [T[-1]], + ), + ) + MOI.Test.test_linear_integration_Interval(bridged_mock, config) + return +end + + +function test_runtests(T) + MOI.Bridges.runtests( + MOI.Bridges.Constraint.IntervalToHyperRectangleBridge, + model -> begin + x = MOI.add_variable(model) + MOI.add_constraint(model, x, MOI.Interval{T}(3, 5)) + end, + model -> begin + x = MOI.add_variable(model) + MOI.add_constraint(model, MOI.VectorOfVariables([x]), MOI.HyperRectangle(T[3], T[5])) + end, + eltype = T, + ) + MOI.Bridges.runtests( + MOI.Bridges.Constraint.IntervalToHyperRectangleBridge, + model -> begin + x = MOI.add_variable(model) + MOI.add_constraint(model, T(2) * x, MOI.Interval{T}(3, 5)) + end, + model -> begin + x = MOI.add_variable(model) + MOI.add_constraint(model, MOI.Utilities.vectorize([T(2) * x]), MOI.HyperRectangle(T[3], T[5])) + end, + eltype = T, + ) + return +end + +end # module + +TestConstraintIntervalToHyperRectangle.runtests() From de275dc8a2c770e2c06cc8d63f2b30609856c6a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 10 May 2025 09:30:09 +0200 Subject: [PATCH 2/3] Add ref from Vectorize bridge --- src/Bridges/Constraint/bridges/VectorizeBridge.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Bridges/Constraint/bridges/VectorizeBridge.jl b/src/Bridges/Constraint/bridges/VectorizeBridge.jl index fb61a94a72..50a1ea4854 100644 --- a/src/Bridges/Constraint/bridges/VectorizeBridge.jl +++ b/src/Bridges/Constraint/bridges/VectorizeBridge.jl @@ -15,6 +15,9 @@ where `T` is the coefficient type of `g(x) - a`. +See also [`IntervalToHyperRectangleBridge`](@ref) for double-sided bound +constraints. + ## Source node `VectorizeBridge` supports: From 7bd50ed9ee862b703aeef5da71123ff8434627f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 10 May 2025 11:09:06 +0200 Subject: [PATCH 3/3] Fix format --- .../bridges/IntervalToHyperRectangleBridge.jl | 33 ++++++++++++------- .../IntervalToHyperRectangleBridge.jl | 13 ++++++-- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/Bridges/Constraint/bridges/IntervalToHyperRectangleBridge.jl b/src/Bridges/Constraint/bridges/IntervalToHyperRectangleBridge.jl index 5a2fd8d258..3e9e5e5951 100644 --- a/src/Bridges/Constraint/bridges/IntervalToHyperRectangleBridge.jl +++ b/src/Bridges/Constraint/bridges/IntervalToHyperRectangleBridge.jl @@ -56,7 +56,9 @@ function MOI.supports_constraint( return MOI.Utilities.is_coefficient_type(F, T) end -function MOI.Bridges.added_constrained_variable_types(::Type{<:IntervalToHyperRectangleBridge}) +function MOI.Bridges.added_constrained_variable_types( + ::Type{<:IntervalToHyperRectangleBridge}, +) return Tuple{Type}[] end @@ -89,7 +91,10 @@ function MOI.get( return [bridge.vector_constraint] end -function MOI.delete(model::MOI.ModelLike, bridge::IntervalToHyperRectangleBridge) +function MOI.delete( + model::MOI.ModelLike, + bridge::IntervalToHyperRectangleBridge, +) MOI.delete(model, bridge.vector_constraint) return end @@ -99,7 +104,11 @@ function MOI.supports( attr::Union{MOI.ConstraintPrimalStart,MOI.ConstraintDualStart}, ::Type{IntervalToHyperRectangleBridge{T,F,G}}, ) where {T,F,G} - return MOI.supports(model, attr, MOI.ConstraintIndex{F,MOI.HyperRectangle{T}}) + return MOI.supports( + model, + attr, + MOI.ConstraintIndex{F,MOI.HyperRectangle{T}}, + ) end function MOI.get( @@ -120,12 +129,7 @@ function MOI.set( bridge::IntervalToHyperRectangleBridge, value, ) - MOI.set( - model, - attr, - bridge.vector_constraint, - [value], - ) + MOI.set(model, attr, bridge.vector_constraint, [value]) return end @@ -199,9 +203,14 @@ function MOI.get( attr::MOI.ConstraintFunction, bridge::IntervalToHyperRectangleBridge{T,F,G}, ) where {T,F,G} - return convert(G, only(MOI.Utilities.scalarize( - MOI.get(model, attr, bridge.vector_constraint), - ))) + return convert( + G, + only( + MOI.Utilities.scalarize( + MOI.get(model, attr, bridge.vector_constraint), + ), + ), + ) end function MOI.get( diff --git a/test/Bridges/Constraint/IntervalToHyperRectangleBridge.jl b/test/Bridges/Constraint/IntervalToHyperRectangleBridge.jl index eeb2d0d26c..80c36c624e 100644 --- a/test/Bridges/Constraint/IntervalToHyperRectangleBridge.jl +++ b/test/Bridges/Constraint/IntervalToHyperRectangleBridge.jl @@ -80,7 +80,6 @@ function test_linear_integration(T) return end - function test_runtests(T) MOI.Bridges.runtests( MOI.Bridges.Constraint.IntervalToHyperRectangleBridge, @@ -90,7 +89,11 @@ function test_runtests(T) end, model -> begin x = MOI.add_variable(model) - MOI.add_constraint(model, MOI.VectorOfVariables([x]), MOI.HyperRectangle(T[3], T[5])) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x]), + MOI.HyperRectangle(T[3], T[5]), + ) end, eltype = T, ) @@ -102,7 +105,11 @@ function test_runtests(T) end, model -> begin x = MOI.add_variable(model) - MOI.add_constraint(model, MOI.Utilities.vectorize([T(2) * x]), MOI.HyperRectangle(T[3], T[5])) + MOI.add_constraint( + model, + MOI.Utilities.vectorize([T(2) * x]), + MOI.HyperRectangle(T[3], T[5]), + ) end, eltype = T, )