Skip to content

Commit 98f19e8

Browse files
committed
Add constraint programming sets (#1837)
This PR adds - BinPacking - Circuit - Cumulative - Table - Path Fix BinPacking test
1 parent 622bb27 commit 98f19e8

File tree

6 files changed

+480
-0
lines changed

6 files changed

+480
-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

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