Skip to content

Commit 02dc52c

Browse files
authored
Add Bridges.Constraint.InequalityToComplementsBridge (#2582)
1 parent f6f8a09 commit 02dc52c

File tree

3 files changed

+326
-0
lines changed

3 files changed

+326
-0
lines changed

src/Bridges/Constraint/Constraint.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ include("bridges/geomean.jl")
3636
include("bridges/indicator_activate_on_zero.jl")
3737
include("bridges/indicator_flipsign.jl")
3838
include("bridges/indicator_sos.jl")
39+
include("bridges/inequality_to_complements.jl")
3940
include("bridges/integer_to_zeroone.jl")
4041
include("bridges/interval.jl")
4142
include("bridges/ltgt_to_interval.jl")
@@ -135,6 +136,7 @@ function add_all_bridges(bridged_model, ::Type{T}) where {T}
135136
MOI.Bridges.add_bridge(bridged_model, SemiToBinaryBridge{T})
136137
MOI.Bridges.add_bridge(bridged_model, ZeroOneBridge{T})
137138
MOI.Bridges.add_bridge(bridged_model, IntegerToZeroOneBridge{T})
139+
MOI.Bridges.add_bridge(bridged_model, InequalityToComplementsBridge{T})
138140
# Do not add by default
139141
# MOI.Bridges.add_bridge(bridged_model, NumberConversionBridge{T})
140142
# Constraint programming bridges
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# Copyright (c) 2017: Miles Lubin and contributors
2+
# Copyright (c) 2017: Google Inc.
3+
#
4+
# Use of this source code is governed by an MIT-style license that can be found
5+
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.
6+
7+
"""
8+
InequalityToComplementsBridge{T,F,S,G} <: Bridges.Constraint.AbstractBridge
9+
10+
`InequalityToComplementsBridge` implements the following reformulations:
11+
12+
* ``f(x) \\ge b`` into ``\\exists y`` such that ``f(x) - b \\perp y \\ge 0``
13+
* ``f(x) \\le b`` into ``f(x) - b \\perp y \\le 0``
14+
* ``f(x) = b`` into ``f(x) - b \\perp y``
15+
16+
## Source node
17+
18+
`InequalityToComplementsBridge` supports:
19+
20+
* `F` in [`MOI.GreaterThan{T}`](@ref)
21+
* `F` in [`MOI.LessThan{T}`](@ref)
22+
* `F` in [`MOI.EqualTo`](@ref)
23+
24+
## Target nodes
25+
26+
`InequalityToComplementsBridge` creates:
27+
28+
* [`MOI.VariableIndex`](@ref) in [`MOI.LessThan{T}`](@ref)
29+
* [`MOI.VariableIndex`](@ref) in [`MOI.GreaterThan{T}`](@ref)
30+
* `G` in [`MOI.Complements`](@ref)
31+
"""
32+
mutable struct InequalityToComplementsBridge{
33+
T,
34+
F<:MOI.AbstractScalarFunction,
35+
S<:Union{MOI.GreaterThan{T},MOI.LessThan{T},MOI.EqualTo{T}},
36+
G<:MOI.AbstractVectorFunction,
37+
} <: AbstractBridge
38+
y::MOI.VariableIndex
39+
set::S
40+
ci::MOI.ConstraintIndex{G,MOI.Complements}
41+
end
42+
43+
const InequalityToComplements{T,OT<:MOI.ModelLike} =
44+
SingleBridgeOptimizer{InequalityToComplementsBridge{T},OT}
45+
46+
function _add_y_variable(model, ::MOI.GreaterThan{T}) where {T}
47+
return MOI.add_constrained_variable(model, MOI.GreaterThan(zero(T)))[1]
48+
end
49+
50+
function _add_y_variable(model, ::MOI.LessThan{T}) where {T}
51+
return MOI.add_constrained_variable(model, MOI.LessThan(zero(T)))[1]
52+
end
53+
54+
_add_y_variable(model, ::MOI.EqualTo) = MOI.add_variable(model)
55+
56+
function bridge_constraint(
57+
::Type{InequalityToComplementsBridge{T,F,S,G}},
58+
model::MOI.ModelLike,
59+
f::F,
60+
set::S,
61+
) where {T,F,S<:Union{MOI.GreaterThan{T},MOI.LessThan{T},MOI.EqualTo{T}},G}
62+
y = _add_y_variable(model, set)
63+
f_set = MOI.Utilities.operate(-, T, f, MOI.constant(set))
64+
g = MOI.Utilities.operate(vcat, T, f_set, y)
65+
ci = MOI.add_constraint(model, g, MOI.Complements(2))
66+
return InequalityToComplementsBridge{T,F,S,G}(y, set, ci)
67+
end
68+
69+
function MOI.supports_constraint(
70+
::Type{<:InequalityToComplementsBridge{T}},
71+
::Type{F},
72+
::Type{<:Union{MOI.GreaterThan{T},MOI.LessThan{T},MOI.EqualTo{T}}},
73+
) where {T,F<:MOI.AbstractScalarFunction}
74+
return !MOI.Utilities.is_complex(F)
75+
end
76+
77+
function MOI.Bridges.added_constrained_variable_types(
78+
::Type{InequalityToComplementsBridge{T,F,S,G}},
79+
) where {T,F,S<:Union{MOI.GreaterThan{T},MOI.LessThan{T}},G}
80+
return Tuple{Type}[(S,)]
81+
end
82+
83+
function MOI.Bridges.added_constrained_variable_types(
84+
::Type{InequalityToComplementsBridge{T,F,MOI.EqualTo{T},G}},
85+
) where {T,F,G}
86+
return Tuple{Type}[(MOI.Reals,)]
87+
end
88+
89+
function MOI.Bridges.added_constraint_types(
90+
::Type{InequalityToComplementsBridge{T,F,S,G}},
91+
) where {T,F,S,G}
92+
return Tuple{Type,Type}[(G, MOI.Complements)]
93+
end
94+
95+
function concrete_bridge_type(
96+
::Type{<:InequalityToComplementsBridge},
97+
F::Type{<:MOI.AbstractScalarFunction},
98+
S::Type{<:Union{MOI.GreaterThan{T},MOI.LessThan{T},MOI.EqualTo{T}}},
99+
) where {T}
100+
G = MOI.Utilities.promote_operation(vcat, T, F, MOI.VariableIndex)
101+
return InequalityToComplementsBridge{T,F,S,G}
102+
end
103+
104+
function MOI.get(::InequalityToComplementsBridge, ::MOI.NumberOfVariables)
105+
return Int64(1)
106+
end
107+
108+
function MOI.get(
109+
bridge::InequalityToComplementsBridge,
110+
::MOI.ListOfVariableIndices,
111+
)
112+
return [bridge.y]
113+
end
114+
115+
function MOI.get(
116+
::InequalityToComplementsBridge{T,F,S,G},
117+
::MOI.NumberOfConstraints{G,MOI.Complements},
118+
)::Int64 where {T,F,S,G}
119+
return 1
120+
end
121+
122+
function MOI.get(
123+
bridge::InequalityToComplementsBridge{T,F,S,G},
124+
::MOI.ListOfConstraintIndices{G,MOI.Complements},
125+
) where {T,F,S,G}
126+
return [bridge.ci]
127+
end
128+
129+
function MOI.delete(model::MOI.ModelLike, bridge::InequalityToComplementsBridge)
130+
MOI.delete(model, bridge.y)
131+
MOI.delete(model, bridge.ci)
132+
return
133+
end
134+
135+
function MOI.get(
136+
model::MOI.ModelLike,
137+
attr::MOI.ConstraintFunction,
138+
bridge::InequalityToComplementsBridge{T},
139+
) where {T}
140+
g = MOI.get(model, attr, bridge.ci)
141+
f_set = first(MOI.Utilities.scalarize(g))
142+
return MOI.Utilities.operate(+, T, f_set, MOI.constant(bridge.set))
143+
end
144+
145+
function MOI.get(
146+
::MOI.ModelLike,
147+
::MOI.ConstraintSet,
148+
bridge::InequalityToComplementsBridge,
149+
)
150+
return bridge.set
151+
end
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# Copyright (c) 2017: Miles Lubin and contributors
2+
# Copyright (c) 2017: Google Inc.
3+
#
4+
# Use of this source code is governed by an MIT-style license that can be found
5+
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.
6+
7+
module TestConstraintInequalityToComplements
8+
9+
using Test
10+
11+
import MathOptInterface as MOI
12+
13+
function runtests()
14+
for name in names(@__MODULE__; all = true)
15+
if startswith("$(name)", "test_")
16+
@testset "$(name)" begin
17+
getfield(@__MODULE__, name)()
18+
end
19+
end
20+
end
21+
return
22+
end
23+
24+
function test_runtests_GreaterThan()
25+
MOI.Bridges.runtests(
26+
MOI.Bridges.Constraint.InequalityToComplementsBridge,
27+
"""
28+
variables: x
29+
1.0 * x >= 0.0
30+
""",
31+
"""
32+
variables: x, y
33+
[1.0 * x, y] in Complements(2)
34+
y >= 0.0
35+
""",
36+
)
37+
MOI.Bridges.runtests(
38+
MOI.Bridges.Constraint.InequalityToComplementsBridge,
39+
"""
40+
variables: x
41+
2.0 * x >= 3.0
42+
""",
43+
"""
44+
variables: x, y
45+
[2.0 * x + -3.0, y] in Complements(2)
46+
y >= 0.0
47+
""",
48+
)
49+
MOI.Bridges.runtests(
50+
MOI.Bridges.Constraint.InequalityToComplementsBridge,
51+
"""
52+
variables: x
53+
2.0 * x * x >= 3.0
54+
""",
55+
"""
56+
variables: x, y
57+
[2.0 * x * x + -3.0, y] in Complements(2)
58+
y >= 0.0
59+
""",
60+
)
61+
return
62+
end
63+
64+
function test_runtests_LessThan()
65+
MOI.Bridges.runtests(
66+
MOI.Bridges.Constraint.InequalityToComplementsBridge,
67+
"""
68+
variables: x
69+
1.0 * x <= 0.0
70+
""",
71+
"""
72+
variables: x, y
73+
[1.0 * x, y] in Complements(2)
74+
y <= 0.0
75+
""",
76+
)
77+
MOI.Bridges.runtests(
78+
MOI.Bridges.Constraint.InequalityToComplementsBridge,
79+
"""
80+
variables: x
81+
2.0 * x <= 3.0
82+
""",
83+
"""
84+
variables: x, y
85+
[2.0 * x + -3.0, y] in Complements(2)
86+
y <= 0.0
87+
""",
88+
)
89+
MOI.Bridges.runtests(
90+
MOI.Bridges.Constraint.InequalityToComplementsBridge,
91+
"""
92+
variables: x
93+
2.0 * x * x <= 3.0
94+
""",
95+
"""
96+
variables: x, y
97+
[2.0 * x * x + -3.0, y] in Complements(2)
98+
y <= 0.0
99+
""",
100+
)
101+
return
102+
end
103+
104+
function test_runtests_EqualTo()
105+
MOI.Bridges.runtests(
106+
MOI.Bridges.Constraint.InequalityToComplementsBridge,
107+
"""
108+
variables: x
109+
1.0 * x == 0.0
110+
""",
111+
"""
112+
variables: x, y
113+
[1.0 * x, y] in Complements(2)
114+
""",
115+
)
116+
MOI.Bridges.runtests(
117+
MOI.Bridges.Constraint.InequalityToComplementsBridge,
118+
"""
119+
variables: x
120+
2.0 * x == 3.0
121+
""",
122+
"""
123+
variables: x, y
124+
[2.0 * x + -3.0, y] in Complements(2)
125+
""",
126+
)
127+
MOI.Bridges.runtests(
128+
MOI.Bridges.Constraint.InequalityToComplementsBridge,
129+
"""
130+
variables: x
131+
2.0 * x * x == 3.0
132+
""",
133+
"""
134+
variables: x, y
135+
[2.0 * x * x + -3.0, y] in Complements(2)
136+
""",
137+
)
138+
return
139+
end
140+
141+
function test_ScalarNonlinearFunction()
142+
# We can't use the standard runtests because ScalarNonlinearFunction does
143+
# not preserve f(x) ≈ (f(x) - g(x)) + g(x)
144+
for set in (MOI.EqualTo(1.0), MOI.LessThan(1.0), MOI.GreaterThan(1.0))
145+
inner = MOI.Utilities.Model{Float64}()
146+
model = MOI.Bridges.Constraint.InequalityToComplements{Float64}(inner)
147+
x = MOI.add_variable(model)
148+
f = MOI.ScalarNonlinearFunction(:sin, Any[x])
149+
c = MOI.add_constraint(model, f, set)
150+
F, S = MOI.VectorNonlinearFunction, MOI.Complements
151+
indices = MOI.get(inner, MOI.ListOfConstraintIndices{F,S}())
152+
@test length(indices) == 1
153+
inner_variables = MOI.get(inner, MOI.ListOfVariableIndices())
154+
@test length(inner_variables) == 2
155+
u, v = inner_variables
156+
u_sin = MOI.ScalarNonlinearFunction(:sin, Any[u])
157+
g = MOI.VectorNonlinearFunction([
158+
MOI.ScalarNonlinearFunction(:-, Any[u_sin, 1.0]),
159+
MOI.ScalarNonlinearFunction(:+, Any[v]),
160+
])
161+
@test (MOI.get(inner, MOI.ConstraintFunction(), indices[1]), g)
162+
h = MOI.ScalarNonlinearFunction(
163+
:+,
164+
Any[MOI.ScalarNonlinearFunction(:-, Any[f, 1.0]), 1.0],
165+
)
166+
@test (MOI.get(model, MOI.ConstraintFunction(), c), h)
167+
end
168+
return
169+
end
170+
171+
end # module
172+
173+
TestConstraintInequalityToComplements.runtests()

0 commit comments

Comments
 (0)