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.
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # OR-Tools
2
2
 
3
- [OR-Tools](https://developers.google.com/optimization/) - operations research tools - for Ruby
3
+ [OR-Tools](https://github.com/google/or-tools) - operations research tools - for Ruby
4
+
5
+ [![Build Status](https://travis-ci.org/ankane/or-tools.svg?branch=master)](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.add_not_equal(x, y)
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)