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.
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
- ```ruby
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
- Define the constraints
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
- ```ruby
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
- ```ruby
108
+ # invoke the solver
120
109
  solver.solve
121
- ```
122
110
 
123
- Display the solution
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
- Create the variables
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
- Create the constraint
155
-
156
- ```ruby
135
+ # create the constraint
157
136
  model.add(x != y)
158
- ```
159
-
160
- Call the solver
161
137
 
162
- ```ruby
138
+ # call the solver
163
139
  solver = ORTools::CpSolver.new
164
140
  status = solver.solve(model)
165
- ```
166
141
 
167
- Display the first solution
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
- Create the variables
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
- ```ruby
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
- ```ruby
169
+ # define the objective function
207
170
  model.maximize(x*2 + y*2 + z*3)
208
- ```
209
171
 
210
- Call the solver
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
- Define the constraints
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
- Define the solution printer
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
- ```ruby
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
- ```ruby
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
- ```ruby
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
- Create a solution printer
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
- ```ruby
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
- ```ruby
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
- Define the constraints
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
- Define the objective
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
- ```ruby
334
+ # call the solver
411
335
  status = solver.solve
412
- ```
413
336
 
414
- Display the solution
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
- ```ruby
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
- Run the solver
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
- ```ruby
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
- Define the distance callback
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
- ```ruby
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
- Run the solver
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
- ```ruby
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
- ```ruby
1016
+ # declare the solver
1126
1017
  solver = ORTools::KnapsackSolver.new(:branch_and_bound, "KnapsackExample")
1127
- ```
1128
1018
 
1129
- Call the solver
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
- ```ruby
1056
+ # declare the solver
1173
1057
  solver = ORTools::Solver.new("simple_mip_program", :cbc)
1174
- ```
1175
1058
 
1176
- Create the variables
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
- ```ruby
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
- ```ruby
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
- Call the solver and print the solution
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
- Declare the solver
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
- ```ruby
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
- Define the constraints
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
- ```ruby
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
- ```ruby
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
- ```ruby
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
- ```ruby
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
- ```ruby
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
- Invoke the solver and display the results
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
- ## Assignment
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
- ```ruby
1297
+ # create the solver
1453
1298
  assignment = ORTools::LinearSumAssignment.new
1454
- ```
1455
1299
 
1456
- Add the costs to the solver
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
- ```ruby
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
- ## Assignment as a Min Cost Problem
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
- ```ruby
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
- Create the graph and constraints
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
- Invoke the solver
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
- ## Assignment as a MIP Problem
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
- Create the data
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
- ```ruby
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
- ```ruby
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
- Create the constraints
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
- ```ruby
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
- ## Employee Scheduling
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
- Create the variables
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
- ```ruby
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
- Assign shifts evenly
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
- ```ruby
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
- Call the solver and display the results
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
- ## Sudoku
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
- ## Set Partitioning
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