Skip to content

Commit 1bea7e5

Browse files
authored
[Bridges] add CountGreaterThanToMILPBridge (#1927)
1 parent 3c0d875 commit 1bea7e5

File tree

5 files changed

+448
-0
lines changed

5 files changed

+448
-0
lines changed

docs/src/submodules/Bridges/list_of_bridges.md

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ Bridges.Constraint.BinPackingToMILPBridge
5656
Bridges.Constraint.CountAtLeastToCountBelongsBridge
5757
Bridges.Constraint.CountBelongsToMILPBridge
5858
Bridges.Constraint.CountDistinctToMILPBridge
59+
Bridges.Constraint.CountGreaterThanToMILPBridge
5960
Bridges.Constraint.TableToMILPBridge
6061
```
6162

src/Bridges/Constraint/Constraint.jl

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ include("bridges/bin_packing.jl")
2323
include("bridges/count_at_least.jl")
2424
include("bridges/count_belongs.jl")
2525
include("bridges/count_distinct.jl")
26+
include("bridges/count_greater_than.jl")
2627
include("bridges/det.jl")
2728
include("bridges/flip_sign.jl")
2829
include("bridges/functionize.jl")
@@ -101,6 +102,7 @@ function add_all_bridges(bridged_model, ::Type{T}) where {T}
101102
MOI.Bridges.add_bridge(bridged_model, CountAtLeastToCountBelongsBridge{T})
102103
MOI.Bridges.add_bridge(bridged_model, CountBelongsToMILPBridge{T})
103104
MOI.Bridges.add_bridge(bridged_model, CountDistinctToMILPBridge{T})
105+
MOI.Bridges.add_bridge(bridged_model, CountGreaterThanToMILPBridge{T})
104106
MOI.Bridges.add_bridge(bridged_model, TableToMILPBridge{T})
105107
return
106108
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
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+
CountGreaterThanToMILPBridge{T,F} <: Bridges.Constraint.AbstractBridge
9+
10+
`CountGreaterThanToMILPBridge` implements the following reformulation:
11+
12+
* ``(c, y, x) \\in CountGreaterThan()`` into a mixed-integer linear program.
13+
14+
## Source node
15+
16+
`CountGreaterThanToMILPBridge` supports:
17+
18+
* `F` in [`MOI.CountGreaterThan`](@ref)
19+
20+
## Target nodes
21+
22+
`CountGreaterThanToMILPBridge` creates:
23+
24+
* [`MOI.VariableIndex`](@ref) in [`MOI.ZeroOne`](@ref)
25+
* [`MOI.ScalarAffineFunction{T}`](@ref) in [`MOI.EqualTo{T}`](@ref)
26+
* [`MOI.ScalarAffineFunction{T}`](@ref) in [`MOI.GreaterThan{T}`](@ref)
27+
"""
28+
struct CountGreaterThanToMILPBridge{
29+
T,
30+
F<:Union{MOI.VectorOfVariables,MOI.VectorAffineFunction{T}},
31+
} <: AbstractBridge
32+
f::F
33+
s::MOI.CountGreaterThan
34+
variables::Vector{MOI.VariableIndex}
35+
greater_than::Vector{
36+
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.GreaterThan{T}},
37+
}
38+
equal_to::Vector{
39+
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}},
40+
}
41+
end
42+
43+
const CountGreaterThanToMILP{T,OT<:MOI.ModelLike} =
44+
SingleBridgeOptimizer{CountGreaterThanToMILPBridge{T},OT}
45+
46+
function bridge_constraint(
47+
::Type{CountGreaterThanToMILPBridge{T,F}},
48+
model::MOI.ModelLike,
49+
f::F,
50+
s::MOI.CountGreaterThan,
51+
) where {T,F}
52+
return CountGreaterThanToMILPBridge{T,F}(
53+
f,
54+
s,
55+
MOI.VariableIndex[],
56+
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.GreaterThan{T}}[],
57+
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}[],
58+
)
59+
end
60+
61+
function MOI.supports_constraint(
62+
::Type{<:CountGreaterThanToMILPBridge{T}},
63+
::Type{F},
64+
::Type{MOI.CountGreaterThan},
65+
) where {T,F<:Union{MOI.VectorOfVariables,MOI.VectorAffineFunction{T}}}
66+
return true
67+
end
68+
69+
function MOI.Bridges.added_constrained_variable_types(
70+
::Type{<:CountGreaterThanToMILPBridge},
71+
)
72+
return Tuple{Type}[(MOI.ZeroOne,)]
73+
end
74+
75+
function MOI.Bridges.added_constraint_types(
76+
::Type{<:CountGreaterThanToMILPBridge{T}},
77+
) where {T}
78+
return Tuple{Type,Type}[
79+
(MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}),
80+
(MOI.ScalarAffineFunction{T}, MOI.GreaterThan{T}),
81+
]
82+
end
83+
84+
function concrete_bridge_type(
85+
::Type{<:CountGreaterThanToMILPBridge{T}},
86+
::Type{F},
87+
::Type{MOI.CountGreaterThan},
88+
) where {T,F<:Union{MOI.VectorOfVariables,MOI.VectorAffineFunction{T}}}
89+
return CountGreaterThanToMILPBridge{T,F}
90+
end
91+
92+
function MOI.get(
93+
::MOI.ModelLike,
94+
::MOI.ConstraintFunction,
95+
bridge::CountGreaterThanToMILPBridge,
96+
)
97+
return bridge.f
98+
end
99+
100+
function MOI.get(
101+
::MOI.ModelLike,
102+
::MOI.ConstraintSet,
103+
bridge::CountGreaterThanToMILPBridge,
104+
)
105+
return bridge.s
106+
end
107+
108+
function MOI.delete(model::MOI.ModelLike, bridge::CountGreaterThanToMILPBridge)
109+
MOI.delete.(model, bridge.greater_than)
110+
empty!(bridge.greater_than)
111+
MOI.delete.(model, bridge.equal_to)
112+
empty!(bridge.equal_to)
113+
MOI.delete.(model, bridge.variables)
114+
empty!(bridge.variables)
115+
return
116+
end
117+
118+
function MOI.get(
119+
bridge::CountGreaterThanToMILPBridge,
120+
::MOI.NumberOfVariables,
121+
)::Int64
122+
return length(bridge.variables)
123+
end
124+
125+
function MOI.get(
126+
bridge::CountGreaterThanToMILPBridge,
127+
::MOI.ListOfVariableIndices,
128+
)
129+
return copy(bridge.variables)
130+
end
131+
132+
function MOI.get(
133+
bridge::CountGreaterThanToMILPBridge,
134+
::MOI.NumberOfConstraints{MOI.VariableIndex,MOI.ZeroOne},
135+
)::Int64
136+
return length(bridge.variables)
137+
end
138+
139+
function MOI.get(
140+
bridge::CountGreaterThanToMILPBridge,
141+
::MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.ZeroOne},
142+
)
143+
return [
144+
MOI.ConstraintIndex{MOI.VariableIndex,MOI.ZeroOne}.(z.value) for
145+
z in bridge.variables
146+
]
147+
end
148+
149+
function MOI.get(
150+
bridge::CountGreaterThanToMILPBridge{T},
151+
::MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}},
152+
)::Int64 where {T}
153+
return length(bridge.equal_to)
154+
end
155+
156+
function MOI.get(
157+
bridge::CountGreaterThanToMILPBridge{T},
158+
::MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}},
159+
) where {T}
160+
return copy(bridge.equal_to)
161+
end
162+
163+
function MOI.get(
164+
bridge::CountGreaterThanToMILPBridge{T},
165+
::MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},MOI.GreaterThan{T}},
166+
)::Int64 where {T}
167+
return length(bridge.greater_than)
168+
end
169+
170+
function MOI.get(
171+
bridge::CountGreaterThanToMILPBridge{T},
172+
::MOI.ListOfConstraintIndices{
173+
MOI.ScalarAffineFunction{T},
174+
MOI.GreaterThan{T},
175+
},
176+
) where {T}
177+
return copy(bridge.greater_than)
178+
end
179+
180+
MOI.Bridges.needs_final_touch(::CountGreaterThanToMILPBridge) = true
181+
182+
# We use the bridge as the first argument to avoid type piracy of other methods.
183+
function _get_bounds(
184+
bridge::CountGreaterThanToMILPBridge{T},
185+
model::MOI.ModelLike,
186+
bounds::Dict{MOI.VariableIndex,NTuple{2,T}},
187+
f::MOI.ScalarAffineFunction{T},
188+
) where {T}
189+
lb = ub = f.constant
190+
for term in f.terms
191+
ret = _get_bounds(bridge, model, bounds, term.variable)
192+
if ret === nothing
193+
return nothing
194+
end
195+
lb += term.coefficient * ret[1]
196+
ub += term.coefficient * ret[2]
197+
end
198+
return lb, ub
199+
end
200+
201+
# We use the bridge as the first argument to avoid type piracy of other methods.
202+
function _get_bounds(
203+
::CountGreaterThanToMILPBridge{T},
204+
model::MOI.ModelLike,
205+
bounds::Dict{MOI.VariableIndex,NTuple{2,T}},
206+
x::MOI.VariableIndex,
207+
) where {T}
208+
if haskey(bounds, x)
209+
return bounds[x]
210+
end
211+
ret = MOI.Utilities.get_bounds(model, T, x)
212+
if ret == (typemin(T), typemax(T))
213+
return nothing
214+
end
215+
bounds[x] = ret
216+
return ret
217+
end
218+
219+
function _add_unit_expansion(
220+
bridge::CountGreaterThanToMILPBridge{T,F},
221+
model::MOI.ModelLike,
222+
S,
223+
bounds,
224+
x,
225+
) where {T,F}
226+
ret = _get_bounds(bridge, model, bounds, x)
227+
if ret === nothing
228+
error(
229+
"Unable to use $(typeof(bridge)) because an element in the " *
230+
"function has a non-finite domain: $x",
231+
)
232+
end
233+
unit_f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{T}[], zero(T))
234+
convex_f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{T}[], zero(T))
235+
for xi in ret[1]::T:ret[2]::T
236+
new_var, _ = MOI.add_constrained_variable(model, MOI.ZeroOne())
237+
push!(bridge.variables, new_var)
238+
if !haskey(S, xi)
239+
S[xi] = Tuple{Float64,MOI.VariableIndex}[]
240+
end
241+
push!(S[xi], new_var)
242+
push!(unit_f.terms, MOI.ScalarAffineTerm(T(-xi), new_var))
243+
push!(convex_f.terms, MOI.ScalarAffineTerm(one(T), new_var))
244+
end
245+
push!(
246+
bridge.equal_to,
247+
MOI.Utilities.normalize_and_add_constraint(
248+
model,
249+
MOI.Utilities.operate(+, T, x, unit_f),
250+
MOI.EqualTo(zero(T));
251+
allow_modify_function = true,
252+
),
253+
)
254+
push!(
255+
bridge.equal_to,
256+
MOI.add_constraint(model, convex_f, MOI.EqualTo(one(T))),
257+
)
258+
return
259+
end
260+
261+
function MOI.Bridges.final_touch(
262+
bridge::CountGreaterThanToMILPBridge{T,F},
263+
model::MOI.ModelLike,
264+
) where {T,F}
265+
# Clear any existing reformulations!
266+
MOI.delete(model, bridge)
267+
Sx = Dict{T,Vector{MOI.VariableIndex}}()
268+
Sy = Dict{T,Vector{MOI.VariableIndex}}()
269+
scalars = collect(MOI.Utilities.eachscalar(bridge.f))
270+
bounds = Dict{MOI.VariableIndex,NTuple{2,T}}()
271+
_add_unit_expansion(bridge, model, Sy, bounds, scalars[2])
272+
for i in 3:length(scalars)
273+
_add_unit_expansion(bridge, model, Sx, bounds, scalars[i])
274+
end
275+
# We use a sort so that the model order is deterministic.
276+
for s in sort!(collect(keys(Sy)))
277+
if haskey(Sx, s)
278+
M = length(Sx[s])
279+
terms = [MOI.ScalarAffineTerm(one(T), x) for x in Sx[s]]
280+
push!(terms, MOI.ScalarAffineTerm(T(M), first(Sy[s])))
281+
f = MOI.Utilities.operate(
282+
-,
283+
T,
284+
scalars[1],
285+
MOI.ScalarAffineFunction(terms, zero(T)),
286+
)
287+
ci = MOI.Utilities.normalize_and_add_constraint(
288+
model,
289+
f,
290+
MOI.GreaterThan(T(1 - M));
291+
allow_modify_function = true,
292+
)
293+
push!(bridge.greater_than, ci)
294+
end
295+
end
296+
return
297+
end

src/Test/test_cpsat.jl

+2
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ function test_cpsat_CountGreaterThan(
207207
c, _ = MOI.add_constrained_variable(model, MOI.Integer())
208208
y, _ = MOI.add_constrained_variable(model, MOI.Integer())
209209
x = [MOI.add_constrained_variable(model, MOI.Integer())[1] for _ in 1:3]
210+
MOI.add_constraint.(model, x, MOI.Interval(T(0), T(4)))
211+
MOI.add_constraint(model, y, MOI.Interval(T(0), T(4)))
210212
MOI.add_constraint(
211213
model,
212214
MOI.VectorOfVariables([c; y; x]),

0 commit comments

Comments
 (0)