or-tools 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +357 -299
- data/ext/or-tools/ext.cpp +109 -11
- data/lib/or-tools.rb +1 -0
- data/lib/or_tools/bool_var.rb +9 -0
- data/lib/or_tools/cp_solver.rb +6 -1
- data/lib/or_tools/cp_solver_solution_callback.rb +4 -0
- data/lib/or_tools/ext.bundle +0 -0
- data/lib/or_tools/linear_expr.rb +8 -0
- data/lib/or_tools/version.rb +1 -1
- metadata +3 -2
data/README.md
CHANGED
@@ -66,32 +66,27 @@ Assignment
|
|
66
66
|
Scheduling
|
67
67
|
|
68
68
|
- [Employee Scheduling](#employee-scheduling)
|
69
|
+
- [The Job Shop Problem](#the-job-shop-problem)
|
69
70
|
|
70
71
|
Other Examples
|
71
72
|
|
72
73
|
- [Sudoku](#sudoku)
|
74
|
+
- [Wedding Seating Chart](#wedding-seating-chart)
|
73
75
|
- [Set Partitioning](#set-partitioning)
|
74
76
|
|
75
77
|
### The Glop Linear Solver
|
76
78
|
|
77
79
|
[Guide](https://developers.google.com/optimization/lp/glop)
|
78
80
|
|
79
|
-
Declare the solver
|
80
|
-
|
81
81
|
```ruby
|
82
|
+
# declare the solver
|
82
83
|
solver = ORTools::Solver.new("LinearProgrammingExample", :glop)
|
83
|
-
```
|
84
|
-
|
85
|
-
Create the variables
|
86
84
|
|
87
|
-
|
85
|
+
# create the variables
|
88
86
|
x = solver.num_var(0, solver.infinity, "x")
|
89
87
|
y = solver.num_var(0, solver.infinity, "y")
|
90
|
-
```
|
91
88
|
|
92
|
-
|
93
|
-
|
94
|
-
```ruby
|
89
|
+
# define the constraints
|
95
90
|
constraint0 = solver.constraint(-solver.infinity, 14)
|
96
91
|
constraint0.set_coefficient(x, 1)
|
97
92
|
constraint0.set_coefficient(y, 2)
|
@@ -103,26 +98,17 @@ constraint1.set_coefficient(y, -1)
|
|
103
98
|
constraint2 = solver.constraint(-solver.infinity, 2)
|
104
99
|
constraint2.set_coefficient(x, 1)
|
105
100
|
constraint2.set_coefficient(y, -1)
|
106
|
-
```
|
107
|
-
|
108
|
-
Define the objective function
|
109
101
|
|
110
|
-
|
102
|
+
# define the objective function
|
111
103
|
objective = solver.objective
|
112
104
|
objective.set_coefficient(x, 3)
|
113
105
|
objective.set_coefficient(y, 4)
|
114
106
|
objective.set_maximization
|
115
|
-
```
|
116
|
-
|
117
|
-
Invoke the solver
|
118
107
|
|
119
|
-
|
108
|
+
# invoke the solver
|
120
109
|
solver.solve
|
121
|
-
```
|
122
110
|
|
123
|
-
|
124
|
-
|
125
|
-
```ruby
|
111
|
+
# display the solution
|
126
112
|
opt_solution = 3 * x.solution_value + 4 * y.solution_value
|
127
113
|
puts "Number of variables = #{solver.num_variables}"
|
128
114
|
puts "Number of constraints = #{solver.num_constraints}"
|
@@ -136,37 +122,24 @@ puts "Optimal objective value = #{opt_solution}"
|
|
136
122
|
|
137
123
|
[Guide](https://developers.google.com/optimization/cp/cp_solver)
|
138
124
|
|
139
|
-
Declare the model
|
140
|
-
|
141
125
|
```ruby
|
126
|
+
# declare the model
|
142
127
|
model = ORTools::CpModel.new
|
143
|
-
```
|
144
128
|
|
145
|
-
|
146
|
-
|
147
|
-
```ruby
|
129
|
+
# create the variables
|
148
130
|
num_vals = 3
|
149
131
|
x = model.new_int_var(0, num_vals - 1, "x")
|
150
132
|
y = model.new_int_var(0, num_vals - 1, "y")
|
151
133
|
z = model.new_int_var(0, num_vals - 1, "z")
|
152
|
-
```
|
153
134
|
|
154
|
-
|
155
|
-
|
156
|
-
```ruby
|
135
|
+
# create the constraint
|
157
136
|
model.add(x != y)
|
158
|
-
```
|
159
|
-
|
160
|
-
Call the solver
|
161
137
|
|
162
|
-
|
138
|
+
# call the solver
|
163
139
|
solver = ORTools::CpSolver.new
|
164
140
|
status = solver.solve(model)
|
165
|
-
```
|
166
141
|
|
167
|
-
|
168
|
-
|
169
|
-
```ruby
|
142
|
+
# display the first solution
|
170
143
|
if status == :feasible
|
171
144
|
puts "x = #{solver.value(x)}"
|
172
145
|
puts "y = #{solver.value(y)}"
|
@@ -178,38 +151,25 @@ end
|
|
178
151
|
|
179
152
|
[Guide](https://developers.google.com/optimization/cp/integer_opt_cp)
|
180
153
|
|
181
|
-
Declare the model
|
182
|
-
|
183
154
|
```ruby
|
155
|
+
# declare the model
|
184
156
|
model = ORTools::CpModel.new
|
185
|
-
```
|
186
157
|
|
187
|
-
|
188
|
-
|
189
|
-
```ruby
|
158
|
+
# create the variables
|
190
159
|
var_upper_bound = [50, 45, 37].max
|
191
160
|
x = model.new_int_var(0, var_upper_bound, "x")
|
192
161
|
y = model.new_int_var(0, var_upper_bound, "y")
|
193
162
|
z = model.new_int_var(0, var_upper_bound, "z")
|
194
|
-
```
|
195
|
-
|
196
|
-
Define the constraints
|
197
163
|
|
198
|
-
|
164
|
+
# define the constraints
|
199
165
|
model.add(x*2 + y*7 + z*3 <= 50)
|
200
166
|
model.add(x*3 - y*5 + z*7 <= 45)
|
201
167
|
model.add(x*5 + y*2 - z*6 <= 37)
|
202
|
-
```
|
203
|
-
|
204
|
-
Define the objective function
|
205
168
|
|
206
|
-
|
169
|
+
# define the objective function
|
207
170
|
model.maximize(x*2 + y*2 + z*3)
|
208
|
-
```
|
209
171
|
|
210
|
-
|
211
|
-
|
212
|
-
```ruby
|
172
|
+
# call the solver
|
213
173
|
solver = ORTools::CpSolver.new
|
214
174
|
status = solver.solve(model)
|
215
175
|
|
@@ -226,9 +186,8 @@ end
|
|
226
186
|
|
227
187
|
[Guide](https://developers.google.com/optimization/cp/cryptarithmetic)
|
228
188
|
|
229
|
-
Define the variables
|
230
|
-
|
231
189
|
```ruby
|
190
|
+
# define the variables
|
232
191
|
model = ORTools::CpModel.new
|
233
192
|
|
234
193
|
base = 10
|
@@ -245,20 +204,14 @@ r = model.new_int_var(0, base - 1, "R")
|
|
245
204
|
e = model.new_int_var(0, base - 1, "E")
|
246
205
|
|
247
206
|
letters = [c, p, i, s, f, u, n, t, r, e]
|
248
|
-
```
|
249
207
|
|
250
|
-
|
251
|
-
|
252
|
-
```ruby
|
208
|
+
# define the constraints
|
253
209
|
model.add_all_different(letters)
|
254
210
|
|
255
211
|
model.add(c * base + p + i * base + s + f * base * base + u * base +
|
256
212
|
n == t * base * base * base + r * base * base + u * base + e)
|
257
|
-
```
|
258
213
|
|
259
|
-
|
260
|
-
|
261
|
-
```ruby
|
214
|
+
# define the solution printer
|
262
215
|
class VarArraySolutionPrinter < ORTools::CpSolverSolutionCallback
|
263
216
|
attr_reader :solution_count
|
264
217
|
|
@@ -276,11 +229,8 @@ class VarArraySolutionPrinter < ORTools::CpSolverSolutionCallback
|
|
276
229
|
puts
|
277
230
|
end
|
278
231
|
end
|
279
|
-
```
|
280
|
-
|
281
|
-
Invoke the solver
|
282
232
|
|
283
|
-
|
233
|
+
# invoke the solver
|
284
234
|
solver = ORTools::CpSolver.new
|
285
235
|
solution_printer = VarArraySolutionPrinter.new(letters)
|
286
236
|
status = solver.search_for_all_solutions(model, solution_printer)
|
@@ -298,22 +248,15 @@ puts " - solutions found : %i" % solution_printer.solution_count
|
|
298
248
|
|
299
249
|
[Guide](https://developers.google.com/optimization/cp/queens)
|
300
250
|
|
301
|
-
Declare the model
|
302
|
-
|
303
251
|
```ruby
|
252
|
+
# declare the model
|
304
253
|
board_size = 8
|
305
254
|
model = ORTools::CpModel.new
|
306
|
-
```
|
307
|
-
|
308
|
-
Create the variables
|
309
255
|
|
310
|
-
|
256
|
+
# create the variables
|
311
257
|
queens = board_size.times.map { |i| model.new_int_var(0, board_size - 1, "x%i" % i) }
|
312
|
-
```
|
313
|
-
|
314
|
-
Create the constraints
|
315
258
|
|
316
|
-
|
259
|
+
# create the constraints
|
317
260
|
board_size.times do |i|
|
318
261
|
diag1 = []
|
319
262
|
diag2 = []
|
@@ -328,11 +271,8 @@ board_size.times do |i|
|
|
328
271
|
model.add_all_different(diag1)
|
329
272
|
model.add_all_different(diag2)
|
330
273
|
end
|
331
|
-
```
|
332
274
|
|
333
|
-
|
334
|
-
|
335
|
-
```ruby
|
275
|
+
# create a solution printer
|
336
276
|
class SolutionPrinter < ORTools::CpSolverSolutionCallback
|
337
277
|
attr_reader :solution_count
|
338
278
|
|
@@ -350,11 +290,8 @@ class SolutionPrinter < ORTools::CpSolverSolutionCallback
|
|
350
290
|
puts
|
351
291
|
end
|
352
292
|
end
|
353
|
-
```
|
354
|
-
|
355
|
-
Call the solver and display the results
|
356
293
|
|
357
|
-
|
294
|
+
# call the solver and display the results
|
358
295
|
solver = ORTools::CpSolver.new
|
359
296
|
solution_printer = SolutionPrinter.new(queens)
|
360
297
|
status = solver.search_for_all_solutions(model, solution_printer)
|
@@ -366,25 +303,18 @@ puts "Solutions found : %i" % solution_printer.solution_count
|
|
366
303
|
|
367
304
|
[Guide](https://developers.google.com/optimization/mip/integer_opt)
|
368
305
|
|
369
|
-
Declare the MIP solver
|
370
|
-
|
371
306
|
```ruby
|
307
|
+
# declare the MIP solver
|
372
308
|
solver = ORTools::Solver.new("simple_mip_program", :cbc)
|
373
|
-
```
|
374
|
-
|
375
|
-
Define the variables
|
376
309
|
|
377
|
-
|
310
|
+
# define the variables
|
378
311
|
infinity = solver.infinity
|
379
312
|
x = solver.int_var(0.0, infinity, "x")
|
380
313
|
y = solver.int_var(0.0, infinity, "y")
|
381
314
|
|
382
315
|
puts "Number of variables = #{solver.num_variables}"
|
383
|
-
```
|
384
316
|
|
385
|
-
|
386
|
-
|
387
|
-
```ruby
|
317
|
+
# define the constraints
|
388
318
|
c0 = solver.constraint(-infinity, 17.5)
|
389
319
|
c0.set_coefficient(x, 1)
|
390
320
|
c0.set_coefficient(y, 7)
|
@@ -394,26 +324,17 @@ c1.set_coefficient(x, 1);
|
|
394
324
|
c1.set_coefficient(y, 0);
|
395
325
|
|
396
326
|
puts "Number of constraints = #{solver.num_constraints}"
|
397
|
-
```
|
398
327
|
|
399
|
-
|
400
|
-
|
401
|
-
```ruby
|
328
|
+
# define the objective
|
402
329
|
objective = solver.objective
|
403
330
|
objective.set_coefficient(x, 1)
|
404
331
|
objective.set_coefficient(y, 10)
|
405
332
|
objective.set_maximization
|
406
|
-
```
|
407
|
-
|
408
|
-
Call the solver
|
409
333
|
|
410
|
-
|
334
|
+
# call the solver
|
411
335
|
status = solver.solve
|
412
|
-
```
|
413
336
|
|
414
|
-
|
415
|
-
|
416
|
-
```ruby
|
337
|
+
# display the solution
|
417
338
|
if status == :optimal
|
418
339
|
puts "Solution:"
|
419
340
|
puts "Objective value = #{solver.objective.value}"
|
@@ -428,9 +349,8 @@ end
|
|
428
349
|
|
429
350
|
[Guide](https://developers.google.com/optimization/routing/tsp.html)
|
430
351
|
|
431
|
-
Create the data
|
432
|
-
|
433
352
|
```ruby
|
353
|
+
# create the data
|
434
354
|
data = {}
|
435
355
|
data[:distance_matrix] = [
|
436
356
|
[0, 2451, 713, 1018, 1631, 1374, 2408, 213, 2571, 875, 1420, 2145, 1972],
|
@@ -449,11 +369,8 @@ data[:distance_matrix] = [
|
|
449
369
|
]
|
450
370
|
data[:num_vehicles] = 1
|
451
371
|
data[:depot] = 0
|
452
|
-
```
|
453
|
-
|
454
|
-
Create the distance callback
|
455
372
|
|
456
|
-
|
373
|
+
# create the distance callback
|
457
374
|
manager = ORTools::RoutingIndexManager.new(data[:distance_matrix].length, data[:num_vehicles], data[:depot])
|
458
375
|
routing = ORTools::RoutingModel.new(manager)
|
459
376
|
|
@@ -465,17 +382,11 @@ end
|
|
465
382
|
|
466
383
|
transit_callback_index = routing.register_transit_callback(distance_callback)
|
467
384
|
routing.set_arc_cost_evaluator_of_all_vehicles(transit_callback_index)
|
468
|
-
```
|
469
385
|
|
470
|
-
|
471
|
-
|
472
|
-
```ruby
|
386
|
+
# run the solver
|
473
387
|
assignment = routing.solve(first_solution_strategy: :path_cheaper_arc)
|
474
|
-
```
|
475
|
-
|
476
|
-
Print the solution
|
477
388
|
|
478
|
-
|
389
|
+
# print the solution
|
479
390
|
puts "Objective: #{assignment.objective_value} miles"
|
480
391
|
index = routing.start(0)
|
481
392
|
plan_output = String.new("Route for vehicle 0:\n")
|
@@ -494,9 +405,8 @@ puts plan_output
|
|
494
405
|
|
495
406
|
[Guide](https://developers.google.com/optimization/routing/vrp)
|
496
407
|
|
497
|
-
Create the data
|
498
|
-
|
499
408
|
```ruby
|
409
|
+
# create the data
|
500
410
|
data = {}
|
501
411
|
data[:distance_matrix] = [
|
502
412
|
[0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],
|
@@ -519,11 +429,8 @@ data[:distance_matrix] = [
|
|
519
429
|
]
|
520
430
|
data[:num_vehicles] = 4
|
521
431
|
data[:depot] = 0
|
522
|
-
```
|
523
432
|
|
524
|
-
|
525
|
-
|
526
|
-
```ruby
|
433
|
+
# define the distance callback
|
527
434
|
manager = ORTools::RoutingIndexManager.new(data[:distance_matrix].length, data[:num_vehicles], data[:depot])
|
528
435
|
routing = ORTools::RoutingModel.new(manager)
|
529
436
|
|
@@ -535,26 +442,17 @@ end
|
|
535
442
|
|
536
443
|
transit_callback_index = routing.register_transit_callback(distance_callback)
|
537
444
|
routing.set_arc_cost_evaluator_of_all_vehicles(transit_callback_index)
|
538
|
-
```
|
539
|
-
|
540
|
-
Add a distance dimension
|
541
445
|
|
542
|
-
|
446
|
+
# add a distance dimension
|
543
447
|
dimension_name = "Distance"
|
544
448
|
routing.add_dimension(transit_callback_index, 0, 3000, true, dimension_name)
|
545
449
|
distance_dimension = routing.mutable_dimension(dimension_name)
|
546
450
|
distance_dimension.global_span_cost_coefficient = 100
|
547
|
-
```
|
548
451
|
|
549
|
-
|
550
|
-
|
551
|
-
```ruby
|
452
|
+
# run the solver
|
552
453
|
solution = routing.solve(first_solution_strategy: :path_cheapest_arc)
|
553
|
-
```
|
554
|
-
|
555
|
-
Print the solution
|
556
454
|
|
557
|
-
|
455
|
+
# print the solution
|
558
456
|
max_route_distance = 0
|
559
457
|
data[:num_vehicles].times do |vehicle_id|
|
560
458
|
index = routing.start(vehicle_id)
|
@@ -698,7 +596,6 @@ data[:num_vehicles] = 4
|
|
698
596
|
data[:depot] = 0
|
699
597
|
|
700
598
|
manager = ORTools::RoutingIndexManager.new(data[:distance_matrix].size, data[:num_vehicles], data[:depot])
|
701
|
-
|
702
599
|
routing = ORTools::RoutingModel.new(manager)
|
703
600
|
|
704
601
|
distance_callback = lambda do |from_index, to_index|
|
@@ -906,7 +803,6 @@ data[:depot_capacity] = 2
|
|
906
803
|
data[:depot] = 0
|
907
804
|
|
908
805
|
manager = ORTools::RoutingIndexManager.new(data[:time_matrix].size, data[:num_vehicles], data[:depot])
|
909
|
-
|
910
806
|
routing = ORTools::RoutingModel.new(manager)
|
911
807
|
|
912
808
|
time_callback = lambda do |from_index, to_index|
|
@@ -1014,7 +910,6 @@ data[:num_vehicles] = 4
|
|
1014
910
|
data[:depot] = 0
|
1015
911
|
|
1016
912
|
manager = ORTools::RoutingIndexManager.new(data[:distance_matrix].size, data[:num_vehicles], data[:depot])
|
1017
|
-
|
1018
913
|
routing = ORTools::RoutingModel.new(manager)
|
1019
914
|
|
1020
915
|
distance_callback = lambda do |from_index, to_index|
|
@@ -1103,9 +998,8 @@ routing.solve(
|
|
1103
998
|
|
1104
999
|
[Guide](https://developers.google.com/optimization/bin/knapsack)
|
1105
1000
|
|
1106
|
-
Create the data
|
1107
|
-
|
1108
1001
|
```ruby
|
1002
|
+
# create the data
|
1109
1003
|
values = [
|
1110
1004
|
360, 83, 59, 130, 431, 67, 230, 52, 93, 125, 670, 892, 600, 38, 48, 147,
|
1111
1005
|
78, 256, 63, 17, 120, 164, 432, 35, 92, 110, 22, 42, 50, 323, 514, 28,
|
@@ -1118,17 +1012,11 @@ weights = [[
|
|
1118
1012
|
3, 86, 66, 31, 65, 0, 79, 20, 65, 52, 13
|
1119
1013
|
]]
|
1120
1014
|
capacities = [850]
|
1121
|
-
```
|
1122
|
-
|
1123
|
-
Declare the solver
|
1124
1015
|
|
1125
|
-
|
1016
|
+
# declare the solver
|
1126
1017
|
solver = ORTools::KnapsackSolver.new(:branch_and_bound, "KnapsackExample")
|
1127
|
-
```
|
1128
1018
|
|
1129
|
-
|
1130
|
-
|
1131
|
-
```ruby
|
1019
|
+
# call the solver
|
1132
1020
|
solver.init(values, weights, capacities)
|
1133
1021
|
computed_value = solver.solve
|
1134
1022
|
|
@@ -1152,9 +1040,8 @@ puts "Packed weights: #{packed_weights}"
|
|
1152
1040
|
|
1153
1041
|
[Guide](https://developers.google.com/optimization/bin/multiple_knapsack)
|
1154
1042
|
|
1155
|
-
Create the data
|
1156
|
-
|
1157
1043
|
```ruby
|
1044
|
+
# create the data
|
1158
1045
|
data = {}
|
1159
1046
|
weights = [48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36]
|
1160
1047
|
values = [10, 30, 25, 50, 35, 30, 15, 40, 30, 35, 45, 10, 20, 30, 25]
|
@@ -1165,28 +1052,21 @@ data[:num_items] = weights.length
|
|
1165
1052
|
num_bins = 5
|
1166
1053
|
data[:bins] = (0...num_bins).to_a
|
1167
1054
|
data[:bin_capacities] = [100, 100, 100, 100, 100]
|
1168
|
-
```
|
1169
|
-
|
1170
|
-
Declare the solver
|
1171
1055
|
|
1172
|
-
|
1056
|
+
# declare the solver
|
1173
1057
|
solver = ORTools::Solver.new("simple_mip_program", :cbc)
|
1174
|
-
```
|
1175
1058
|
|
1176
|
-
|
1177
|
-
|
1178
|
-
```ruby
|
1059
|
+
# create the variables
|
1060
|
+
# x[i, j] = 1 if item i is packed in bin j
|
1179
1061
|
x = {}
|
1180
1062
|
data[:items].each do |i|
|
1181
1063
|
data[:bins].each do |j|
|
1182
1064
|
x[[i, j]] = solver.int_var(0, 1, "x_%i_%i" % [i, j])
|
1183
1065
|
end
|
1184
1066
|
end
|
1185
|
-
```
|
1186
|
-
|
1187
|
-
Define the constraints
|
1188
1067
|
|
1189
|
-
|
1068
|
+
# define the constraints
|
1069
|
+
# each item can be in at most one bin
|
1190
1070
|
data[:items].each do |i|
|
1191
1071
|
sum = ORTools::LinearExpr.new
|
1192
1072
|
data[:bins].each do |j|
|
@@ -1195,6 +1075,7 @@ data[:items].each do |i|
|
|
1195
1075
|
solver.add(sum <= 1.0)
|
1196
1076
|
end
|
1197
1077
|
|
1078
|
+
# the amount packed in each bin cannot exceed its capacity
|
1198
1079
|
data[:bins].each do |j|
|
1199
1080
|
weight = ORTools::LinearExpr.new
|
1200
1081
|
data[:items].each do |i|
|
@@ -1202,11 +1083,8 @@ data[:bins].each do |j|
|
|
1202
1083
|
end
|
1203
1084
|
solver.add(weight <= data[:bin_capacities][j])
|
1204
1085
|
end
|
1205
|
-
```
|
1206
|
-
|
1207
|
-
Define the objective
|
1208
1086
|
|
1209
|
-
|
1087
|
+
# define the objective
|
1210
1088
|
objective = solver.objective
|
1211
1089
|
|
1212
1090
|
data[:items].each do |i|
|
@@ -1215,11 +1093,8 @@ data[:items].each do |i|
|
|
1215
1093
|
end
|
1216
1094
|
end
|
1217
1095
|
objective.set_maximization
|
1218
|
-
```
|
1219
1096
|
|
1220
|
-
|
1221
|
-
|
1222
|
-
```ruby
|
1097
|
+
# call the solver and print the solution
|
1223
1098
|
status = solver.solve
|
1224
1099
|
|
1225
1100
|
if status == :optimal
|
@@ -1251,26 +1126,20 @@ end
|
|
1251
1126
|
|
1252
1127
|
[Guide](https://developers.google.com/optimization/bin/bin_packing)
|
1253
1128
|
|
1254
|
-
Create the data
|
1255
|
-
|
1256
1129
|
```ruby
|
1130
|
+
# create the data
|
1257
1131
|
data = {}
|
1258
1132
|
weights = [48, 30, 19, 36, 36, 27, 42, 42, 36, 24, 30]
|
1259
1133
|
data[:weights] = weights
|
1260
1134
|
data[:items] = (0...weights.length).to_a
|
1261
1135
|
data[:bins] = data[:items]
|
1262
1136
|
data[:bin_capacity] = 100
|
1263
|
-
```
|
1264
1137
|
|
1265
|
-
|
1266
|
-
|
1267
|
-
```ruby
|
1138
|
+
# create the mip solver with the CBC backend
|
1268
1139
|
solver = ORTools::Solver.new("simple_mip_program", :cbc)
|
1269
|
-
```
|
1270
|
-
|
1271
|
-
Create the variables
|
1272
1140
|
|
1273
|
-
|
1141
|
+
# variables
|
1142
|
+
# x[i, j] = 1 if item i is packed in bin j
|
1274
1143
|
x = {}
|
1275
1144
|
data[:items].each do |i|
|
1276
1145
|
data[:bins].each do |j|
|
@@ -1278,34 +1147,28 @@ data[:items].each do |i|
|
|
1278
1147
|
end
|
1279
1148
|
end
|
1280
1149
|
|
1150
|
+
# y[j] = 1 if bin j is used
|
1281
1151
|
y = {}
|
1282
1152
|
data[:bins].each do |j|
|
1283
1153
|
y[j] = solver.int_var(0, 1, "y[%i]" % j)
|
1284
1154
|
end
|
1285
|
-
```
|
1286
1155
|
|
1287
|
-
|
1288
|
-
|
1289
|
-
```ruby
|
1156
|
+
# constraints
|
1157
|
+
# each item must be in exactly one bin
|
1290
1158
|
data[:items].each do |i|
|
1291
1159
|
solver.add(solver.sum(data[:bins].map { |j| x[[i, j]] }) == 1)
|
1292
1160
|
end
|
1293
1161
|
|
1162
|
+
# the amount packed in each bin cannot exceed its capacity
|
1294
1163
|
data[:bins].each do |j|
|
1295
1164
|
sum = solver.sum(data[:items].map { |i| x[[i, j]] * data[:weights][i] })
|
1296
1165
|
solver.add(sum <= y[j] * data[:bin_capacity])
|
1297
1166
|
end
|
1298
|
-
```
|
1299
|
-
|
1300
|
-
Define the objective
|
1301
1167
|
|
1302
|
-
|
1168
|
+
# objective: minimize the number of bins used
|
1303
1169
|
solver.minimize(solver.sum(data[:bins].map { |j| y[j] }))
|
1304
|
-
```
|
1305
|
-
|
1306
|
-
Call the solver and print the solution
|
1307
1170
|
|
1308
|
-
|
1171
|
+
# call the solver and print the solution
|
1309
1172
|
if status == :optimal
|
1310
1173
|
num_bins = 0
|
1311
1174
|
data[:bins].each do |j|
|
@@ -1339,27 +1202,20 @@ end
|
|
1339
1202
|
|
1340
1203
|
[Guide](https://developers.google.com/optimization/flow/maxflow)
|
1341
1204
|
|
1342
|
-
Define the data
|
1343
|
-
|
1344
1205
|
```ruby
|
1206
|
+
# define the data
|
1345
1207
|
start_nodes = [0, 0, 0, 1, 1, 2, 2, 3, 3]
|
1346
1208
|
end_nodes = [1, 2, 3, 2, 4, 3, 4, 2, 4]
|
1347
1209
|
capacities = [20, 30, 10, 40, 30, 10, 20, 5, 20]
|
1348
|
-
```
|
1349
|
-
|
1350
|
-
Declare the solver and add the arcs
|
1351
1210
|
|
1352
|
-
|
1211
|
+
# declare the solver and add the arcs
|
1353
1212
|
max_flow = ORTools::SimpleMaxFlow.new
|
1354
1213
|
|
1355
1214
|
start_nodes.length.times do |i|
|
1356
1215
|
max_flow.add_arc_with_capacity(start_nodes[i], end_nodes[i], capacities[i])
|
1357
1216
|
end
|
1358
|
-
```
|
1359
|
-
|
1360
|
-
Invoke the solver and display the results
|
1361
1217
|
|
1362
|
-
|
1218
|
+
# invoke the solver and display the results
|
1363
1219
|
if max_flow.solve(0, 4) == :optimal
|
1364
1220
|
puts "Max flow: #{max_flow.optimal_flow}"
|
1365
1221
|
puts
|
@@ -1383,19 +1239,15 @@ end
|
|
1383
1239
|
|
1384
1240
|
[Guide](https://developers.google.com/optimization/flow/mincostflow)
|
1385
1241
|
|
1386
|
-
Define the data
|
1387
|
-
|
1388
1242
|
```ruby
|
1243
|
+
# define the data
|
1389
1244
|
start_nodes = [ 0, 0, 1, 1, 1, 2, 2, 3, 4]
|
1390
1245
|
end_nodes = [ 1, 2, 2, 3, 4, 3, 4, 4, 2]
|
1391
1246
|
capacities = [15, 8, 20, 4, 10, 15, 4, 20, 5]
|
1392
1247
|
unit_costs = [ 4, 4, 2, 2, 6, 1, 3, 2, 3]
|
1393
1248
|
supplies = [20, 0, 0, -5, -15]
|
1394
|
-
```
|
1395
|
-
|
1396
|
-
Declare the solver and add the arcs
|
1397
1249
|
|
1398
|
-
|
1250
|
+
# declare the solver and add the arcs
|
1399
1251
|
min_cost_flow = ORTools::SimpleMinCostFlow.new
|
1400
1252
|
|
1401
1253
|
start_nodes.length.times do |i|
|
@@ -1407,11 +1259,8 @@ end
|
|
1407
1259
|
supplies.length.times do |i|
|
1408
1260
|
min_cost_flow.set_node_supply(i, supplies[i])
|
1409
1261
|
end
|
1410
|
-
```
|
1411
1262
|
|
1412
|
-
|
1413
|
-
|
1414
|
-
```ruby
|
1263
|
+
# invoke the solver and display the results
|
1415
1264
|
if min_cost_flow.solve == :optimal
|
1416
1265
|
puts "Minimum cost #{min_cost_flow.optimal_cost}"
|
1417
1266
|
puts
|
@@ -1431,13 +1280,12 @@ else
|
|
1431
1280
|
end
|
1432
1281
|
```
|
1433
1282
|
|
1434
|
-
|
1283
|
+
### Assignment
|
1435
1284
|
|
1436
1285
|
[Guide](https://developers.google.com/optimization/assignment/simple_assignment)
|
1437
1286
|
|
1438
|
-
Create the data
|
1439
|
-
|
1440
1287
|
```ruby
|
1288
|
+
# create the data
|
1441
1289
|
cost = [[ 90, 76, 75, 70],
|
1442
1290
|
[ 35, 85, 55, 65],
|
1443
1291
|
[125, 95, 90, 105],
|
@@ -1445,17 +1293,11 @@ cost = [[ 90, 76, 75, 70],
|
|
1445
1293
|
|
1446
1294
|
rows = cost.length
|
1447
1295
|
cols = cost[0].length
|
1448
|
-
```
|
1449
|
-
|
1450
|
-
Create the solver
|
1451
1296
|
|
1452
|
-
|
1297
|
+
# create the solver
|
1453
1298
|
assignment = ORTools::LinearSumAssignment.new
|
1454
|
-
```
|
1455
1299
|
|
1456
|
-
|
1457
|
-
|
1458
|
-
```ruby
|
1300
|
+
# add the costs to the solver
|
1459
1301
|
rows.times do |worker|
|
1460
1302
|
cols.times do |task|
|
1461
1303
|
if cost[worker][task]
|
@@ -1463,11 +1305,8 @@ rows.times do |worker|
|
|
1463
1305
|
end
|
1464
1306
|
end
|
1465
1307
|
end
|
1466
|
-
```
|
1467
|
-
|
1468
|
-
Invoke the solver
|
1469
1308
|
|
1470
|
-
|
1309
|
+
# invoke the solver
|
1471
1310
|
solve_status = assignment.solve
|
1472
1311
|
if solve_status == :optimal
|
1473
1312
|
puts "Total cost = #{assignment.optimal_cost}"
|
@@ -1486,19 +1325,15 @@ elsif solve_status == :possible_overflow
|
|
1486
1325
|
end
|
1487
1326
|
```
|
1488
1327
|
|
1489
|
-
|
1328
|
+
### Assignment as a Min Cost Problem
|
1490
1329
|
|
1491
1330
|
[Guide](https://developers.google.com/optimization/assignment/assignment_min_cost_flow)
|
1492
1331
|
|
1493
|
-
Create the solver
|
1494
|
-
|
1495
1332
|
```ruby
|
1333
|
+
# create the solver
|
1496
1334
|
min_cost_flow = ORTools::SimpleMinCostFlow.new
|
1497
|
-
```
|
1498
|
-
|
1499
|
-
Create the data
|
1500
1335
|
|
1501
|
-
|
1336
|
+
# create the data
|
1502
1337
|
start_nodes = [0, 0, 0, 0] + [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4] + [5, 6, 7, 8]
|
1503
1338
|
end_nodes = [1, 2, 3, 4] + [5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8] + [9, 9, 9, 9]
|
1504
1339
|
capacities = [1, 1, 1, 1] + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + [1, 1, 1, 1]
|
@@ -1507,11 +1342,8 @@ supplies = [4, 0, 0, 0, 0, 0, 0, 0, 0, -4]
|
|
1507
1342
|
source = 0
|
1508
1343
|
sink = 9
|
1509
1344
|
tasks = 4
|
1510
|
-
```
|
1511
1345
|
|
1512
|
-
|
1513
|
-
|
1514
|
-
```ruby
|
1346
|
+
# create the graph and constraints
|
1515
1347
|
start_nodes.length.times do |i|
|
1516
1348
|
min_cost_flow.add_arc_with_capacity_and_unit_cost(
|
1517
1349
|
start_nodes[i], end_nodes[i], capacities[i], costs[i]
|
@@ -1521,11 +1353,8 @@ end
|
|
1521
1353
|
supplies.length.times do |i|
|
1522
1354
|
min_cost_flow.set_node_supply(i, supplies[i])
|
1523
1355
|
end
|
1524
|
-
```
|
1525
1356
|
|
1526
|
-
|
1527
|
-
|
1528
|
-
```ruby
|
1357
|
+
# invoke the solver
|
1529
1358
|
if min_cost_flow.solve == :optimal
|
1530
1359
|
puts "Total cost = #{min_cost_flow.optimal_cost}"
|
1531
1360
|
puts
|
@@ -1545,19 +1374,15 @@ else
|
|
1545
1374
|
end
|
1546
1375
|
```
|
1547
1376
|
|
1548
|
-
|
1377
|
+
### Assignment as a MIP Problem
|
1549
1378
|
|
1550
1379
|
[Guide](https://developers.google.com/optimization/assignment/assignment_mip)
|
1551
1380
|
|
1552
|
-
Create the solver
|
1553
|
-
|
1554
1381
|
```ruby
|
1382
|
+
# create the solver
|
1555
1383
|
solver = ORTools::Solver.new("SolveAssignmentProblemMIP", :cbc)
|
1556
|
-
```
|
1557
1384
|
|
1558
|
-
|
1559
|
-
|
1560
|
-
```ruby
|
1385
|
+
# create the data
|
1561
1386
|
cost = [[90, 76, 75, 70],
|
1562
1387
|
[35, 85, 55, 65],
|
1563
1388
|
[125, 95, 90, 105],
|
@@ -1568,11 +1393,8 @@ cost = [[90, 76, 75, 70],
|
|
1568
1393
|
team1 = [0, 2, 4]
|
1569
1394
|
team2 = [1, 3, 5]
|
1570
1395
|
team_max = 2
|
1571
|
-
```
|
1572
|
-
|
1573
|
-
Create the variables
|
1574
1396
|
|
1575
|
-
|
1397
|
+
# create the variables
|
1576
1398
|
num_workers = cost.length
|
1577
1399
|
num_tasks = cost[1].length
|
1578
1400
|
x = {}
|
@@ -1582,19 +1404,13 @@ num_workers.times do |i|
|
|
1582
1404
|
x[[i, j]] = solver.bool_var("x[#{i},#{j}]")
|
1583
1405
|
end
|
1584
1406
|
end
|
1585
|
-
```
|
1586
|
-
|
1587
|
-
Create the objective function
|
1588
1407
|
|
1589
|
-
|
1408
|
+
# create the objective function
|
1590
1409
|
solver.minimize(solver.sum(
|
1591
1410
|
num_workers.times.flat_map { |i| num_tasks.times.map { |j| x[[i, j]] * cost[i][j] } }
|
1592
1411
|
))
|
1593
|
-
```
|
1594
1412
|
|
1595
|
-
|
1596
|
-
|
1597
|
-
```ruby
|
1413
|
+
# create the constraints
|
1598
1414
|
num_workers.times do |i|
|
1599
1415
|
solver.add(solver.sum(num_tasks.times.map { |j| x[[i, j]] }) <= 1)
|
1600
1416
|
end
|
@@ -1605,11 +1421,8 @@ end
|
|
1605
1421
|
|
1606
1422
|
solver.add(solver.sum(team1.flat_map { |i| num_tasks.times.map { |j| x[[i, j]] } }) <= team_max)
|
1607
1423
|
solver.add(solver.sum(team2.flat_map { |i| num_tasks.times.map { |j| x[[i, j]] } }) <= team_max)
|
1608
|
-
```
|
1609
|
-
|
1610
|
-
Invoke the solver
|
1611
1424
|
|
1612
|
-
|
1425
|
+
# invoke the solver
|
1613
1426
|
sol = solver.solve
|
1614
1427
|
|
1615
1428
|
puts "Total cost = #{solver.objective.value}"
|
@@ -1630,24 +1443,20 @@ puts
|
|
1630
1443
|
puts "Time = #{solver.wall_time} milliseconds"
|
1631
1444
|
```
|
1632
1445
|
|
1633
|
-
|
1446
|
+
### Employee Scheduling
|
1634
1447
|
|
1635
1448
|
[Guide](https://developers.google.com/optimization/scheduling/employee_scheduling)
|
1636
1449
|
|
1637
|
-
Define the data
|
1638
|
-
|
1639
1450
|
```ruby
|
1451
|
+
# define the data
|
1640
1452
|
num_nurses = 4
|
1641
1453
|
num_shifts = 3
|
1642
1454
|
num_days = 3
|
1643
1455
|
all_nurses = num_nurses.times.to_a
|
1644
1456
|
all_shifts = num_shifts.times.to_a
|
1645
1457
|
all_days = num_days.times.to_a
|
1646
|
-
```
|
1647
1458
|
|
1648
|
-
|
1649
|
-
|
1650
|
-
```ruby
|
1459
|
+
# create the variables
|
1651
1460
|
model = ORTools::CpModel.new
|
1652
1461
|
|
1653
1462
|
shifts = {}
|
@@ -1658,11 +1467,8 @@ all_nurses.each do |n|
|
|
1658
1467
|
end
|
1659
1468
|
end
|
1660
1469
|
end
|
1661
|
-
```
|
1662
|
-
|
1663
|
-
Assign nurses to shifts
|
1664
1470
|
|
1665
|
-
|
1471
|
+
# assign nurses to shifts
|
1666
1472
|
all_days.each do |d|
|
1667
1473
|
all_shifts.each do |s|
|
1668
1474
|
model.add(model.sum(all_nurses.map { |n| shifts[[n, d, s]] }) == 1)
|
@@ -1674,11 +1480,8 @@ all_nurses.each do |n|
|
|
1674
1480
|
model.add(model.sum(all_shifts.map { |s| shifts[[n, d, s]] }) <= 1)
|
1675
1481
|
end
|
1676
1482
|
end
|
1677
|
-
```
|
1678
1483
|
|
1679
|
-
|
1680
|
-
|
1681
|
-
```ruby
|
1484
|
+
# assign shifts evenly
|
1682
1485
|
min_shifts_per_nurse = (num_shifts * num_days) / num_nurses
|
1683
1486
|
max_shifts_per_nurse = min_shifts_per_nurse + 1
|
1684
1487
|
all_nurses.each do |n|
|
@@ -1686,11 +1489,8 @@ all_nurses.each do |n|
|
|
1686
1489
|
model.add(num_shifts_worked >= min_shifts_per_nurse)
|
1687
1490
|
model.add(num_shifts_worked <= max_shifts_per_nurse)
|
1688
1491
|
end
|
1689
|
-
```
|
1690
|
-
|
1691
|
-
Create a printer
|
1692
1492
|
|
1693
|
-
|
1493
|
+
# create a printer
|
1694
1494
|
class NursesPartialSolutionPrinter < ORTools::CpSolverSolutionCallback
|
1695
1495
|
attr_reader :solution_count
|
1696
1496
|
|
@@ -1727,11 +1527,8 @@ class NursesPartialSolutionPrinter < ORTools::CpSolverSolutionCallback
|
|
1727
1527
|
@solution_count += 1
|
1728
1528
|
end
|
1729
1529
|
end
|
1730
|
-
```
|
1731
1530
|
|
1732
|
-
|
1733
|
-
|
1734
|
-
```ruby
|
1531
|
+
# call the solver and display the results
|
1735
1532
|
solver = ORTools::CpSolver.new
|
1736
1533
|
a_few_solutions = 5.times.to_a
|
1737
1534
|
solution_printer = NursesPartialSolutionPrinter.new(
|
@@ -1747,7 +1544,110 @@ puts " - wall time : %f s" % solver.wall_time
|
|
1747
1544
|
puts " - solutions found : %i" % solution_printer.solution_count
|
1748
1545
|
```
|
1749
1546
|
|
1750
|
-
|
1547
|
+
### The Job Shop Problem
|
1548
|
+
|
1549
|
+
[Guide](https://developers.google.com/optimization/scheduling/job_shop)
|
1550
|
+
|
1551
|
+
```ruby
|
1552
|
+
# create the model
|
1553
|
+
model = ORTools::CpModel.new
|
1554
|
+
|
1555
|
+
jobs_data = [
|
1556
|
+
[[0, 3], [1, 2], [2, 2]],
|
1557
|
+
[[0, 2], [2, 1], [1, 4]],
|
1558
|
+
[[1, 4], [2, 3]]
|
1559
|
+
]
|
1560
|
+
|
1561
|
+
machines_count = 1 + jobs_data.flat_map { |job| job.map { |task| task[0] } }.max
|
1562
|
+
all_machines = machines_count.times.to_a
|
1563
|
+
|
1564
|
+
# computes horizon dynamically as the sum of all durations
|
1565
|
+
horizon = jobs_data.flat_map { |job| job.map { |task| task[1] } }.sum
|
1566
|
+
|
1567
|
+
# creates job intervals and add to the corresponding machine lists
|
1568
|
+
all_tasks = {}
|
1569
|
+
machine_to_intervals = Hash.new { |hash, key| hash[key] = [] }
|
1570
|
+
|
1571
|
+
jobs_data.each_with_index do |job, job_id|
|
1572
|
+
job.each_with_index do |task, task_id|
|
1573
|
+
machine = task[0]
|
1574
|
+
duration = task[1]
|
1575
|
+
suffix = "_%i_%i" % [job_id, task_id]
|
1576
|
+
start_var = model.new_int_var(0, horizon, "start" + suffix)
|
1577
|
+
duration_var = model.new_int_var(duration, duration, "duration" + suffix)
|
1578
|
+
end_var = model.new_int_var(0, horizon, "end" + suffix)
|
1579
|
+
interval_var = model.new_interval_var(start_var, duration_var, end_var, "interval" + suffix)
|
1580
|
+
all_tasks[[job_id, task_id]] = {start: start_var, end: end_var, interval: interval_var}
|
1581
|
+
machine_to_intervals[machine] << interval_var
|
1582
|
+
end
|
1583
|
+
end
|
1584
|
+
|
1585
|
+
# create and add disjunctive constraints
|
1586
|
+
all_machines.each do |machine|
|
1587
|
+
model.add_no_overlap(machine_to_intervals[machine])
|
1588
|
+
end
|
1589
|
+
|
1590
|
+
# precedences inside a job
|
1591
|
+
jobs_data.each_with_index do |job, job_id|
|
1592
|
+
(job.size - 1).times do |task_id|
|
1593
|
+
model.add(all_tasks[[job_id, task_id + 1]][:start] >= all_tasks[[job_id, task_id]][:end])
|
1594
|
+
end
|
1595
|
+
end
|
1596
|
+
|
1597
|
+
# makespan objective
|
1598
|
+
obj_var = model.new_int_var(0, horizon, "makespan")
|
1599
|
+
model.add_max_equality(obj_var, jobs_data.map.with_index { |job, job_id| all_tasks[[job_id, job.size - 1]][:end] })
|
1600
|
+
model.minimize(obj_var)
|
1601
|
+
|
1602
|
+
# solve model
|
1603
|
+
solver = ORTools::CpSolver.new
|
1604
|
+
status = solver.solve(model)
|
1605
|
+
|
1606
|
+
# create one list of assigned tasks per machine
|
1607
|
+
assigned_jobs = Hash.new { |hash, key| hash[key] = [] }
|
1608
|
+
jobs_data.each_with_index do |job, job_id|
|
1609
|
+
job.each_with_index do |task, task_id|
|
1610
|
+
machine = task[0]
|
1611
|
+
assigned_jobs[machine] << {
|
1612
|
+
start: solver.value(all_tasks[[job_id, task_id]][:start]),
|
1613
|
+
job: job_id,
|
1614
|
+
index: task_id,
|
1615
|
+
duration: task[1]
|
1616
|
+
}
|
1617
|
+
end
|
1618
|
+
end
|
1619
|
+
|
1620
|
+
# create per machine output lines
|
1621
|
+
output = String.new("")
|
1622
|
+
all_machines.each do |machine|
|
1623
|
+
# sort by starting time
|
1624
|
+
assigned_jobs[machine].sort_by! { |v| v[:start] }
|
1625
|
+
sol_line_tasks = "Machine #{machine}: "
|
1626
|
+
sol_line = " "
|
1627
|
+
|
1628
|
+
assigned_jobs[machine].each do |assigned_task|
|
1629
|
+
name = "job_%i_%i" % [assigned_task[:job], assigned_task[:index]]
|
1630
|
+
# add spaces to output to align columns
|
1631
|
+
sol_line_tasks += "%-10s" % name
|
1632
|
+
start = assigned_task[:start]
|
1633
|
+
duration = assigned_task[:duration]
|
1634
|
+
sol_tmp = "[%i,%i]" % [start, start + duration]
|
1635
|
+
# add spaces to output to align columns
|
1636
|
+
sol_line += "%-10s" % sol_tmp
|
1637
|
+
end
|
1638
|
+
|
1639
|
+
sol_line += "\n"
|
1640
|
+
sol_line_tasks += "\n"
|
1641
|
+
output += sol_line_tasks
|
1642
|
+
output += sol_line
|
1643
|
+
end
|
1644
|
+
|
1645
|
+
# finally print the solution found
|
1646
|
+
puts "Optimal Schedule Length: %i" % solver.objective_value
|
1647
|
+
puts output
|
1648
|
+
```
|
1649
|
+
|
1650
|
+
### Sudoku
|
1751
1651
|
|
1752
1652
|
[Example](https://github.com/google/or-tools/blob/stable/examples/python/sudoku_sat.py)
|
1753
1653
|
|
@@ -1821,7 +1721,165 @@ if status == :feasible
|
|
1821
1721
|
end
|
1822
1722
|
```
|
1823
1723
|
|
1824
|
-
|
1724
|
+
### Wedding Seating Chart
|
1725
|
+
|
1726
|
+
[Example](https://github.com/google/or-tools/blob/stable/examples/python/wedding_optimal_chart_sat.py)
|
1727
|
+
|
1728
|
+
```ruby
|
1729
|
+
# From
|
1730
|
+
# Meghan L. Bellows and J. D. Luc Peterson
|
1731
|
+
# "Finding an optimal seating chart for a wedding"
|
1732
|
+
# http://www.improbable.com/news/2012/Optimal-seating-chart.pdf
|
1733
|
+
# http://www.improbable.com/2012/02/12/finding-an-optimal-seating-chart-for-a-wedding
|
1734
|
+
#
|
1735
|
+
# Every year, millions of brides (not to mention their mothers, future
|
1736
|
+
# mothers-in-law, and occasionally grooms) struggle with one of the
|
1737
|
+
# most daunting tasks during the wedding-planning process: the
|
1738
|
+
# seating chart. The guest responses are in, banquet hall is booked,
|
1739
|
+
# menu choices have been made. You think the hard parts are over,
|
1740
|
+
# but you have yet to embark upon the biggest headache of them all.
|
1741
|
+
# In order to make this process easier, we present a mathematical
|
1742
|
+
# formulation that models the seating chart problem. This model can
|
1743
|
+
# be solved to find the optimal arrangement of guests at tables.
|
1744
|
+
# At the very least, it can provide a starting point and hopefully
|
1745
|
+
# minimize stress and arguments.
|
1746
|
+
#
|
1747
|
+
# Adapted from
|
1748
|
+
# https://github.com/google/or-tools/blob/stable/examples/python/wedding_optimal_chart_sat.py
|
1749
|
+
|
1750
|
+
# Easy problem (from the paper)
|
1751
|
+
# num_tables = 2
|
1752
|
+
# table_capacity = 10
|
1753
|
+
# min_known_neighbors = 1
|
1754
|
+
|
1755
|
+
# Slightly harder problem (also from the paper)
|
1756
|
+
num_tables = 5
|
1757
|
+
table_capacity = 4
|
1758
|
+
min_known_neighbors = 1
|
1759
|
+
|
1760
|
+
# Connection matrix: who knows who, and how strong
|
1761
|
+
# is the relation
|
1762
|
+
c = [
|
1763
|
+
[1, 50, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
1764
|
+
[50, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
1765
|
+
[1, 1, 1, 50, 1, 1, 1, 1, 10, 0, 0, 0, 0, 0, 0, 0, 0],
|
1766
|
+
[1, 1, 50, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
1767
|
+
[1, 1, 1, 1, 1, 50, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
1768
|
+
[1, 1, 1, 1, 50, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
1769
|
+
[1, 1, 1, 1, 1, 1, 1, 50, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
1770
|
+
[1, 1, 1, 1, 1, 1, 50, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
1771
|
+
[1, 1, 10, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
1772
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 50, 1, 1, 1, 1, 1, 1],
|
1773
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 1, 1, 1, 1, 1, 1, 1],
|
1774
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
|
1775
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
|
1776
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
|
1777
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
|
1778
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
|
1779
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]
|
1780
|
+
]
|
1781
|
+
|
1782
|
+
# Names of the guests. B: Bride side, G: Groom side
|
1783
|
+
names = [
|
1784
|
+
"Deb (B)", "John (B)", "Martha (B)", "Travis (B)", "Allan (B)",
|
1785
|
+
"Lois (B)", "Jayne (B)", "Brad (B)", "Abby (B)", "Mary Helen (G)",
|
1786
|
+
"Lee (G)", "Annika (G)", "Carl (G)", "Colin (G)", "Shirley (G)",
|
1787
|
+
"DeAnn (G)", "Lori (G)"
|
1788
|
+
]
|
1789
|
+
|
1790
|
+
num_guests = c.size
|
1791
|
+
|
1792
|
+
all_tables = num_tables.times.to_a
|
1793
|
+
all_guests = num_guests.times.to_a
|
1794
|
+
|
1795
|
+
# create the cp model
|
1796
|
+
model = ORTools::CpModel.new
|
1797
|
+
|
1798
|
+
# decision variables
|
1799
|
+
seats = {}
|
1800
|
+
all_tables.each do |t|
|
1801
|
+
all_guests.each do |g|
|
1802
|
+
seats[[t, g]] = model.new_bool_var("guest %i seats on table %i" % [g, t])
|
1803
|
+
end
|
1804
|
+
end
|
1805
|
+
|
1806
|
+
colocated = {}
|
1807
|
+
(num_guests - 1).times do |g1|
|
1808
|
+
(g1 + 1).upto(num_guests - 1) do |g2|
|
1809
|
+
colocated[[g1, g2]] = model.new_bool_var("guest %i seats with guest %i" % [g1, g2])
|
1810
|
+
end
|
1811
|
+
end
|
1812
|
+
|
1813
|
+
same_table = {}
|
1814
|
+
(num_guests - 1).times do |g1|
|
1815
|
+
(g1 + 1).upto(num_guests - 1) do |g2|
|
1816
|
+
all_tables.each do |t|
|
1817
|
+
same_table[[g1, g2, t]] = model.new_bool_var("guest %i seats with guest %i on table %i" % [g1, g2, t])
|
1818
|
+
end
|
1819
|
+
end
|
1820
|
+
end
|
1821
|
+
|
1822
|
+
# Objective
|
1823
|
+
model.maximize(model.sum((num_guests - 1).times.flat_map { |g1| (g1 + 1).upto(num_guests - 1).select { |g2| c[g1][g2] > 0 }.map { |g2| colocated[[g1, g2]] * c[g1][g2] } }))
|
1824
|
+
|
1825
|
+
#
|
1826
|
+
# Constraints
|
1827
|
+
#
|
1828
|
+
|
1829
|
+
# Everybody seats at one table.
|
1830
|
+
all_guests.each do |g|
|
1831
|
+
model.add(model.sum(all_tables.map { |t| seats[[t, g]] }) == 1)
|
1832
|
+
end
|
1833
|
+
|
1834
|
+
# Tables have a max capacity.
|
1835
|
+
all_tables.each do |t|
|
1836
|
+
model.add(model.sum(all_guests.map { |g| seats[[t, g]] }) <= table_capacity)
|
1837
|
+
end
|
1838
|
+
|
1839
|
+
# Link colocated with seats
|
1840
|
+
(num_guests - 1).times do |g1|
|
1841
|
+
(g1 + 1).upto(num_guests - 1) do |g2|
|
1842
|
+
all_tables.each do |t|
|
1843
|
+
# Link same_table and seats.
|
1844
|
+
model.add_bool_or([seats[[t, g1]].not, seats[[t, g2]].not, same_table[[g1, g2, t]]])
|
1845
|
+
model.add_implication(same_table[[g1, g2, t]], seats[[t, g1]])
|
1846
|
+
model.add_implication(same_table[[g1, g2, t]], seats[[t, g2]])
|
1847
|
+
end
|
1848
|
+
|
1849
|
+
# Link colocated and same_table.
|
1850
|
+
model.add(model.sum(all_tables.map { |t| same_table[[g1, g2, t]] }) == colocated[[g1, g2]])
|
1851
|
+
end
|
1852
|
+
end
|
1853
|
+
|
1854
|
+
# Min known neighbors rule.
|
1855
|
+
all_tables.each do |t|
|
1856
|
+
model.add(
|
1857
|
+
model.sum(
|
1858
|
+
(num_guests - 1).times.flat_map { |g1|
|
1859
|
+
(g1 + 1).upto(num_guests - 1).select { |g2| c[g1][g2] > 0 }.flat_map { |g2|
|
1860
|
+
all_tables.map { |t2| same_table[[g1, g2, t2]] }
|
1861
|
+
}
|
1862
|
+
}
|
1863
|
+
) >= min_known_neighbors
|
1864
|
+
)
|
1865
|
+
end
|
1866
|
+
|
1867
|
+
# Symmetry breaking. First guest seats on the first table.
|
1868
|
+
model.add(seats[[0, 0]] == 1)
|
1869
|
+
|
1870
|
+
# Solve model
|
1871
|
+
solver = ORTools::CpSolver.new
|
1872
|
+
solution_printer = WeddingChartPrinter.new(seats, names, num_tables, num_guests)
|
1873
|
+
solver.solve_with_solution_callback(model, solution_printer)
|
1874
|
+
|
1875
|
+
puts "Statistics"
|
1876
|
+
puts " - conflicts : %i" % solver.num_conflicts
|
1877
|
+
puts " - branches : %i" % solver.num_branches
|
1878
|
+
puts " - wall time : %f s" % solver.wall_time
|
1879
|
+
puts " - num solutions: %i" % solution_printer.num_solutions
|
1880
|
+
```
|
1881
|
+
|
1882
|
+
### Set Partitioning
|
1825
1883
|
|
1826
1884
|
[Example](https://pythonhosted.org/PuLP/CaseStudies/a_set_partitioning_problem.html)
|
1827
1885
|
|