or-tools 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|