Skip to content

Commit fbf833c

Browse files
authored
Fix and test that Bridges.final_touch can be called multiple times (#2089)
1 parent 79081f0 commit fbf833c

File tree

11 files changed

+192
-21
lines changed

11 files changed

+192
-21
lines changed

src/Bridges/Bridges.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@ function runtests(
255255
model = _bridged_model(Bridge, inner)
256256
MOI.Utilities.loadfromstring!(model, input)
257257
final_touch(model)
258+
# Should be able to call final_touch multiple times.
259+
final_touch(model)
258260
# Load a non-bridged input model, and check that getters are the same.
259261
test = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
260262
MOI.Utilities.loadfromstring!(test, input)

src/Bridges/Constraint/bridges/bin_packing.jl

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ struct BinPackingToMILPBridge{
5959
equal_to::Vector{
6060
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}},
6161
}
62+
bounds::Vector{NTuple{2,T}}
6263
end
6364

6465
const BinPackingToMILP{T,OT<:MOI.ModelLike} =
@@ -76,6 +77,7 @@ function bridge_constraint(
7677
MOI.VariableIndex[],
7778
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.LessThan{T}}[],
7879
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}[],
80+
NTuple{2,T}[],
7981
)
8082
end
8183

@@ -133,6 +135,7 @@ function MOI.delete(model::MOI.ModelLike, bridge::BinPackingToMILPBridge)
133135
empty!(bridge.equal_to)
134136
MOI.delete.(model, bridge.variables)
135137
empty!(bridge.variables)
138+
empty!(bridge.bounds)
136139
return
137140
end
138141

@@ -232,9 +235,7 @@ function MOI.Bridges.final_touch(
232235
bridge::BinPackingToMILPBridge{T,F},
233236
model::MOI.ModelLike,
234237
) where {T,F}
235-
# Clear any existing reformulations!
236-
MOI.delete(model, bridge)
237-
S = Dict{T,Vector{Tuple{Float64,MOI.VariableIndex}}}()
238+
S = Dict{T,Vector{Tuple{T,MOI.VariableIndex}}}()
238239
scalars = collect(MOI.Utilities.eachscalar(bridge.f))
239240
bounds = Dict{MOI.VariableIndex,NTuple{2,T}}()
240241
for i in 1:length(scalars)
@@ -246,41 +247,56 @@ function MOI.Bridges.final_touch(
246247
"function has a non-finite domain: $x",
247248
)
248249
end
250+
if length(bridge.bounds) < i
251+
# This is the first time calling final_touch
252+
push!(bridge.bounds, ret)
253+
elseif bridge.bounds[i] == ret
254+
# We've called final_touch before, and the bounds match. No need to
255+
# reformulate a second time.
256+
continue
257+
elseif bridge.bounds[i] != ret
258+
# There is a stored bound, and the current bounds do not match. This
259+
# means the model has been modified since the previous call to
260+
# final_touch. We need to delete the bridge and start again.
261+
MOI.delete(model, bridge)
262+
MOI.Bridges.final_touch(bridge, model)
263+
return
264+
end
249265
unit_f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{T}[], zero(T))
250266
convex_f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{T}[], zero(T))
251267
for xi in ret[1]::T:ret[2]::T
252268
new_var, _ = MOI.add_constrained_variable(model, MOI.ZeroOne())
253269
push!(bridge.variables, new_var)
254270
if !haskey(S, xi)
255-
S[xi] = Tuple{Float64,MOI.VariableIndex}[]
271+
S[xi] = Tuple{T,MOI.VariableIndex}[]
256272
end
257273
push!(S[xi], (bridge.s.weights[i], new_var))
258-
push!(unit_f.terms, MOI.ScalarAffineTerm(T(-xi), new_var))
259-
push!(convex_f.terms, MOI.ScalarAffineTerm(one(T), new_var))
274+
push!(unit_f.terms, MOI.ScalarAffineTerm{T}(T(-xi), new_var))
275+
push!(convex_f.terms, MOI.ScalarAffineTerm{T}(one(T), new_var))
260276
end
261277
push!(
262278
bridge.equal_to,
263279
MOI.Utilities.normalize_and_add_constraint(
264280
model,
265281
MOI.Utilities.operate(+, T, x, unit_f),
266-
MOI.EqualTo(zero(T));
282+
MOI.EqualTo{T}(zero(T));
267283
allow_modify_function = true,
268284
),
269285
)
270286
push!(
271287
bridge.equal_to,
272-
MOI.add_constraint(model, convex_f, MOI.EqualTo(one(T))),
288+
MOI.add_constraint(model, convex_f, MOI.EqualTo{T}(one(T))),
273289
)
274290
end
275291
# We use a sort so that the model order is deterministic.
276292
for s in sort!(collect(keys(S)))
277293
ci = MOI.add_constraint(
278294
model,
279-
MOI.ScalarAffineFunction(
280-
[MOI.ScalarAffineTerm(w, z) for (w, z) in S[s]],
295+
MOI.ScalarAffineFunction{T}(
296+
[MOI.ScalarAffineTerm{T}(w, z) for (w, z) in S[s]],
281297
zero(T),
282298
),
283-
MOI.LessThan(bridge.s.capacity),
299+
MOI.LessThan{T}(bridge.s.capacity),
284300
)
285301
push!(bridge.less_than, ci)
286302
end

src/Bridges/Constraint/bridges/count_belongs.jl

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ mutable struct CountBelongsToMILPBridge{
5959
equal_to::Vector{
6060
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}},
6161
}
62+
bounds::Vector{NTuple{2,T}}
6263
function CountBelongsToMILPBridge{T}(
6364
f::Union{MOI.VectorOfVariables,MOI.VectorAffineFunction{T}},
6465
s::MOI.CountBelongs,
@@ -68,6 +69,7 @@ mutable struct CountBelongsToMILPBridge{
6869
s,
6970
MOI.VariableIndex[],
7071
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}[],
72+
NTuple{2,T}[],
7173
)
7274
end
7375
end
@@ -139,6 +141,7 @@ function MOI.delete(model::MOI.ModelLike, bridge::CountBelongsToMILPBridge)
139141
MOI.delete(model, x)
140142
end
141143
empty!(bridge.variables)
144+
empty!(bridge.bounds)
142145
return
143146
end
144147

