@@ -41,6 +41,24 @@ non-zero:
41
41
n - \\ sum\\ limits_{j \\ in \\ bigcup_{i=1,\\ ldots,d} S_i} y_{j} = 0
42
42
```
43
43
44
+ ## Formulation (special case)
45
+
46
+ In the special case that the constraint is `[2, x, y] in CountDistinct(3)`, then
47
+ the constraint is equivalent to `[x, y] in AllDifferent(2)`, which is equivalent
48
+ to `x != y`.
49
+
50
+ ```math
51
+ (x - y <= -1) \\ vee (y - x <= -1)
52
+ ```
53
+ which is equivalent to (for suitable `M`):
54
+ ```math
55
+ \\ begin{aligned}
56
+ z \\ in \\ {0, 1\\ } \\\\
57
+ x - y - M * z <= -1 \\\\
58
+ y - x - M * (1 - z) <= -1
59
+ \\ end{aligned}
60
+ ```
61
+
44
62
## Source node
45
63
46
64
`CountDistinctToMILPBridge` supports:
@@ -232,9 +250,105 @@ function MOI.Bridges.final_touch(
232
250
bridge:: CountDistinctToMILPBridge{T,F} ,
233
251
model:: MOI.ModelLike ,
234
252
) where {T,F}
235
- S = Dict {T,Vector{MOI.VariableIndex}} ()
236
253
scalars = collect (MOI. Utilities. eachscalar (bridge. f))
237
254
bounds = Dict {MOI.VariableIndex,NTuple{2,T}} ()
255
+ ret = MOI. Utilities. get_bounds (model, bounds, scalars[1 ])
256
+ if MOI. output_dimension (bridge. f) == 3 && ret == (2.0 , 2.0 )
257
+ # The special case of
258
+ # [x, y] in AllDifferent()
259
+ # bridged to
260
+ # [2, x, y] in CountDistinct()
261
+ # This is equivalent to the NotEqualTo set.
262
+ _final_touch_not_equal_case (bridge, model, scalars)
263
+ else
264
+ _final_touch_general_case (bridge, model, scalars)
265
+ end
266
+ return
267
+ end
268
+
269
+ function _final_touch_not_equal_case (
270
+ bridge:: CountDistinctToMILPBridge{T,F} ,
271
+ model:: MOI.ModelLike ,
272
+ scalars,
273
+ ) where {T,F}
274
+ bounds = Dict {MOI.VariableIndex,NTuple{2,T}} ()
275
+ new_bounds = false
276
+ for i in 2 : length (scalars)
277
+ x = scalars[i]
278
+ ret = MOI. Utilities. get_bounds (model, bounds, x)
279
+ if ret === nothing
280
+ error (
281
+ " Unable to use CountDistinctToMILPBridge because element $i " *
282
+ " in the function has a non-finite domain: $x " ,
283
+ )
284
+ end
285
+ if length (bridge. bounds) < i - 1
286
+ # This is the first time calling final_touch
287
+ push! (bridge. bounds, ret)
288
+ new_bounds = true
289
+ elseif bridge. bounds[i- 1 ] == ret
290
+ # We've called final_touch before, and the bounds match. No need to
291
+ # reformulate a second time.
292
+ continue
293
+ elseif bridge. bounds[i- 1 ] != ret
294
+ # There is a stored bound, and the current bounds do not match. This
295
+ # means the model has been modified since the previous call to
296
+ # final_touch. We need to delete the bridge and start again.
297
+ MOI. delete (model, bridge)
298
+ MOI. Bridges. final_touch (bridge, model)
299
+ return
300
+ end
301
+ end
302
+ if ! new_bounds
303
+ return
304
+ end
305
+ # [2, x, y] in CountDistinct()
306
+ # <-->
307
+ # x != y
308
+ # <-->
309
+ # {x - y >= 1} \/ {y - x >= 1}
310
+ # <-->
311
+ # {x - y <= -1} \/ {y - x <= -1}
312
+ # <-->
313
+ # {x - y - M * z <= -1} /\ {y - x - M * (1 - z) <= -1}, z in {0, 1}
314
+ z, _ = MOI. add_constrained_variable (model, MOI. ZeroOne ())
315
+ push! (bridge. variables, z)
316
+ x, y = scalars[2 ], scalars[3 ]
317
+ bx, by = bridge. bounds[1 ], bridge. bounds[2 ]
318
+ # {x - y - M * z <= -1}, M = u_x - l_y + 1
319
+ M = bx[2 ] - by[1 ] + 1
320
+ f = MOI. Utilities. operate (- , T, x, y)
321
+ push! (
322
+ bridge. less_than,
323
+ MOI. Utilities. normalize_and_add_constraint (
324
+ model,
325
+ MOI. Utilities. operate! (- , T, f, M * z),
326
+ MOI. LessThan (T (- 1 ));
327
+ allow_modify_function = true ,
328
+ ),
329
+ )
330
+ # {y - x - M * (1 - z) <= -1}, M = u_x - l_y + 1
331
+ M = by[2 ] - bx[1 ] + 1
332
+ g = MOI. Utilities. operate (- , T, y, x)
333
+ push! (
334
+ bridge. less_than,
335
+ MOI. Utilities. normalize_and_add_constraint (
336
+ model,
337
+ MOI. Utilities. operate! (+ , T, g, M * z),
338
+ MOI. LessThan (T (- 1 + M));
339
+ allow_modify_function = true ,
340
+ ),
341
+ )
342
+ return
343
+ end
344
+
345
+ function _final_touch_general_case (
346
+ bridge:: CountDistinctToMILPBridge{T,F} ,
347
+ model:: MOI.ModelLike ,
348
+ scalars,
349
+ ) where {T,F}
350
+ S = Dict {T,Vector{MOI.VariableIndex}} ()
351
+ bounds = Dict {MOI.VariableIndex,NTuple{2,T}} ()
238
352
for i in 2 : length (scalars)
239
353
x = scalars[i]
240
354
ret = MOI. Utilities. get_bounds (model, bounds, x)
0 commit comments