From a0b3e3a3ad013916c4da52545734e782267b8850 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 14 Nov 2024 16:27:51 +1300 Subject: [PATCH 1/6] Add Bridges.Constraint.InequalityToComplementsBridge --- src/Bridges/Constraint/Constraint.jl | 2 + .../bridges/inequality_to_complements.jl | 151 +++++++++++++++ .../Constraint/inequality_to_complements.jl | 173 ++++++++++++++++++ 3 files changed, 326 insertions(+) create mode 100644 src/Bridges/Constraint/bridges/inequality_to_complements.jl create mode 100644 test/Bridges/Constraint/inequality_to_complements.jl diff --git a/src/Bridges/Constraint/Constraint.jl b/src/Bridges/Constraint/Constraint.jl index 65af0f4d02..d9d3c84074 100644 --- a/src/Bridges/Constraint/Constraint.jl +++ b/src/Bridges/Constraint/Constraint.jl @@ -36,6 +36,7 @@ include("bridges/geomean.jl") include("bridges/indicator_activate_on_zero.jl") include("bridges/indicator_flipsign.jl") include("bridges/indicator_sos.jl") +include("bridges/inequality_to_complements.jl") include("bridges/integer_to_zeroone.jl") include("bridges/interval.jl") include("bridges/ltgt_to_interval.jl") @@ -135,6 +136,7 @@ function add_all_bridges(bridged_model, ::Type{T}) where {T} MOI.Bridges.add_bridge(bridged_model, SemiToBinaryBridge{T}) MOI.Bridges.add_bridge(bridged_model, ZeroOneBridge{T}) MOI.Bridges.add_bridge(bridged_model, IntegerToZeroOneBridge{T}) + MOI.Bridges.add_bridge(bridged_model, InequalityToComplementsBridge{T}) # Do not add by default # MOI.Bridges.add_bridge(bridged_model, NumberConversionBridge{T}) # Constraint programming bridges diff --git a/src/Bridges/Constraint/bridges/inequality_to_complements.jl b/src/Bridges/Constraint/bridges/inequality_to_complements.jl new file mode 100644 index 0000000000..73c9725545 --- /dev/null +++ b/src/Bridges/Constraint/bridges/inequality_to_complements.jl @@ -0,0 +1,151 @@ +# 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. + +""" + InequalityToComplementsBridge{T,F,S,G} <: Bridges.Constraint.AbstractBridge + +`InequalityToComplementsBridge` implements the following reformulations: + + * ``f(x) \\ge b`` into ``f(x) - b \\perp y \\ge 0`` + * ``f(x) \\le b`` into ``f(x) - b \\perp y \\le 0`` + * ``f(x) = b`` into ``f(x) - b \\perp y`` + +## Source node + +`InequalityToComplementsBridge` supports: + + * `F` in [`MOI.GreaterThan{T}`](@ref) + * `F` in [`MOI.LessThan{T}`](@ref) + * `F` in [`MOI.EqualTo`](@ref) + +## Target nodes + +`InequalityToComplementsBridge` creates: + + * [`MOI.VariableIndex`](@ref) in [`MOI.LessThan{T}`](@ref) + * [`MOI.VariableIndex`](@ref) in [`MOI.GreaterThan{T}`](@ref) + * `F` in [`MOI.Complements`](@ref) +""" +mutable struct InequalityToComplementsBridge{ + T, + F<:MOI.AbstractScalarFunction, + S<:Union{MOI.GreaterThan{T},MOI.LessThan{T},MOI.EqualTo{T}}, + G<:MOI.AbstractVectorFunction, +} <: AbstractBridge + y::MOI.VariableIndex + set::S + ci::MOI.ConstraintIndex{G,MOI.Complements} +end + +const InequalityToComplements{T,OT<:MOI.ModelLike} = + SingleBridgeOptimizer{InequalityToComplementsBridge{T},OT} + +function _add_y_variable(model, ::MOI.GreaterThan{T}) where {T} + return MOI.add_constrained_variable(model, MOI.GreaterThan(zero(T)))[1] +end + +function _add_y_variable(model, ::MOI.LessThan{T}) where {T} + return MOI.add_constrained_variable(model, MOI.LessThan(zero(T)))[1] +end + +_add_y_variable(model, ::MOI.EqualTo) = MOI.add_variable(model) + +function bridge_constraint( + ::Type{InequalityToComplementsBridge{T,F,S,G}}, + model::MOI.ModelLike, + f::F, + set::S, +) where {T,F,S<:Union{MOI.GreaterThan{T},MOI.LessThan{T},MOI.EqualTo{T}},G} + y = _add_y_variable(model, set) + f_set = MOI.Utilities.operate(-, T, f, MOI.constant(set)) + g = MOI.Utilities.operate(vcat, T, f_set, y) + ci = MOI.add_constraint(model, g, MOI.Complements(2)) + return InequalityToComplementsBridge{T,F,S,G}(y, set, ci) +end + +function MOI.supports_constraint( + ::Type{<:InequalityToComplementsBridge{T}}, + ::Type{<:MOI.AbstractScalarFunction}, + ::Type{<:Union{MOI.GreaterThan{T},MOI.LessThan{T},MOI.EqualTo{T}}}, +) where {T} + return true +end + +function MOI.Bridges.added_constrained_variable_types( + ::Type{InequalityToComplementsBridge{T,F,S,G}}, +) where {T,F,S<:Union{MOI.GreaterThan{T},MOI.LessThan{T}},G} + return Tuple{Type}[(S,)] +end + +function MOI.Bridges.added_constrained_variable_types( + ::Type{InequalityToComplementsBridge{T,F,MOI.EqualTo{T},G}}, +) where {T,F,G} + return Tuple{Type}[(MOI.Reals,)] +end + +function MOI.Bridges.added_constraint_types( + ::Type{InequalityToComplementsBridge{T,F,S,G}}, +) where {T,F,S,G} + return Tuple{Type,Type}[(G,MOI.Complements)] +end + +function concrete_bridge_type( + ::Type{<:InequalityToComplementsBridge}, + F::Type{<:MOI.AbstractScalarFunction}, + S::Type{<:Union{MOI.GreaterThan{T},MOI.LessThan{T},MOI.EqualTo{T}}}, +) where {T} + G = MOI.Utilities.promote_operation(vcat, T, F, MOI.VariableIndex) + return InequalityToComplementsBridge{T,F,S,G} +end + +function MOI.get(::InequalityToComplementsBridge, ::MOI.NumberOfVariables) + return Int64(1) +end + +function MOI.get( + bridge::InequalityToComplementsBridge, + ::MOI.ListOfVariableIndices, +) + return [bridge.y] +end + +function MOI.get( + ::InequalityToComplementsBridge{T,F,S,G}, + ::MOI.NumberOfConstraints{G,MOI.Complements}, +)::Int64 where {T,F,S,G} + return 1 +end + +function MOI.get( + bridge::InequalityToComplementsBridge{T,F,S,G}, + ::MOI.ListOfConstraintIndices{G,MOI.Complements}, +) where {T,F,S,G} + return [bridge.ci] +end + +function MOI.delete(model::MOI.ModelLike, bridge::InequalityToComplementsBridge) + MOI.delete(model, bridge.y) + MOI.delete(model, bridge.ci) + return +end + +function MOI.get( + model::MOI.ModelLike, + attr::MOI.ConstraintFunction, + bridge::InequalityToComplementsBridge{T}, +) where {T} + g = MOI.get(model, attr, bridge.ci) + f_set = first(MOI.Utilities.scalarize(g)) + return MOI.Utilities.operate(+, T, f_set, MOI.constant(bridge.set)) +end + +function MOI.get( + ::MOI.ModelLike, + ::MOI.ConstraintSet, + bridge::InequalityToComplementsBridge, +) + return bridge.set +end diff --git a/test/Bridges/Constraint/inequality_to_complements.jl b/test/Bridges/Constraint/inequality_to_complements.jl new file mode 100644 index 0000000000..975816be29 --- /dev/null +++ b/test/Bridges/Constraint/inequality_to_complements.jl @@ -0,0 +1,173 @@ +# 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 TestConstraintInequalityToComplements + +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 + +function test_runtests_GreaterThan() + MOI.Bridges.runtests( + MOI.Bridges.Constraint.InequalityToComplementsBridge, + """ + variables: x + 1.0 * x >= 0.0 + """, + """ + variables: x, y + [1.0 * x, y] in Complements(2) + y >= 0.0 + """, + ) + MOI.Bridges.runtests( + MOI.Bridges.Constraint.InequalityToComplementsBridge, + """ + variables: x + 2.0 * x >= 3.0 + """, + """ + variables: x, y + [2.0 * x + -3.0, y] in Complements(2) + y >= 0.0 + """, + ) + MOI.Bridges.runtests( + MOI.Bridges.Constraint.InequalityToComplementsBridge, + """ + variables: x + 2.0 * x * x >= 3.0 + """, + """ + variables: x, y + [2.0 * x * x + -3.0, y] in Complements(2) + y >= 0.0 + """, + ) + return +end + +function test_runtests_LessThan() + MOI.Bridges.runtests( + MOI.Bridges.Constraint.InequalityToComplementsBridge, + """ + variables: x + 1.0 * x <= 0.0 + """, + """ + variables: x, y + [1.0 * x, y] in Complements(2) + y <= 0.0 + """, + ) + MOI.Bridges.runtests( + MOI.Bridges.Constraint.InequalityToComplementsBridge, + """ + variables: x + 2.0 * x <= 3.0 + """, + """ + variables: x, y + [2.0 * x + -3.0, y] in Complements(2) + y <= 0.0 + """, + ) + MOI.Bridges.runtests( + MOI.Bridges.Constraint.InequalityToComplementsBridge, + """ + variables: x + 2.0 * x * x <= 3.0 + """, + """ + variables: x, y + [2.0 * x * x + -3.0, y] in Complements(2) + y <= 0.0 + """, + ) + return +end + +function test_runtests_EqualTo() + MOI.Bridges.runtests( + MOI.Bridges.Constraint.InequalityToComplementsBridge, + """ + variables: x + 1.0 * x == 0.0 + """, + """ + variables: x, y + [1.0 * x, y] in Complements(2) + """, + ) + MOI.Bridges.runtests( + MOI.Bridges.Constraint.InequalityToComplementsBridge, + """ + variables: x + 2.0 * x == 3.0 + """, + """ + variables: x, y + [2.0 * x + -3.0, y] in Complements(2) + """, + ) + MOI.Bridges.runtests( + MOI.Bridges.Constraint.InequalityToComplementsBridge, + """ + variables: x + 2.0 * x * x == 3.0 + """, + """ + variables: x, y + [2.0 * x * x + -3.0, y] in Complements(2) + """, + ) + return +end + +function test_ScalarNonlinearFunction() + # We can't use the standard runtests because ScalarNonlinearFunction does + # not preserve f(x) ≈ (f(x) - g(x)) + g(x) + for set in (MOI.EqualTo(1.0), MOI.LessThan(1.0), MOI.GreaterThan(1.0)) + inner = MOI.Utilities.Model{Float64}() + model = MOI.Bridges.Constraint.InequalityToComplements{Float64}(inner) + x = MOI.add_variable(model) + f = MOI.ScalarNonlinearFunction(:sin, Any[x]) + c = MOI.add_constraint(model, f, set) + F, S = MOI.VectorNonlinearFunction, MOI.Complements + indices = MOI.get(inner, MOI.ListOfConstraintIndices{F,S}()) + @test length(indices) == 1 + inner_variables = MOI.get(inner, MOI.ListOfVariableIndices()) + @test length(inner_variables) == 2 + u, v = inner_variables + u_sin = MOI.ScalarNonlinearFunction(:sin, Any[u]) + g = MOI.VectorNonlinearFunction([ + MOI.ScalarNonlinearFunction(:-, Any[u_sin, 1.0]), + MOI.ScalarNonlinearFunction(:+, Any[v]), + ]) + @test ≈(MOI.get(inner, MOI.ConstraintFunction(), indices[1]), g) + h = MOI.ScalarNonlinearFunction( + :+, + Any[MOI.ScalarNonlinearFunction(:-, Any[f, 1.0]), 1.0], + ) + @test ≈(MOI.get(model, MOI.ConstraintFunction(), c), h) + end + return +end + +end # module + +TestConstraintInequalityToComplements.runtests() From d018795b3c5e39a9815c0370a3aaadc6d68abdb3 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 14 Nov 2024 16:35:51 +1300 Subject: [PATCH 2/6] Update src/Bridges/Constraint/bridges/inequality_to_complements.jl --- src/Bridges/Constraint/bridges/inequality_to_complements.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/Constraint/bridges/inequality_to_complements.jl b/src/Bridges/Constraint/bridges/inequality_to_complements.jl index 73c9725545..b162b84af1 100644 --- a/src/Bridges/Constraint/bridges/inequality_to_complements.jl +++ b/src/Bridges/Constraint/bridges/inequality_to_complements.jl @@ -89,7 +89,7 @@ end function MOI.Bridges.added_constraint_types( ::Type{InequalityToComplementsBridge{T,F,S,G}}, ) where {T,F,S,G} - return Tuple{Type,Type}[(G,MOI.Complements)] + return Tuple{Type,Type}[(G, MOI.Complements)] end function concrete_bridge_type( From 0271c060c121607c45acf0564a2ee7739819a64f Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 14 Nov 2024 17:55:12 +1300 Subject: [PATCH 3/6] Update --- src/Bridges/Constraint/bridges/inequality_to_complements.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Bridges/Constraint/bridges/inequality_to_complements.jl b/src/Bridges/Constraint/bridges/inequality_to_complements.jl index b162b84af1..f7dc238bb8 100644 --- a/src/Bridges/Constraint/bridges/inequality_to_complements.jl +++ b/src/Bridges/Constraint/bridges/inequality_to_complements.jl @@ -68,10 +68,10 @@ end function MOI.supports_constraint( ::Type{<:InequalityToComplementsBridge{T}}, - ::Type{<:MOI.AbstractScalarFunction}, + ::Type{F}, ::Type{<:Union{MOI.GreaterThan{T},MOI.LessThan{T},MOI.EqualTo{T}}}, -) where {T} - return true +) where {F<:MOI.AbstractScalarFunction} + return !MOI.Utilities.is_complex(F) end function MOI.Bridges.added_constrained_variable_types( From 18352c479c91ef25593ffe12d70c7d651b0651a5 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 14 Nov 2024 19:46:54 +1300 Subject: [PATCH 4/6] Update src/Bridges/Constraint/bridges/inequality_to_complements.jl --- src/Bridges/Constraint/bridges/inequality_to_complements.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/Constraint/bridges/inequality_to_complements.jl b/src/Bridges/Constraint/bridges/inequality_to_complements.jl index f7dc238bb8..9f8822cdbe 100644 --- a/src/Bridges/Constraint/bridges/inequality_to_complements.jl +++ b/src/Bridges/Constraint/bridges/inequality_to_complements.jl @@ -70,7 +70,7 @@ function MOI.supports_constraint( ::Type{<:InequalityToComplementsBridge{T}}, ::Type{F}, ::Type{<:Union{MOI.GreaterThan{T},MOI.LessThan{T},MOI.EqualTo{T}}}, -) where {F<:MOI.AbstractScalarFunction} +) where {T,F<:MOI.AbstractScalarFunction} return !MOI.Utilities.is_complex(F) end From e7297f3ceb675b40e92b1d6fb5acc9cbba81729f Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 14 Nov 2024 20:22:45 +1300 Subject: [PATCH 5/6] Update src/Bridges/Constraint/bridges/inequality_to_complements.jl --- src/Bridges/Constraint/bridges/inequality_to_complements.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/Constraint/bridges/inequality_to_complements.jl b/src/Bridges/Constraint/bridges/inequality_to_complements.jl index 9f8822cdbe..d2c418c746 100644 --- a/src/Bridges/Constraint/bridges/inequality_to_complements.jl +++ b/src/Bridges/Constraint/bridges/inequality_to_complements.jl @@ -27,7 +27,7 @@ * [`MOI.VariableIndex`](@ref) in [`MOI.LessThan{T}`](@ref) * [`MOI.VariableIndex`](@ref) in [`MOI.GreaterThan{T}`](@ref) - * `F` in [`MOI.Complements`](@ref) + * `G` in [`MOI.Complements`](@ref) """ mutable struct InequalityToComplementsBridge{ T, From 7907001ac5a523c7cfab0c4ce702459d59e75eae Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Fri, 15 Nov 2024 08:14:54 +1300 Subject: [PATCH 6/6] Update src/Bridges/Constraint/bridges/inequality_to_complements.jl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Benoît Legat --- src/Bridges/Constraint/bridges/inequality_to_complements.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/Constraint/bridges/inequality_to_complements.jl b/src/Bridges/Constraint/bridges/inequality_to_complements.jl index d2c418c746..c0850f3bcb 100644 --- a/src/Bridges/Constraint/bridges/inequality_to_complements.jl +++ b/src/Bridges/Constraint/bridges/inequality_to_complements.jl @@ -9,7 +9,7 @@ `InequalityToComplementsBridge` implements the following reformulations: - * ``f(x) \\ge b`` into ``f(x) - b \\perp y \\ge 0`` + * ``f(x) \\ge b`` into ``\\exists y`` such that ``f(x) - b \\perp y \\ge 0`` * ``f(x) \\le b`` into ``f(x) - b \\perp y \\le 0`` * ``f(x) = b`` into ``f(x) - b \\perp y``