@@ -248,6 +251,21 @@ function _unit_expansion(
248251
"non-finite domain: $(f[i])",
249252
)
250253
end
254+
if length(bridge.bounds) < i
255+
# This is the first time calling final_touch
256+
push!(bridge.bounds, ret)
257+
elseif bridge.bounds[i] == ret
258+
# We've called final_touch before, and the bounds match. No need to
259+
# reformulate a second time.
260+
continue
261+
elseif bridge.bounds[i] != ret
262+
# There is a stored bound, and the current bounds do not match. This
263+
# means the model has been modified since the previous call to
264+
# final_touch. We need to delete the bridge and start again.
265+
MOI.delete(model, bridge)
266+
MOI.Bridges.final_touch(bridge, model)
267+
break
268+
end
251269
unit_f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{T}[], zero(T))
252270
convex_f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{T}[], zero(T))
253271
for xi in ret[1]:ret[2]
@@ -277,9 +295,11 @@ function MOI.Bridges.final_touch(
277295
bridge::CountBelongsToMILPBridge{T,F},
278296
model::MOI.ModelLike,
279297
) where {T,F}
280-
MOI.delete(model, bridge)
281298
scalars = collect(MOI.Utilities.eachscalar(bridge.f))
282299
S, ci = _unit_expansion(bridge, model, scalars[2:end])
300+
if isempty(S) && isempty(ci)
301+
return # Nothing to bridge. We must have already called final_touch.
302+
end
283303
append!(bridge.equal_to, ci)
284304
for (_, s) in S
285305
append!(bridge.variables, s)

src/Bridges/Constraint/bridges/count_distinct.jl

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ mutable struct CountDistinctToMILPBridge{
7878
less_than::Vector{
7979
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.LessThan{T}},
8080
}
81+
bounds::Vector{NTuple{2,T}}
8182
function CountDistinctToMILPBridge{T}(
8283
f::Union{MOI.VectorOfVariables,MOI.VectorAffineFunction{T}},
8384
) where {T}
@@ -86,6 +87,7 @@ mutable struct CountDistinctToMILPBridge{
8687
MOI.VariableIndex[],
8788
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}[],
8889
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.LessThan{T}}[],
90+
NTuple{2,T}[],
8991
)
9092
end
9193
end
@@ -164,6 +166,7 @@ function MOI.delete(model::MOI.ModelLike, bridge::CountDistinctToMILPBridge)
164166
MOI.delete(model, x)
165167
end
166168
empty!(bridge.variables)
169+
empty!(bridge.bounds)
167170
return
168171
end
169172

