Skip to content

Commit

Permalink
Add Bridges.Constraint.InequalityToComplementsBridge (#2582)
Browse files Browse the repository at this point in the history
  • Loading branch information
odow authored Nov 14, 2024
1 parent f6f8a09 commit 02dc52c
Show file tree
Hide file tree
Showing 3 changed files with 326 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/Bridges/Constraint/Constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
151 changes: 151 additions & 0 deletions src/Bridges/Constraint/bridges/inequality_to_complements.jl
Original file line number Diff line number Diff line change
@@ -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 ``\\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``
## 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)
* `G` 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{F},
::Type{<:Union{MOI.GreaterThan{T},MOI.LessThan{T},MOI.EqualTo{T}}},
) where {T,F<:MOI.AbstractScalarFunction}
return !MOI.Utilities.is_complex(F)
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
173 changes: 173 additions & 0 deletions test/Bridges/Constraint/inequality_to_complements.jl
Original file line number Diff line number Diff line change
@@ -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()

0 comments on commit 02dc52c

Please sign in to comment.