Skip to content

Commit c95fe73

Browse files
committed
Add constraint programming sets (#1837)
This PR adds - BinPacking - Circuit - Cumulative - Table - Path
1 parent 4b2c9e8 commit c95fe73

File tree

6 files changed

+470
-0
lines changed

6 files changed

+470
-0
lines changed

docs/src/reference/standard_form.md

+5
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,14 @@ Complements
101101
```@docs
102102
AllDifferent
103103
Among
104+
BinPacking
104105
CountAtLeast
105106
CountDistinct
106107
CountGreaterThan
108+
Circuit
109+
Cumulative
110+
Path
111+
Table
107112
```
108113

109114
## Matrix sets

src/Test/test_basic_constraint.jl

+16
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,17 @@ _set(::Type{MOI.CountDistinct}) = MOI.CountDistinct(4)
114114
_set(::Type{MOI.Among}) = MOI.Among(4, Set([3, 4]))
115115
_set(::Type{MOI.CountAtLeast}) = MOI.CountAtLeast(1, [2, 2], Set([3]))
116116
_set(::Type{MOI.CountGreaterThan}) = MOI.CountGreaterThan(5)
117+
_set(::Type{MOI.Circuit}) = MOI.Circuit(3)
118+
_set(::Type{MOI.Cumulative}) = MOI.Cumulative(7)
119+
_set(::Type{MOI.Path}) = MOI.Path([1, 1, 2, 2, 3], [2, 3, 3, 4, 4])
120+
121+
function _set(::Type{T}, ::Type{MOI.BinPacking}) where {T}
122+
return MOI.BinPacking(T(2), T[1, 2])
123+
end
124+
125+
function _set(::Type{T}, ::Type{MOI.Table}) where {T}
126+
return MOI.Table(T[0 1 1; 1 0 1; 1 1 0])
127+
end
117128

118129
function _set(
119130
::Type{MOI.Indicator{MOI.ACTIVATE_ON_ONE,MOI.LessThan{T}}},
@@ -282,6 +293,11 @@ for s in [
282293
:Among,
283294
:CountAtLeast,
284295
:CountGreaterThan,
296+
:BinPacking,
297+
:Circuit,
298+
:Cumulative,
299+
:Table,
300+
:Path,
285301
]
286302
S = getfield(MOI, s)
287303
functions = if S <: MOI.AbstractScalarSet

src/Test/test_cpsat.jl

+245
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ function test_cpsat_CountDistinct(
6565
@requires _supports(config, MOI.optimize!)
6666
y = [MOI.add_constrained_variable(model, MOI.Integer()) for _ in 1:4]
6767
x = first.(y)
68+
MOI.add_constraint.(model, x, MOI.Interval(T(0), T(4)))
6869
MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.CountDistinct(4))
6970
MOI.optimize!(model)
7071
x_val = round.(Int, MOI.get.(model, MOI.VariablePrimal(), x))
@@ -142,6 +143,7 @@ function test_cpsat_CountAtLeast(
142143
x, _ = MOI.add_constrained_variable(model, MOI.Integer())
143144
y, _ = MOI.add_constrained_variable(model, MOI.Integer())
144145
z, _ = MOI.add_constrained_variable(model, MOI.Integer())
146+
MOI.add_constraint.(model, [x, y, z], MOI.Interval(T(0), T(3)))
145147
variables = [x, y, y, z]
146148
partitions = [2, 2]
147149
set = Set([3])
@@ -220,3 +222,246 @@ function setup_test(
220222
)
221223
return
222224
end
225+
226+
"""
227+
test_cpsat_BinPacking(model::MOI.ModelLike, config::Config)
228+
229+
Add a VectorOfVariables-in-BinPacking constraint.
230+
"""
231+
function test_cpsat_BinPacking(
232+
model::MOI.ModelLike,
233+
config::Config{T},
234+
) where {T}
235+
@requires MOI.supports_constraint(
236+
model,
237+
MOI.VectorOfVariables,
238+
MOI.BinPacking{T},
239+
)
240+
@requires MOI.supports_add_constrained_variable(model, MOI.Integer)
241+
@requires _supports(config, MOI.optimize!)
242+
x = [MOI.add_constrained_variable(model, MOI.Integer())[1] for _ in 1:2]
243+
MOI.add_constraint.(model, x, MOI.Interval(T(0), T(2)))
244+
MOI.add_constraint(
245+
model,
246+
MOI.VectorOfVariables(x),
247+
MOI.BinPacking(T(2), T[1, 2]),
248+
)
249+
MOI.optimize!(model)
250+
x_val = round.(Int, MOI.get.(model, MOI.VariablePrimal(), x))
251+
@test 1 * x_val[1] + 2 * x_val[2] <= T(2)
252+
return
253+
end
254+
255+
function setup_test(
256+
::typeof(test_cpsat_BinPacking),
257+
model::MOIU.MockOptimizer,
258+
::Config{T},
259+
) where {T}
260+
MOIU.set_mock_optimize!(
261+
model,
262+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(
263+
mock,
264+
MOI.OPTIMAL,
265+
(MOI.FEASIBLE_POINT, T[2, 0]),
266+
),
267+
)
268+
return
269+
end
270+
271+
"""
272+
test_cpsat_Cumulative(model::MOI.ModelLike, config::Config)
273+
274+
Add a VectorOfVariables-in-Cumulative constraint.
275+
"""
276+
function test_cpsat_Cumulative(
277+
model::MOI.ModelLike,
278+
config::Config{T},
279+
) where {T}
280+
@requires MOI.supports_constraint(
281+
model,
282+
MOI.VectorOfVariables,
283+
MOI.Cumulative,
284+
)
285+
@requires MOI.supports_add_constrained_variable(model, MOI.Integer)
286+
@requires _supports(config, MOI.optimize!)
287+
s = [MOI.add_constrained_variable(model, MOI.Integer())[1] for _ in 1:3]
288+
MOI.add_constraint.(model, s, MOI.Interval(T(0), T(3)))
289+
d = [MOI.add_constrained_variable(model, MOI.Integer())[1] for _ in 1:3]
290+
MOI.add_constraint.(model, d, MOI.EqualTo(T(2)))
291+
r = [MOI.add_constrained_variable(model, MOI.Integer())[1] for _ in 1:3]
292+
MOI.add_constraint.(model, r, MOI.EqualTo.(T[3, 2, 1]))
293+
b, _ = MOI.add_constrained_variable(model, MOI.Integer())
294+
MOI.add_constraint.(model, b, MOI.EqualTo(T(5)))
295+
MOI.add_constraint(
296+
model,
297+
MOI.VectorOfVariables([s; d; r; b]),
298+
MOI.Cumulative(10),
299+
)
300+
MOI.optimize!(model)
301+
s_val = round.(Int, MOI.get.(model, MOI.VariablePrimal(), s))
302+
d_val = round.(Int, MOI.get.(model, MOI.VariablePrimal(), d))
303+
r_val = round.(Int, MOI.get.(model, MOI.VariablePrimal(), r))
304+
b_val = round(Int, MOI.get(model, MOI.VariablePrimal(), b))
305+
times = zeros(1 + maximum(s_val) + maximum(d_val))
306+
for i in 1:3
307+
for j in 0:(d_val[i]-1)
308+
t = s_val[i] + j
309+
times[t+1] += r_val[i]
310+
end
311+
end
312+
@test all(times .<= b_val)
313+
return
314+
end
315+
316+
function setup_test(
317+
::typeof(test_cpsat_Cumulative),
318+
model::MOIU.MockOptimizer,
319+
::Config{T},
320+
) where {T}
321+
MOIU.set_mock_optimize!(
322+
model,
323+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(
324+
mock,
325+
MOI.OPTIMAL,
326+
(MOI.FEASIBLE_POINT, T[0, 1, 2, 2, 2, 2, 3, 2, 1, 5]),
327+
),
328+
)
329+
return
330+
end
331+
332+
"""
333+
test_cpsat_Table(model::MOI.ModelLike, config::Config)
334+
335+
Add a VectorOfVariables-in-Table constraint.
336+
"""
337+
function test_cpsat_Table(model::MOI.ModelLike, config::Config{T}) where {T}
338+
@requires MOI.supports_constraint(
339+
model,
340+
MOI.VectorOfVariables,
341+
MOI.Table{T},
342+
)
343+
@requires MOI.supports_add_constrained_variable(model, MOI.Integer)
344+
@requires _supports(config, MOI.optimize!)
345+
x = [MOI.add_constrained_variable(model, MOI.Integer())[1] for _ in 1:3]
346+
table = T[1 1 0; 0 1 1]
347+
MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.Table(table))
348+
MOI.optimize!(model)
349+
x_val = round.(Int, MOI.get.(model, MOI.VariablePrimal(), x))
350+
@test x_val == [1, 1, 0] || x_val == [0, 1, 1]
351+
return
352+
end
353+
354+
function setup_test(
355+
::typeof(test_cpsat_Table),
356+
model::MOIU.MockOptimizer,
357+
::Config{T},
358+
) where {T}
359+
MOIU.set_mock_optimize!(
360+
model,
361+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(
362+
mock,
363+
MOI.OPTIMAL,
364+
(MOI.FEASIBLE_POINT, T[1, 1, 0]),
365+
),
366+
)
367+
return
368+
end
369+
370+
"""
371+
test_cpsat_Circuit(model::MOI.ModelLike, config::Config)
372+
373+
Add a VectorOfVariables-in-Circuit constraint.
374+
"""
375+
function test_cpsat_Circuit(model::MOI.ModelLike, config::Config{T}) where {T}
376+
@requires MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Circuit)
377+
@requires MOI.supports_add_constrained_variable(model, MOI.Integer)
378+
@requires _supports(config, MOI.optimize!)
379+
x = [MOI.add_constrained_variable(model, MOI.Integer())[1] for _ in 1:3]
380+
MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.Circuit(3))
381+
MOI.optimize!(model)
382+
x_val = round.(Int, MOI.get.(model, MOI.VariablePrimal(), x))
383+
@test x_val == [3, 1, 2] || x_val == [2, 3, 1]
384+
return
385+
end
386+
387+
function setup_test(
388+
::typeof(test_cpsat_Circuit),
389+
model::MOIU.MockOptimizer,
390+
::Config{T},
391+
) where {T}
392+
MOIU.set_mock_optimize!(
393+
model,
394+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(
395+
mock,
396+
MOI.OPTIMAL,
397+
(MOI.FEASIBLE_POINT, T[3, 1, 2]),
398+
),
399+
)
400+
return
401+
end
402+
403+
"""
404+
test_cpsat_Path(model::MOI.ModelLike, config::Config)
405+
406+
Add a VectorOfVariables-in-Path constraint.
407+
"""
408+
function test_cpsat_Path(model::MOI.ModelLike, config::Config{T}) where {T}
409+
@requires MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Path)
410+
@requires MOI.supports_add_constrained_variable(model, MOI.Integer)
411+
@requires _supports(config, MOI.optimize!)
412+
from = [1, 1, 2, 2, 3]
413+
to = [2, 3, 3, 4, 4]
414+
N, E = 4, 5
415+
s, _ = MOI.add_constrained_variable(model, MOI.Integer())
416+
t, _ = MOI.add_constrained_variable(model, MOI.Integer())
417+
ns = MOI.add_variables(model, N)
418+
MOI.add_constraint.(model, ns, MOI.ZeroOne())
419+
es = MOI.add_variables(model, E)
420+
MOI.add_constraint.(model, es, MOI.ZeroOne())
421+
MOI.add_constraint(
422+
model,
423+
MOI.VectorOfVariables([s; t; ns; es]),
424+
MOI.Path(from, to),
425+
)
426+
MOI.optimize!(model)
427+
s_val = round(Int, MOI.get(model, MOI.VariablePrimal(), s))
428+
@test 1 <= s_val <= 4
429+
t_val = round(Int, MOI.get(model, MOI.VariablePrimal(), t))
430+
@test 1 <= t_val <= 4
431+
ns_val = round.(Int, MOI.get.(model, MOI.VariablePrimal(), ns))
432+
es_val = round.(Int, MOI.get.(model, MOI.VariablePrimal(), es))
433+
outs = Vector{Int}[[1, 2], [3, 4], [5], Int[]]
434+
ins = Vector{Int}[[], [1], [2, 3], [4, 5]]
435+
has_edges = s_val == t_val ? 0 : 1
436+
# source: must have no incoming and one outgoing (if s != t)
437+
@test sum(es_val[o] for o in ins[s_val]; init = 0) == 0
438+
@test sum(es_val[o] for o in outs[s_val]; init = 0) == has_edges
439+
# dest: must have no outgoing and one incoming (if s != t)
440+
@test sum(es_val[o] for o in ins[t_val]; init = 0) == has_edges
441+
@test sum(es_val[o] for o in outs[t_val]; init = 0) == 0
442+
for i in 1:4
443+
if i != s_val && i != t_val
444+
# other nodes: must have one incoming and one outgoing iff node is
445+
# in subgraph.
446+
@test sum(es_val[o] for o in outs[i]; init = 0) == ns_val[i]
447+
@test sum(es_val[o] for o in ins[i]; init = 0) == ns_val[i]
448+
end
449+
end
450+
return
451+
end
452+
453+
function setup_test(
454+
::typeof(test_cpsat_Path),
455+
model::MOIU.MockOptimizer,
456+
::Config{T},
457+
) where {T}
458+
MOIU.set_mock_optimize!(
459+
model,
460+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(
461+
mock,
462+
MOI.OPTIMAL,
463+
(MOI.FEASIBLE_POINT, T[1, 4, 1, 1, 0, 1, 1, 0, 0, 1, 0]),
464+
),
465+
)
466+
return
467+
end

src/Utilities/model.jl

+5
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,9 @@ const LessThanIndicatorZero{T} =
792792
MOI.Among,
793793
MOI.CountAtLeast,
794794
MOI.CountGreaterThan,
795+
MOI.Circuit,
796+
MOI.Cumulative,
797+
MOI.Path,
795798
),
796799
(
797800
MOI.PowerCone,
@@ -800,6 +803,8 @@ const LessThanIndicatorZero{T} =
800803
MOI.SOS2,
801804
LessThanIndicatorOne,
802805
LessThanIndicatorZero,
806+
MOI.Table,
807+
MOI.BinPacking,
803808
),
804809
(),
805810
(MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction),

0 commit comments

Comments
 (0)