@@ -266,8 +269,6 @@ function MOI.Bridges.final_touch(
266269
bridge::CountDistinctToMILPBridge{T,F},
267270
model::MOI.ModelLike,
268271
) where {T,F}
269-
# Clear any existing reformulations!
270-
MOI.delete(model, bridge)
271272
S = Dict{T,Vector{MOI.VariableIndex}}()
272273
scalars = collect(MOI.Utilities.eachscalar(bridge.f))
273274
bounds = Dict{MOI.VariableIndex,NTuple{2,T}}()
@@ -280,6 +281,21 @@ function MOI.Bridges.final_touch(
280281
"in the function has a non-finite domain: $x",
281282
)
282283
end
284+
if length(bridge.bounds) < i - 1
285+
# This is the first time calling final_touch
286+
push!(bridge.bounds, ret)
287+
elseif bridge.bounds[i-1] == ret
288+
# We've called final_touch before, and the bounds match. No need to
289+
# reformulate a second time.
290+
continue
291+
elseif bridge.bounds[i-1] != ret
292+
# There is a stored bound, and the current bounds do not match. This
293+
# means the model has been modified since the previous call to
294+
# final_touch. We need to delete the bridge and start again.
295+
MOI.delete(model, bridge)
296+
MOI.Bridges.final_touch(bridge, model)
297+
return
298+
end
283299
unit_f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{T}[], zero(T))
284300
convex_f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{T}[], zero(T))
285301
for xi in ret[1]::T:ret[2]::T
@@ -306,6 +322,9 @@ function MOI.Bridges.final_touch(
306322
MOI.add_constraint(model, convex_f, MOI.EqualTo(one(T))),
307323
)
308324
end
325+
if isempty(S)
326+
return # Nothing to bridge. We must have already called final_touch.
327+
end
309328
count_terms = MOI.ScalarAffineTerm{T}[]
310329
# We use a sort so that the model order is deterministic.
311330
for s in sort!(collect(keys(S)))

src/Bridges/Constraint/bridges/count_distinct_reif.jl

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ mutable struct ReifiedCountDistinctToMILPBridge{
9090
less_than::Vector{
9191
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.LessThan{T}},
9292
}
93+
bounds::Vector{NTuple{2,T}}
9394
function ReifiedCountDistinctToMILPBridge{T}(
9495
f::Union{MOI.VectorOfVariables,MOI.VectorAffineFunction{T}},
9596
) where {T}
@@ -98,6 +99,7 @@ mutable struct ReifiedCountDistinctToMILPBridge{
9899
MOI.VariableIndex[],
99100
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}[],
100101
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.LessThan{T}}[],
102+
NTuple{2,T}[],
101103
)
102104
end
103105
end
@@ -179,6 +181,7 @@ function MOI.delete(
179181
MOI.delete(model, x)
180182
end
181183
empty!(bridge.variables)
184+
empty!(bridge.bounds)
182185
return
183186
end
184187

@@ -284,8 +287,6 @@ function MOI.Bridges.final_touch(
284287
bridge::ReifiedCountDistinctToMILPBridge{T,F},
285288
model::MOI.ModelLike,
286289
) where {T,F}
287-
# Clear any existing reformulations!
288-
MOI.delete(model, bridge)
289290
S = Dict{T,Vector{MOI.VariableIndex}}()
290291
scalars = collect(MOI.Utilities.eachscalar(bridge.f))
291292
bounds = Dict{MOI.VariableIndex,NTuple{2,T}}()
@@ -298,6 +299,21 @@ function MOI.Bridges.final_touch(
298299
"element $i in the function has a non-finite domain: $x",
299300
)
300301
end
302+
if length(bridge.bounds) < i - 2
303+
# This is the first time calling final_touch
304+
push!(bridge.bounds, ret)
305+
elseif bridge.bounds[i-2] == ret
306+
# We've called final_touch before, and the bounds match. No need to
307+
# reformulate a second time.
308+
continue
309+
elseif bridge.bounds[i-2] != ret
310+
# There is a stored bound, and the current bounds do not match. This
311+
# means the model has been modified since the previous call to
312+
# final_touch. We need to delete the bridge and start again.
313+
MOI.delete(model, bridge)
314+
MOI.Bridges.final_touch(bridge, model)
315+
return
316+
end
301317
unit_f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{T}[], zero(T))
302318
convex_f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{T}[], zero(T))
303319
for xi in ret[1]::T:ret[2]::T
@@ -324,6 +340,9 @@ function MOI.Bridges.final_touch(
324340
MOI.add_constraint(model, convex_f, MOI.EqualTo(one(T))),
325341
)
326342
end
343+
if isempty(S)
344+
return # Nothing to bridge. We must have already called final_touch.
345+
end
327346
count_terms = MOI.ScalarAffineTerm{T}[]
328347
# We use a sort so that the model order is deterministic.
329348
for s in sort!(collect(keys(S)))

