Skip to content

Commit dab02e5

Browse files
authored
[Bridges] add IntegerToZeroOneBridge (#2205)
1 parent 5ac9cbb commit dab02e5

File tree

4 files changed

+280
-0
lines changed

4 files changed

+280
-0
lines changed

docs/src/submodules/Bridges/list_of_bridges.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Bridges.Constraint.IndicatorLessToGreaterThanBridge
6464
Bridges.Constraint.IndicatorSOS1Bridge
6565
Bridges.Constraint.SemiToBinaryBridge
6666
Bridges.Constraint.ZeroOneBridge
67+
Bridges.Constraint.IntegerToZeroOneBridge
6768
Bridges.Constraint.NumberConversionBridge
6869
Bridges.Constraint.AllDifferentToCountDistinctBridge
6970
Bridges.Constraint.ReifiedAllDifferentToCountDistinctBridge

src/Bridges/Constraint/Constraint.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ include("bridges/geomean.jl")
3535
include("bridges/indicator_activate_on_zero.jl")
3636
include("bridges/indicator_flipsign.jl")
3737
include("bridges/indicator_sos.jl")
38+
include("bridges/integer_to_zeroone.jl")
3839
include("bridges/interval.jl")
3940
include("bridges/ltgt_to_interval.jl")
4041
include("bridges/norm_infinity.jl")
@@ -128,6 +129,7 @@ function add_all_bridges(bridged_model, ::Type{T}) where {T}
128129
MOI.Bridges.add_bridge(bridged_model, IndicatorGreaterToLessThanBridge{T})
129130
MOI.Bridges.add_bridge(bridged_model, SemiToBinaryBridge{T})
130131
MOI.Bridges.add_bridge(bridged_model, ZeroOneBridge{T})
132+
MOI.Bridges.add_bridge(bridged_model, IntegerToZeroOneBridge{T})
131133
# Do not add by default
132134
# MOI.Bridges.add_bridge(bridged_model, NumberConversionBridge{T})
133135
# Constraint programming bridges
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
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+
IntegerToZeroOneBridge{T} <: Bridges.Constraint.AbstractBridge
9+
10+
`IntegerToZeroOneBridge` implements the following reformulation:
11+
12+
* ``x \\in \\mathbf{Z}`` into ``y_i \\in \\{0, 1\\}``,
13+
``x == lb + \\sum 2^{i-1} y_i``.
14+
15+
## Source node
16+
17+
`IntegerToZeroOneBridge` supports:
18+
19+
* `VariableIndex` in [`MOI.Integer`](@ref)
20+
21+
## Target nodes
22+
23+
`IntegerToZeroOneBridge` creates:
24+
25+
* [`MOI.VariableIndex`](@ref) in [`MOI.ZeroOne`](@ref)
26+
* [`MOI.ScalarAffineFunction{T}`](@ref) in [`MOI.EqualTo{T}`](@ref)
27+
28+
## Developer note
29+
30+
This bridge is implemented as a constraint bridge instead of a variable bridge
31+
because we don't want to substitute the linear combination of `y` for every
32+
instance of `x`. Doing so would be expensive and greatly reduce the sparsity of
33+
the constraints.
34+
"""
35+
mutable struct IntegerToZeroOneBridge{T} <: AbstractBridge
36+
x::MOI.VariableIndex
37+
y::Vector{MOI.VariableIndex}
38+
ci::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}
39+
last_bounds::Union{Nothing,NTuple{2,T}}
40+
41+
function IntegerToZeroOneBridge{T}(x::MOI.VariableIndex) where {T}
42+
return new{T}(
43+
x,
44+
MOI.VariableIndex[],
45+
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}(0),
46+
nothing,
47+
)
48+
end
49+
end
50+
51+
const IntegerToZeroOne{T,OT<:MOI.ModelLike} =
52+
SingleBridgeOptimizer{IntegerToZeroOneBridge{T},OT}
53+
54+
function bridge_constraint(
55+
::Type{IntegerToZeroOneBridge{T}},
56+
::MOI.ModelLike,
57+
x::MOI.VariableIndex,
58+
::MOI.Integer,
59+
) where {T}
60+
# !!! info
61+
# Postpone creation until final_touch.
62+
return IntegerToZeroOneBridge{T}(x)
63+
end
64+
65+
function MOI.supports_constraint(
66+
::Type{IntegerToZeroOneBridge{T}},
67+
::Type{MOI.VariableIndex},
68+
::Type{MOI.Integer},
69+
) where {T}
70+
return true
71+
end
72+
73+
function MOI.Bridges.added_constrained_variable_types(
74+
::Type{<:IntegerToZeroOneBridge},
75+
)
76+
return Tuple{Type}[(MOI.ZeroOne,)]
77+
end
78+
79+
function MOI.Bridges.added_constraint_types(
80+
::Type{IntegerToZeroOneBridge{T}},
81+
) where {T}
82+
return Tuple{Type,Type}[(MOI.ScalarAffineFunction{T}, MOI.EqualTo{T})]
83+
end
84+
85+
function concrete_bridge_type(
86+
::Type{IntegerToZeroOneBridge{T}},
87+
::Type{MOI.VariableIndex},
88+
::Type{MOI.Integer},
89+
) where {T}
90+
return IntegerToZeroOneBridge{T}
91+
end
92+
93+
function MOI.get(
94+
::MOI.ModelLike,
95+
::MOI.ConstraintFunction,
96+
bridge::IntegerToZeroOneBridge,
97+
)
98+
return bridge.x
99+
end
100+
101+
function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet, ::IntegerToZeroOneBridge)
102+
return MOI.Integer()
103+
end
104+
105+
function MOI.delete(model::MOI.ModelLike, bridge::IntegerToZeroOneBridge)
106+
MOI.delete(model, bridge.ci)
107+
MOI.delete(model, bridge.y)
108+
return
109+
end
110+
111+
function MOI.get(bridge::IntegerToZeroOneBridge, ::MOI.NumberOfVariables)::Int64
112+
return length(bridge.y)
113+
end
114+
115+
function MOI.get(bridge::IntegerToZeroOneBridge, ::MOI.ListOfVariableIndices)
116+
return copy(bridge.y)
117+
end
118+
119+
function MOI.get(
120+
bridge::IntegerToZeroOneBridge,
121+
::MOI.NumberOfConstraints{MOI.VariableIndex,MOI.ZeroOne},
122+
)::Int64
123+
return length(bridge.y)
124+
end
125+
126+
function MOI.get(
127+
bridge::IntegerToZeroOneBridge,
128+
::MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.ZeroOne},
129+
)
130+
return map(bridge.y) do y
131+
return MOI.ConstraintIndex{MOI.VariableIndex,MOI.ZeroOne}(y.value)
132+
end
133+
end
134+
135+
function MOI.get(
136+
bridge::IntegerToZeroOneBridge{T},
137+
::MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}},
138+
)::Int64 where {T}
139+
return 1
140+
end
141+
142+
function MOI.get(
143+
bridge::IntegerToZeroOneBridge{T},
144+
::MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}},
145+
) where {T}
146+
return [bridge.ci]
147+
end
148+
149+
MOI.Bridges.needs_final_touch(::IntegerToZeroOneBridge) = true
150+
151+
function MOI.Bridges.final_touch(
152+
bridge::IntegerToZeroOneBridge{T},
153+
model::MOI.ModelLike,
154+
) where {T}
155+
ret = MOI.Utilities.get_bounds(model, T, bridge.x)
156+
if ret === bridge.last_bounds
157+
return nothing # final_touch already called
158+
elseif ret[1] == typemin(T) || ret[2] == typemax(T)
159+
error(
160+
"Unable to use IntegerToZeroOneBridge because the variable " *
161+
"$(bridge.x) has a non-finite domain",
162+
)
163+
end
164+
f = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(T(1), bridge.x)], T(0))
165+
lb, ub = ceil(Int, ret[1]), floor(Int, ret[2])
166+
N = floor(Int, log2(ub - lb)) + 1
167+
for i in 1:N
168+
y, _ = MOI.add_constrained_variable(model, MOI.ZeroOne())
169+
push!(bridge.y, y)
170+
push!(f.terms, MOI.ScalarAffineTerm(-(T(2)^(i - 1)), y))
171+
end
172+
bridge.ci = MOI.add_constraint(model, f, MOI.EqualTo{T}(lb))
173+
bridge.last_bounds = ret
174+
return
175+
end
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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 TestConstraintIntegerToZeroOne
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()
25+
MOI.Bridges.runtests(
26+
MOI.Bridges.Constraint.IntegerToZeroOneBridge,
27+
"""
28+
variables: x, z
29+
x in Integer()
30+
x in Interval(1.0, 3.0)
31+
z in ZeroOne()
32+
""",
33+
"""
34+
variables: x, z, y1, y2
35+
y1 in ZeroOne()
36+
y2 in ZeroOne()
37+
x + -1.0 * y1 + -2.0 * y2 == 1.0
38+
x in Interval(1.0, 3.0)
39+
z in ZeroOne()
40+
""",
41+
)
42+
MOI.Bridges.runtests(
43+
MOI.Bridges.Constraint.IntegerToZeroOneBridge,
44+
"""
45+
variables: x
46+
x in Integer()
47+
x in Interval(-1.0, 2.0)
48+
""",
49+
"""
50+
variables: x, y1, y2
51+
y1 in ZeroOne()
52+
y2 in ZeroOne()
53+
x + -1.0 * y1 + -2.0 * y2 == -1.0
54+
x in Interval(-1.0, 2.0)
55+
""",
56+
)
57+
MOI.Bridges.runtests(
58+
MOI.Bridges.Constraint.IntegerToZeroOneBridge,
59+
"""
60+
variables: x
61+
x in Integer()
62+
x in Interval(-2.0, 2.0)
63+
""",
64+
"""
65+
variables: x, y1, y2, y3
66+
y1 in ZeroOne()
67+
y2 in ZeroOne()
68+
y3 in ZeroOne()
69+
x + -1.0 * y1 + -2.0 * y2 + -4.0 * y3 == -2.0
70+
x in Interval(-2.0, 2.0)
71+
""",
72+
)
73+
return
74+
end
75+
76+
function test_finite_domain_error()
77+
inner = MOI.Utilities.Model{Int}()
78+
model = MOI.Bridges.Constraint.IntegerToZeroOne{Int}(inner)
79+
x, _ = MOI.add_constrained_variable(model, MOI.Integer())
80+
@test_throws(
81+
ErrorException(
82+
"Unable to use IntegerToZeroOneBridge because the variable " *
83+
"$(x) has a non-finite domain",
84+
),
85+
MOI.Bridges.final_touch(model),
86+
)
87+
return
88+
end
89+
90+
function test_final_touch_twice()
91+
inner = MOI.Utilities.Model{Int}()
92+
model = MOI.Bridges.Constraint.IntegerToZeroOne{Int}(inner)
93+
x, _ = MOI.add_constrained_variable(model, MOI.Integer())
94+
MOI.add_constraint(model, x, MOI.Interval(1, 3))
95+
MOI.Bridges.final_touch(model)
96+
MOI.Bridges.final_touch(model)
97+
return
98+
end
99+
100+
end # module
101+
102+
TestConstraintIntegerToZeroOne.runtests()

0 commit comments

Comments
 (0)