or-tools 0.1.0 → 0.1.1
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 +6 -0
- data/README.md +748 -2
- data/ext/or-tools/ext.cpp +465 -7
- data/ext/or-tools/extconf.rb +60 -1
- data/lib/or-tools.rb +8 -0
- data/lib/or_tools/comparison.rb +11 -0
- data/lib/or_tools/comparison_operators.rb +9 -0
- data/lib/or_tools/cp_model.rb +29 -0
- data/lib/or_tools/cp_solver.rb +4 -0
- data/lib/or_tools/ext.bundle +0 -0
- data/lib/or_tools/linear_expr.rb +19 -0
- data/lib/or_tools/routing_model.rb +17 -0
- data/lib/or_tools/sat_int_var.rb +9 -0
- data/lib/or_tools/sat_linear_expr.rb +35 -0
- data/lib/or_tools/solver.rb +7 -0
- data/lib/or_tools/version.rb +1 -1
- metadata +10 -2
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# OR-Tools
|
2
2
|
|
3
|
-
[OR-Tools](https://
|
3
|
+
[OR-Tools](https://github.com/google/or-tools) - operations research tools - for Ruby
|
4
|
+
|
5
|
+
[](https://travis-ci.org/ankane/or-tools)
|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
@@ -18,19 +20,102 @@ gem 'or-tools'
|
|
18
20
|
|
19
21
|
## Getting Started
|
20
22
|
|
23
|
+
Linear Optimization
|
24
|
+
|
25
|
+
- [The Glop Linear Solver](#the-glop-linear-solver)
|
26
|
+
|
21
27
|
Constraint Optimization
|
22
28
|
|
23
29
|
- [CP-SAT Solver](#cp-sat-solver)
|
30
|
+
- [Solving an Optimization Problem](#solving-an-optimization-problem)
|
31
|
+
|
32
|
+
Integer Optimization
|
33
|
+
|
34
|
+
- [Mixed-Integer Programming](mixed-integer-programming)
|
35
|
+
|
36
|
+
Routing
|
37
|
+
|
38
|
+
- [Traveling Salesperson Problem (TSP)](#traveling-salesperson-problem-tsp)
|
39
|
+
- [Vehicle Routing Problem (VRP)](#vehicle-routing-problem-vrp)
|
40
|
+
- [Routing Options](#routing-options)
|
24
41
|
|
25
42
|
Bin Packing
|
26
43
|
|
27
44
|
- [The Knapsack Problem](#the-knapsack-problem)
|
45
|
+
- [Multiple Knapsacks](#multiple-knapsacks)
|
46
|
+
- [Bin Packing Problem](#bin-packing-problem)
|
28
47
|
|
29
48
|
Network Flows
|
30
49
|
|
31
50
|
- [Maximum Flows](#maximum-flows)
|
32
51
|
- [Minimum Cost Flows](#minimum-cost-flows)
|
33
52
|
|
53
|
+
Assignment
|
54
|
+
|
55
|
+
- [Assignment](#assignment)
|
56
|
+
- [Assignment as a Min Cost Problem](#assignment-as-a-min-cost-problem)
|
57
|
+
- [Assignment as a MIP Problem](#assignment-as-a-mip-problem)
|
58
|
+
|
59
|
+
### The Glop Linear Solver
|
60
|
+
|
61
|
+
[Guide](https://developers.google.com/optimization/lp/glop)
|
62
|
+
|
63
|
+
Declare the solver
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
solver = ORTools::Solver.new("LinearProgrammingExample", :glop)
|
67
|
+
```
|
68
|
+
|
69
|
+
Create the variables
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
x = solver.num_var(0, solver.infinity, "x")
|
73
|
+
y = solver.num_var(0, solver.infinity, "y")
|
74
|
+
```
|
75
|
+
|
76
|
+
Define the constraints
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
constraint0 = solver.constraint(-solver.infinity, 14)
|
80
|
+
constraint0.set_coefficient(x, 1)
|
81
|
+
constraint0.set_coefficient(y, 2)
|
82
|
+
|
83
|
+
constraint1 = solver.constraint(0, solver.infinity)
|
84
|
+
constraint1.set_coefficient(x, 3)
|
85
|
+
constraint1.set_coefficient(y, -1)
|
86
|
+
|
87
|
+
constraint2 = solver.constraint(-solver.infinity, 2)
|
88
|
+
constraint2.set_coefficient(x, 1)
|
89
|
+
constraint2.set_coefficient(y, -1)
|
90
|
+
```
|
91
|
+
|
92
|
+
Define the objective function
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
objective = solver.objective
|
96
|
+
objective.set_coefficient(x, 3)
|
97
|
+
objective.set_coefficient(y, 4)
|
98
|
+
objective.set_maximization
|
99
|
+
```
|
100
|
+
|
101
|
+
Invoke the solver
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
solver.solve
|
105
|
+
```
|
106
|
+
|
107
|
+
Display the solution
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
opt_solution = 3 * x.solution_value + 4 * y.solution_value
|
111
|
+
puts "Number of variables = #{solver.num_variables}"
|
112
|
+
puts "Number of constraints = #{solver.num_constraints}"
|
113
|
+
puts "Solution:"
|
114
|
+
puts "x = #{x.solution_value}"
|
115
|
+
puts "y = #{y.solution_value}"
|
116
|
+
puts "Optimal objective value = #{opt_solution}"
|
117
|
+
```
|
118
|
+
|
34
119
|
### CP-SAT Solver
|
35
120
|
|
36
121
|
[Guide](https://developers.google.com/optimization/cp/cp_solver)
|
@@ -53,7 +138,7 @@ z = model.new_int_var(0, num_vals - 1, "z")
|
|
53
138
|
Create the constraint
|
54
139
|
|
55
140
|
```ruby
|
56
|
-
model.
|
141
|
+
model.add(x != y)
|
57
142
|
```
|
58
143
|
|
59
144
|
Call the solver
|
@@ -73,6 +158,281 @@ if status == :feasible
|
|
73
158
|
end
|
74
159
|
```
|
75
160
|
|
161
|
+
### Solving an Optimization Problem
|
162
|
+
|
163
|
+
[Guide](https://developers.google.com/optimization/cp/integer_opt_cp)
|
164
|
+
|
165
|
+
Declare the model
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
model = ORTools::CpModel.new
|
169
|
+
```
|
170
|
+
|
171
|
+
Create the variables
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
var_upper_bound = [50, 45, 37].max
|
175
|
+
x = model.new_int_var(0, var_upper_bound, "x")
|
176
|
+
y = model.new_int_var(0, var_upper_bound, "y")
|
177
|
+
z = model.new_int_var(0, var_upper_bound, "z")
|
178
|
+
```
|
179
|
+
|
180
|
+
Define the constraints
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
model.add(x*2 + y*7 + z*3 <= 50)
|
184
|
+
model.add(x*3 - y*5 + z*7 <= 45)
|
185
|
+
model.add(x*5 + y*2 - z*6 <= 37)
|
186
|
+
```
|
187
|
+
|
188
|
+
Define the objective function
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
model.maximize(x*2 + y*2 + z*3)
|
192
|
+
```
|
193
|
+
|
194
|
+
Call the solver
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
solver = ORTools::CpSolver.new
|
198
|
+
status = solver.solve(model)
|
199
|
+
|
200
|
+
if status == :optimal
|
201
|
+
puts "Maximum of objective function: #{solver.objective_value}"
|
202
|
+
puts
|
203
|
+
puts "x value: #{solver.value(x)}"
|
204
|
+
puts "y value: #{solver.value(y)}"
|
205
|
+
puts "z value: #{solver.value(z)}"
|
206
|
+
end
|
207
|
+
```
|
208
|
+
|
209
|
+
### Mixed-Integer Programming
|
210
|
+
|
211
|
+
[Guide](https://developers.google.com/optimization/mip/integer_opt)
|
212
|
+
|
213
|
+
Declare the MIP solver
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
solver = ORTools::Solver.new("simple_mip_program", :cbc)
|
217
|
+
```
|
218
|
+
|
219
|
+
Define the variables
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
infinity = solver.infinity
|
223
|
+
x = solver.int_var(0.0, infinity, "x")
|
224
|
+
y = solver.int_var(0.0, infinity, "y")
|
225
|
+
|
226
|
+
puts "Number of variables = #{solver.num_variables}"
|
227
|
+
```
|
228
|
+
|
229
|
+
Define the constraints
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
c0 = solver.constraint(-infinity, 17.5)
|
233
|
+
c0.set_coefficient(x, 1)
|
234
|
+
c0.set_coefficient(y, 7)
|
235
|
+
|
236
|
+
c1 = solver.constraint(-infinity, 3.5)
|
237
|
+
c1.set_coefficient(x, 1);
|
238
|
+
c1.set_coefficient(y, 0);
|
239
|
+
|
240
|
+
puts "Number of constraints = #{solver.num_constraints}"
|
241
|
+
```
|
242
|
+
|
243
|
+
Define the objective
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
objective = solver.objective
|
247
|
+
objective.set_coefficient(x, 1)
|
248
|
+
objective.set_coefficient(y, 10)
|
249
|
+
objective.set_maximization
|
250
|
+
```
|
251
|
+
|
252
|
+
Call the solver
|
253
|
+
|
254
|
+
```ruby
|
255
|
+
status = solver.solve
|
256
|
+
```
|
257
|
+
|
258
|
+
Display the solution
|
259
|
+
|
260
|
+
```ruby
|
261
|
+
if status == :optimal
|
262
|
+
puts "Solution:"
|
263
|
+
puts "Objective value = #{solver.objective.value}"
|
264
|
+
puts "x = #{x.solution_value}"
|
265
|
+
puts "y = #{y.solution_value}"
|
266
|
+
else
|
267
|
+
puts "The problem does not have an optimal solution."
|
268
|
+
end
|
269
|
+
```
|
270
|
+
|
271
|
+
### Traveling Salesperson Problem (TSP)
|
272
|
+
|
273
|
+
[Guide](https://developers.google.com/optimization/routing/tsp.html)
|
274
|
+
|
275
|
+
Create the data
|
276
|
+
|
277
|
+
```ruby
|
278
|
+
data = {}
|
279
|
+
data[:distance_matrix] = [
|
280
|
+
[0, 2451, 713, 1018, 1631, 1374, 2408, 213, 2571, 875, 1420, 2145, 1972],
|
281
|
+
[2451, 0, 1745, 1524, 831, 1240, 959, 2596, 403, 1589, 1374, 357, 579],
|
282
|
+
[713, 1745, 0, 355, 920, 803, 1737, 851, 1858, 262, 940, 1453, 1260],
|
283
|
+
[1018, 1524, 355, 0, 700, 862, 1395, 1123, 1584, 466, 1056, 1280, 987],
|
284
|
+
[1631, 831, 920, 700, 0, 663, 1021, 1769, 949, 796, 879, 586, 371],
|
285
|
+
[1374, 1240, 803, 862, 663, 0, 1681, 1551, 1765, 547, 225, 887, 999],
|
286
|
+
[2408, 959, 1737, 1395, 1021, 1681, 0, 2493, 678, 1724, 1891, 1114, 701],
|
287
|
+
[213, 2596, 851, 1123, 1769, 1551, 2493, 0, 2699, 1038, 1605, 2300, 2099],
|
288
|
+
[2571, 403, 1858, 1584, 949, 1765, 678, 2699, 0, 1744, 1645, 653, 600],
|
289
|
+
[875, 1589, 262, 466, 796, 547, 1724, 1038, 1744, 0, 679, 1272, 1162],
|
290
|
+
[1420, 1374, 940, 1056, 879, 225, 1891, 1605, 1645, 679, 0, 1017, 1200],
|
291
|
+
[2145, 357, 1453, 1280, 586, 887, 1114, 2300, 653, 1272, 1017, 0, 504],
|
292
|
+
[1972, 579, 1260, 987, 371, 999, 701, 2099, 600, 1162, 1200, 504, 0]
|
293
|
+
]
|
294
|
+
data[:num_vehicles] = 1
|
295
|
+
data[:depot] = 0
|
296
|
+
```
|
297
|
+
|
298
|
+
Create the distance callback
|
299
|
+
|
300
|
+
```ruby
|
301
|
+
manager = ORTools::RoutingIndexManager.new(data[:distance_matrix].length, data[:num_vehicles], data[:depot])
|
302
|
+
routing = ORTools::RoutingModel.new(manager)
|
303
|
+
|
304
|
+
distance_callback = lambda do |from_index, to_index|
|
305
|
+
from_node = manager.index_to_node(from_index)
|
306
|
+
to_node = manager.index_to_node(to_index)
|
307
|
+
data[:distance_matrix][from_node][to_node]
|
308
|
+
end
|
309
|
+
|
310
|
+
transit_callback_index = routing.register_transit_callback(distance_callback)
|
311
|
+
routing.set_arc_cost_evaluator_of_all_vehicles(transit_callback_index)
|
312
|
+
```
|
313
|
+
|
314
|
+
Run the solver
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
assignment = routing.solve(first_solution_strategy: :path_cheaper_arc)
|
318
|
+
```
|
319
|
+
|
320
|
+
Print the solution
|
321
|
+
|
322
|
+
```ruby
|
323
|
+
puts "Objective: #{assignment.objective_value} miles"
|
324
|
+
index = routing.start(0)
|
325
|
+
plan_output = String.new("Route for vehicle 0:\n")
|
326
|
+
route_distance = 0
|
327
|
+
while !routing.end?(index)
|
328
|
+
plan_output += " #{manager.index_to_node(index)} ->"
|
329
|
+
previous_index = index
|
330
|
+
index = assignment.value(routing.next_var(index))
|
331
|
+
route_distance += routing.arc_cost_for_vehicle(previous_index, index, 0)
|
332
|
+
end
|
333
|
+
plan_output += " #{manager.index_to_node(index)}\n"
|
334
|
+
puts plan_output
|
335
|
+
```
|
336
|
+
|
337
|
+
### Vehicle Routing Problem (VRP)
|
338
|
+
|
339
|
+
[Guide](https://developers.google.com/optimization/routing/vrp)
|
340
|
+
|
341
|
+
Create the data
|
342
|
+
|
343
|
+
```ruby
|
344
|
+
data = {}
|
345
|
+
data[:distance_matrix] = [
|
346
|
+
[0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],
|
347
|
+
[548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],
|
348
|
+
[776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],
|
349
|
+
[696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],
|
350
|
+
[582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],
|
351
|
+
[274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],
|
352
|
+
[502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],
|
353
|
+
[194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],
|
354
|
+
[308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],
|
355
|
+
[194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],
|
356
|
+
[536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],
|
357
|
+
[502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],
|
358
|
+
[388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],
|
359
|
+
[354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],
|
360
|
+
[468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],
|
361
|
+
[776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],
|
362
|
+
[662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0]
|
363
|
+
]
|
364
|
+
data[:num_vehicles] = 4
|
365
|
+
data[:depot] = 0
|
366
|
+
```
|
367
|
+
|
368
|
+
Define the distance callback
|
369
|
+
|
370
|
+
```ruby
|
371
|
+
manager = ORTools::RoutingIndexManager.new(data[:distance_matrix].length, data[:num_vehicles], data[:depot])
|
372
|
+
routing = ORTools::RoutingModel.new(manager)
|
373
|
+
|
374
|
+
distance_callback = lambda do |from_index, to_index|
|
375
|
+
from_node = manager.index_to_node(from_index)
|
376
|
+
to_node = manager.index_to_node(to_index)
|
377
|
+
data[:distance_matrix][from_node][to_node]
|
378
|
+
end
|
379
|
+
|
380
|
+
transit_callback_index = routing.register_transit_callback(distance_callback)
|
381
|
+
routing.set_arc_cost_evaluator_of_all_vehicles(transit_callback_index)
|
382
|
+
```
|
383
|
+
|
384
|
+
Add a distance dimension
|
385
|
+
|
386
|
+
```ruby
|
387
|
+
dimension_name = "Distance"
|
388
|
+
routing.add_dimension(transit_callback_index, 0, 3000, true, dimension_name)
|
389
|
+
distance_dimension = routing.mutable_dimension(dimension_name)
|
390
|
+
distance_dimension.global_span_cost_coefficient = 100
|
391
|
+
```
|
392
|
+
|
393
|
+
Run the solver
|
394
|
+
|
395
|
+
```ruby
|
396
|
+
solution = routing.solve(first_solution_strategy: :path_cheapest_arc)
|
397
|
+
```
|
398
|
+
|
399
|
+
Print the solution
|
400
|
+
|
401
|
+
```ruby
|
402
|
+
max_route_distance = 0
|
403
|
+
data[:num_vehicles].times do |vehicle_id|
|
404
|
+
index = routing.start(vehicle_id)
|
405
|
+
plan_output = String.new("Route for vehicle #{vehicle_id}:\n")
|
406
|
+
route_distance = 0
|
407
|
+
while !routing.end?(index)
|
408
|
+
plan_output += " #{manager.index_to_node(index)} -> "
|
409
|
+
previous_index = index
|
410
|
+
index = solution.value(routing.next_var(index))
|
411
|
+
route_distance += routing.arc_cost_for_vehicle(previous_index, index, vehicle_id)
|
412
|
+
end
|
413
|
+
plan_output += "#{manager.index_to_node(index)}\n"
|
414
|
+
plan_output += "Distance of the route: #{route_distance}m\n\n"
|
415
|
+
puts plan_output
|
416
|
+
max_route_distance = [route_distance, max_route_distance].max
|
417
|
+
end
|
418
|
+
puts "Maximum of the route distances: #{max_route_distance}m"
|
419
|
+
```
|
420
|
+
|
421
|
+
### Routing Options
|
422
|
+
|
423
|
+
[Guide](https://developers.google.com/optimization/routing/routing_options)
|
424
|
+
|
425
|
+
```ruby
|
426
|
+
routing.solve(
|
427
|
+
solution_limit: 10,
|
428
|
+
time_limit: 10, # seconds,
|
429
|
+
lns_time_limit: 10, # seconds
|
430
|
+
first_solution_strategy: :path_cheapest_arc,
|
431
|
+
local_search_metaheuristic: :guided_local_search,
|
432
|
+
log_search: true
|
433
|
+
)
|
434
|
+
```
|
435
|
+
|
76
436
|
### The Knapsack Problem
|
77
437
|
|
78
438
|
[Guide](https://developers.google.com/optimization/bin/knapsack)
|
@@ -122,6 +482,193 @@ puts "Packed items: #{packed_items}"
|
|
122
482
|
puts "Packed weights: #{packed_weights}"
|
123
483
|
```
|
124
484
|
|
485
|
+
### Multiple Knapsacks
|
486
|
+
|
487
|
+
[Guide](https://developers.google.com/optimization/bin/multiple_knapsack)
|
488
|
+
|
489
|
+
Create the data
|
490
|
+
|
491
|
+
```ruby
|
492
|
+
data = {}
|
493
|
+
weights = [48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36]
|
494
|
+
values = [10, 30, 25, 50, 35, 30, 15, 40, 30, 35, 45, 10, 20, 30, 25]
|
495
|
+
data[:weights] = weights
|
496
|
+
data[:values] = values
|
497
|
+
data[:items] = (0...weights.length).to_a
|
498
|
+
data[:num_items] = weights.length
|
499
|
+
num_bins = 5
|
500
|
+
data[:bins] = (0...num_bins).to_a
|
501
|
+
data[:bin_capacities] = [100, 100, 100, 100, 100]
|
502
|
+
```
|
503
|
+
|
504
|
+
Declare the solver
|
505
|
+
|
506
|
+
```ruby
|
507
|
+
solver = ORTools::Solver.new("simple_mip_program", :cbc)
|
508
|
+
```
|
509
|
+
|
510
|
+
Create the variables
|
511
|
+
|
512
|
+
```ruby
|
513
|
+
x = {}
|
514
|
+
data[:items].each do |i|
|
515
|
+
data[:bins].each do |j|
|
516
|
+
x[[i, j]] = solver.int_var(0, 1, "x_%i_%i" % [i, j])
|
517
|
+
end
|
518
|
+
end
|
519
|
+
```
|
520
|
+
|
521
|
+
Define the constraints
|
522
|
+
|
523
|
+
```ruby
|
524
|
+
data[:items].each do |i|
|
525
|
+
sum = ORTools::LinearExpr.new
|
526
|
+
data[:bins].each do |j|
|
527
|
+
sum += x[[i, j]]
|
528
|
+
end
|
529
|
+
solver.add(sum <= 1.0)
|
530
|
+
end
|
531
|
+
|
532
|
+
data[:bins].each do |j|
|
533
|
+
weight = ORTools::LinearExpr.new
|
534
|
+
data[:items].each do |i|
|
535
|
+
weight += x[[i, j]] * data[:weights][i]
|
536
|
+
end
|
537
|
+
solver.add(weight <= data[:bin_capacities][j])
|
538
|
+
end
|
539
|
+
```
|
540
|
+
|
541
|
+
Define the objective
|
542
|
+
|
543
|
+
```ruby
|
544
|
+
objective = solver.objective
|
545
|
+
|
546
|
+
data[:items].each do |i|
|
547
|
+
data[:bins].each do |j|
|
548
|
+
objective.set_coefficient(x[[i, j]], data[:values][i])
|
549
|
+
end
|
550
|
+
end
|
551
|
+
objective.set_maximization
|
552
|
+
```
|
553
|
+
|
554
|
+
Call the solver and print the solution
|
555
|
+
|
556
|
+
```ruby
|
557
|
+
status = solver.solve
|
558
|
+
|
559
|
+
if status == :optimal
|
560
|
+
puts "Total packed value: #{objective.value}"
|
561
|
+
total_weight = 0
|
562
|
+
data[:bins].each do |j|
|
563
|
+
bin_weight = 0
|
564
|
+
bin_value = 0
|
565
|
+
puts "Bin #{j}\n\n"
|
566
|
+
data[:items].each do |i|
|
567
|
+
if x[[i, j]].solution_value > 0
|
568
|
+
puts "Item #{i} - weight: #{data[:weights][i]} value: #{data[:values][i]}"
|
569
|
+
bin_weight += data[:weights][i]
|
570
|
+
bin_value += data[:values][i]
|
571
|
+
end
|
572
|
+
end
|
573
|
+
puts "Packed bin weight: #{bin_weight}"
|
574
|
+
puts "Packed bin value: #{bin_value}"
|
575
|
+
puts
|
576
|
+
total_weight += bin_weight
|
577
|
+
end
|
578
|
+
puts "Total packed weight: #{total_weight}"
|
579
|
+
else
|
580
|
+
puts "The problem does not have an optimal solution."
|
581
|
+
end
|
582
|
+
```
|
583
|
+
|
584
|
+
### Bin Packing Problem
|
585
|
+
|
586
|
+
[Guide](https://developers.google.com/optimization/bin/bin_packing)
|
587
|
+
|
588
|
+
Create the data
|
589
|
+
|
590
|
+
```ruby
|
591
|
+
data = {}
|
592
|
+
weights = [48, 30, 19, 36, 36, 27, 42, 42, 36, 24, 30]
|
593
|
+
data[:weights] = weights
|
594
|
+
data[:items] = (0...weights.length).to_a
|
595
|
+
data[:bins] = data[:items]
|
596
|
+
data[:bin_capacity] = 100
|
597
|
+
```
|
598
|
+
|
599
|
+
Declare the solver
|
600
|
+
|
601
|
+
```ruby
|
602
|
+
solver = ORTools::Solver.new("simple_mip_program", :cbc)
|
603
|
+
```
|
604
|
+
|
605
|
+
Create the variables
|
606
|
+
|
607
|
+
```ruby
|
608
|
+
x = {}
|
609
|
+
data[:items].each do |i|
|
610
|
+
data[:bins].each do |j|
|
611
|
+
x[[i, j]] = solver.int_var(0, 1, "x_%i_%i" % [i, j])
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
y = {}
|
616
|
+
data[:bins].each do |j|
|
617
|
+
y[j] = solver.int_var(0, 1, "y[%i]" % j)
|
618
|
+
end
|
619
|
+
```
|
620
|
+
|
621
|
+
Define the constraints
|
622
|
+
|
623
|
+
```ruby
|
624
|
+
data[:items].each do |i|
|
625
|
+
solver.add(solver.sum(data[:bins].map { |j| x[[i, j]] }) == 1)
|
626
|
+
end
|
627
|
+
|
628
|
+
data[:bins].each do |j|
|
629
|
+
sum = solver.sum(data[:items].map { |i| x[[i, j]] * data[:weights][i] })
|
630
|
+
solver.add(sum <= y[j] * data[:bin_capacity])
|
631
|
+
end
|
632
|
+
```
|
633
|
+
|
634
|
+
Define the objective
|
635
|
+
|
636
|
+
```ruby
|
637
|
+
solver.minimize(solver.sum(data[:bins].map { |j| y[j] }))
|
638
|
+
```
|
639
|
+
|
640
|
+
Call the solver and print the solution
|
641
|
+
|
642
|
+
```ruby
|
643
|
+
if status == :optimal
|
644
|
+
num_bins = 0
|
645
|
+
data[:bins].each do |j|
|
646
|
+
if y[j].solution_value == 1
|
647
|
+
bin_items = []
|
648
|
+
bin_weight = 0
|
649
|
+
data[:items].each do |i|
|
650
|
+
if x[[i, j]].solution_value > 0
|
651
|
+
bin_items << i
|
652
|
+
bin_weight += data[:weights][i]
|
653
|
+
end
|
654
|
+
end
|
655
|
+
if bin_weight > 0
|
656
|
+
num_bins += 1
|
657
|
+
puts "Bin number #{j}"
|
658
|
+
puts " Items packed: #{bin_items}"
|
659
|
+
puts " Total weight: #{bin_weight}"
|
660
|
+
puts
|
661
|
+
end
|
662
|
+
end
|
663
|
+
end
|
664
|
+
puts
|
665
|
+
puts "Number of bins used: #{num_bins}"
|
666
|
+
puts "Time = #{solver.wall_time} milliseconds"
|
667
|
+
else
|
668
|
+
puts "The problem does not have an optimal solution."
|
669
|
+
end
|
670
|
+
```
|
671
|
+
|
125
672
|
### Maximum Flows
|
126
673
|
|
127
674
|
[Guide](https://developers.google.com/optimization/flow/maxflow)
|
@@ -218,6 +765,205 @@ else
|
|
218
765
|
end
|
219
766
|
```
|
220
767
|
|
768
|
+
## Assignment
|
769
|
+
|
770
|
+
[Guide](https://developers.google.com/optimization/assignment/simple_assignment)
|
771
|
+
|
772
|
+
Create the data
|
773
|
+
|
774
|
+
```ruby
|
775
|
+
cost = [[ 90, 76, 75, 70],
|
776
|
+
[ 35, 85, 55, 65],
|
777
|
+
[125, 95, 90, 105],
|
778
|
+
[ 45, 110, 95, 115]]
|
779
|
+
|
780
|
+
rows = cost.length
|
781
|
+
cols = cost[0].length
|
782
|
+
```
|
783
|
+
|
784
|
+
Create the solver
|
785
|
+
|
786
|
+
```ruby
|
787
|
+
assignment = ORTools::LinearSumAssignment.new
|
788
|
+
```
|
789
|
+
|
790
|
+
Add the costs to the solver
|
791
|
+
|
792
|
+
```ruby
|
793
|
+
rows.times do |worker|
|
794
|
+
cols.times do |task|
|
795
|
+
if cost[worker][task]
|
796
|
+
assignment.add_arc_with_cost(worker, task, cost[worker][task])
|
797
|
+
end
|
798
|
+
end
|
799
|
+
end
|
800
|
+
```
|
801
|
+
|
802
|
+
Invoke the solver
|
803
|
+
|
804
|
+
```ruby
|
805
|
+
solve_status = assignment.solve
|
806
|
+
if solve_status == :optimal
|
807
|
+
puts "Total cost = #{assignment.optimal_cost}"
|
808
|
+
puts
|
809
|
+
assignment.num_nodes.times do |i|
|
810
|
+
puts "Worker %d assigned to task %d. Cost = %d" % [
|
811
|
+
i,
|
812
|
+
assignment.right_mate(i),
|
813
|
+
assignment.assignment_cost(i)
|
814
|
+
]
|
815
|
+
end
|
816
|
+
elsif solve_status == :infeasible
|
817
|
+
puts "No assignment is possible."
|
818
|
+
elsif solve_status == :possible_overflow
|
819
|
+
puts "Some input costs are too large and may cause an integer overflow."
|
820
|
+
end
|
821
|
+
```
|
822
|
+
|
823
|
+
## Assignment as a Min Cost Problem
|
824
|
+
|
825
|
+
[Guide](https://developers.google.com/optimization/assignment/assignment_min_cost_flow)
|
826
|
+
|
827
|
+
Create the solver
|
828
|
+
|
829
|
+
```ruby
|
830
|
+
min_cost_flow = ORTools::SimpleMinCostFlow.new
|
831
|
+
```
|
832
|
+
|
833
|
+
Create the data
|
834
|
+
|
835
|
+
```ruby
|
836
|
+
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]
|
837
|
+
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]
|
838
|
+
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]
|
839
|
+
costs = [0, 0, 0, 0] + [90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115] + [0, 0, 0, 0]
|
840
|
+
supplies = [4, 0, 0, 0, 0, 0, 0, 0, 0, -4]
|
841
|
+
source = 0
|
842
|
+
sink = 9
|
843
|
+
tasks = 4
|
844
|
+
```
|
845
|
+
|
846
|
+
Create the graph and constraints
|
847
|
+
|
848
|
+
```ruby
|
849
|
+
start_nodes.length.times do |i|
|
850
|
+
min_cost_flow.add_arc_with_capacity_and_unit_cost(
|
851
|
+
start_nodes[i], end_nodes[i], capacities[i], costs[i]
|
852
|
+
)
|
853
|
+
end
|
854
|
+
|
855
|
+
supplies.length.times do |i|
|
856
|
+
min_cost_flow.set_node_supply(i, supplies[i])
|
857
|
+
end
|
858
|
+
```
|
859
|
+
|
860
|
+
Invoke the solver
|
861
|
+
|
862
|
+
```ruby
|
863
|
+
if min_cost_flow.solve == :optimal
|
864
|
+
puts "Total cost = #{min_cost_flow.optimal_cost}"
|
865
|
+
puts
|
866
|
+
min_cost_flow.num_arcs.times do |arc|
|
867
|
+
if min_cost_flow.tail(arc) != source && min_cost_flow.head(arc) != sink
|
868
|
+
if min_cost_flow.flow(arc) > 0
|
869
|
+
puts "Worker %d assigned to task %d. Cost = %d" % [
|
870
|
+
min_cost_flow.tail(arc),
|
871
|
+
min_cost_flow.head(arc),
|
872
|
+
min_cost_flow.unit_cost(arc)
|
873
|
+
]
|
874
|
+
end
|
875
|
+
end
|
876
|
+
end
|
877
|
+
else
|
878
|
+
puts "There was an issue with the min cost flow input."
|
879
|
+
end
|
880
|
+
```
|
881
|
+
|
882
|
+
## Assignment as a MIP Problem
|
883
|
+
|
884
|
+
[Guide](https://developers.google.com/optimization/assignment/assignment_mip)
|
885
|
+
|
886
|
+
Create the solver
|
887
|
+
|
888
|
+
```ruby
|
889
|
+
solver = ORTools::Solver.new("SolveAssignmentProblemMIP", :cbc)
|
890
|
+
```
|
891
|
+
|
892
|
+
Create the data
|
893
|
+
|
894
|
+
```ruby
|
895
|
+
cost = [[90, 76, 75, 70],
|
896
|
+
[35, 85, 55, 65],
|
897
|
+
[125, 95, 90, 105],
|
898
|
+
[45, 110, 95, 115],
|
899
|
+
[60, 105, 80, 75],
|
900
|
+
[45, 65, 110, 95]]
|
901
|
+
|
902
|
+
team1 = [0, 2, 4]
|
903
|
+
team2 = [1, 3, 5]
|
904
|
+
team_max = 2
|
905
|
+
```
|
906
|
+
|
907
|
+
Create the variables
|
908
|
+
|
909
|
+
```ruby
|
910
|
+
num_workers = cost.length
|
911
|
+
num_tasks = cost[1].length
|
912
|
+
x = {}
|
913
|
+
|
914
|
+
num_workers.times do |i|
|
915
|
+
num_tasks.times do |j|
|
916
|
+
x[[i, j]] = solver.bool_var("x[#{i},#{j}]")
|
917
|
+
end
|
918
|
+
end
|
919
|
+
```
|
920
|
+
|
921
|
+
Create the objective function
|
922
|
+
|
923
|
+
```ruby
|
924
|
+
solver.minimize(solver.sum(
|
925
|
+
num_workers.times.flat_map { |i| num_tasks.times.map { |j| x[[i, j]] * cost[i][j] } }
|
926
|
+
))
|
927
|
+
```
|
928
|
+
|
929
|
+
Create the constraints
|
930
|
+
|
931
|
+
```ruby
|
932
|
+
num_workers.times do |i|
|
933
|
+
solver.add(solver.sum(num_tasks.times.map { |j| x[[i, j]] }) <= 1)
|
934
|
+
end
|
935
|
+
|
936
|
+
num_tasks.times do |j|
|
937
|
+
solver.add(solver.sum(num_workers.times.map { |i| x[[i, j]] }) == 1)
|
938
|
+
end
|
939
|
+
|
940
|
+
solver.add(solver.sum(team1.flat_map { |i| num_tasks.times.map { |j| x[[i, j]] } }) <= team_max)
|
941
|
+
solver.add(solver.sum(team2.flat_map { |i| num_tasks.times.map { |j| x[[i, j]] } }) <= team_max)
|
942
|
+
```
|
943
|
+
|
944
|
+
Invoke the solver
|
945
|
+
|
946
|
+
```ruby
|
947
|
+
sol = solver.solve
|
948
|
+
|
949
|
+
puts "Total cost = #{solver.objective.value}"
|
950
|
+
puts
|
951
|
+
num_workers.times do |i|
|
952
|
+
num_tasks.times do |j|
|
953
|
+
if x[[i, j]].solution_value > 0
|
954
|
+
puts "Worker %d assigned to task %d. Cost = %d" % [
|
955
|
+
i,
|
956
|
+
j,
|
957
|
+
cost[i][j]
|
958
|
+
]
|
959
|
+
end
|
960
|
+
end
|
961
|
+
end
|
962
|
+
|
963
|
+
puts
|
964
|
+
puts "Time = #{solver.wall_time} milliseconds"
|
965
|
+
```
|
966
|
+
|
221
967
|
## History
|
222
968
|
|
223
969
|
View the [changelog](https://github.com/ankane/or-tools/blob/master/CHANGELOG.md)
|