src/Bridges/Constraint/bridges/count_greater_than.jl

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ struct CountGreaterThanToMILPBridge{
3838
equal_to::Vector{
3939
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}},
4040
}
41+
bounds::Vector{NTuple{2,T}}
4142
end
4243

4344
const CountGreaterThanToMILP{T,OT<:MOI.ModelLike} =
@@ -55,6 +56,7 @@ function bridge_constraint(
5556
MOI.VariableIndex[],
5657
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.GreaterThan{T}}[],
5758
MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}[],
59+
NTuple{2,T}[],
5860
)
5961
end
6062

@@ -112,6 +114,7 @@ function MOI.delete(model::MOI.ModelLike, bridge::CountGreaterThanToMILPBridge)
112114
empty!(bridge.equal_to)
113115
MOI.delete.(model, bridge.variables)
114116
empty!(bridge.variables)
117+
empty!(bridge.bounds)
115118
return
116119
end
117120

@@ -222,6 +225,7 @@ function _add_unit_expansion(
222225
S,
223226
bounds,
224227
x,
228+
i,
225229
) where {T,F}
226230
ret = _get_bounds(bridge, model, bounds, x)
227231
if ret === nothing
@@ -230,13 +234,28 @@ function _add_unit_expansion(
230234
"function has a non-finite domain: $x",
231235
)
232236
end
237+
if length(bridge.bounds) < i
238+
# This is the first time calling final_touch
239+
push!(bridge.bounds, ret)
240+
elseif bridge.bounds[i] == ret
241+
# We've called final_touch before, and the bounds match. No need to
242+
# reformulate a second time.
243+
return
244+
elseif bridge.bounds[i] != ret
245+
# There is a stored bound, and the current bounds do not match. This
246+
# means the model has been modified since the previous call to
247+
# final_touch. We need to delete the bridge and start again.
248+
MOI.delete(model, bridge)
249+
MOI.Bridges.final_touch(bridge, model)
250+
return
251+
end
233252
unit_f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{T}[], zero(T))
234253
convex_f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{T}[], zero(T))
235254
for xi in ret[1]::T:ret[2]::T
236255
new_var, _ = MOI.add_constrained_variable(model, MOI.ZeroOne())
237256
push!(bridge.variables, new_var)
238257
if !haskey(S, xi)
239-
S[xi] = Tuple{Float64,MOI.VariableIndex}[]
258+
S[xi] = Tuple{T,MOI.VariableIndex}[]
240259
end
241260
push!(S[xi], new_var)
242261
push!(unit_f.terms, MOI.ScalarAffineTerm(T(-xi), new_var))
@@ -262,15 +281,13 @@ function MOI.Bridges.final_touch(
262281
bridge::CountGreaterThanToMILPBridge{T,F},
263282
model::MOI.ModelLike,
264283
) where {T,F}
265-
# Clear any existing reformulations!
266-
MOI.delete(model, bridge)
267284
Sx = Dict{T,Vector{MOI.VariableIndex}}()
268285
Sy = Dict{T,Vector{MOI.VariableIndex}}()
269286
scalars = collect(MOI.Utilities.eachscalar(bridge.f))
270287
bounds = Dict{MOI.VariableIndex,NTuple{2,T}}()
271-
_add_unit_expansion(bridge, model, Sy, bounds, scalars[2])
288+
_add_unit_expansion(bridge, model, Sy, bounds, scalars[2], 1)
272289
for i in 3:length(scalars)
273-
_add_unit_expansion(bridge, model, Sx, bounds, scalars[i])
290+
_add_unit_expansion(bridge, model, Sx, bounds, scalars[i], i - 1)
274291
end
275292
# We use a sort so that the model order is deterministic.
276293
for s in sort!(collect(keys(Sy)))

0 commit comments

Comments
 (0)