@@ -41,6 +41,24 @@ non-zero:
4141n - \\ sum\\ limits_{j \\ in \\ bigcup_{i=1,\\ ldots,d} S_i} y_{j} = 0
4242```
4343
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+
4462## Source node
4563
4664`CountDistinctToMILPBridge` supports:
@@ -232,9 +250,105 @@ function MOI.Bridges.final_touch(
232250 bridge:: CountDistinctToMILPBridge{T,F} ,
233251 model:: MOI.ModelLike ,
234252) where {T,F}
235- S = Dict {T,Vector{MOI.VariableIndex}} ()
236253 scalars = collect (MOI. Utilities. eachscalar (bridge. f))
237254 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}} ()
238352 for i in 2 : length (scalars)
239353 x = scalars[i]
240354 ret = MOI. Utilities. get_bounds (model, bounds, x)
0 commit comments