or-tools 0.2.0 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4377b9acdd93be666c700e05fb56cf01e59f088cf149f345ab888ef018c82d04
4
- data.tar.gz: f32d95eb857cf479db372f4c0c27b18590bb9137aa72a9b6e478789830b7ea68
3
+ metadata.gz: dba74899e2a57b086643657e5093f12e67fd4fcdda7e22c2b533911f9991e43a
4
+ data.tar.gz: 963c0b75f55b4b772ead3a8402ec8bb4b90570c23de1c7dc3a437758e8949af2
5
5
  SHA512:
6
- metadata.gz: ab952c566d3f84c57cce0b843c27ad6e3fa9c06f4e85b273af2c82564403cd6bb88e8f8ae1d39c21f5732e4d3415729b8d4bd87418a34f0d02c588d816ab9d49
7
- data.tar.gz: 95a03e432680341c029e21ae2148385c1628a7df127ea8707991a47d087f37cd5ebb4288518f16b1cfd7c84c03a254c0cf8c26c5c8d574b949c3ed3901ca9cfe
6
+ metadata.gz: a5dc8662ec580c97a905817867346615e2fbb187d5b75d6b2b839a86e7385a20b2ed4f9f4c13ed536079c6141cdd0bf95bfad7ea6408d4eb81d8f0ed09e32a68
7
+ data.tar.gz: 195967d1e662ba05751ff31fbec3c96ebac519cd58a343ca551f9ba6c12304542b7f4127a37f88e99cda6b214d42089df7ed9b5a3134c0ec1eb978ea72a95be4
@@ -1,3 +1,29 @@
1
+ ## 0.3.4 (2021-01-14)
2
+
3
+ - Added support for time limit for `CpSolver`
4
+ - Added `add_dimension_with_vehicle_transits` and `status` methods to `RoutingModel`
5
+
6
+ ## 0.3.3 (2020-10-12)
7
+
8
+ - Added support for start and end points for routing
9
+
10
+ ## 0.3.2 (2020-08-04)
11
+
12
+ - Updated OR-Tools to 7.8
13
+ - Added binary installation for Ubuntu 20.04
14
+
15
+ ## 0.3.1 (2020-07-21)
16
+
17
+ - Reduced gem size
18
+
19
+ ## 0.3.0 (2020-07-21)
20
+
21
+ - Updated OR-Tools to 7.7
22
+ - Added `BasicScheduler` class
23
+ - Added `Seating` class
24
+ - Added `TSP` class
25
+ - Added `Sudoku` class
26
+
1
27
  ## 0.2.0 (2020-05-22)
2
28
 
3
29
  - No longer need to download the OR-Tools C++ library separately on Mac, Ubuntu 18.04, Ubuntu 16.04, Debian 10, and CentOS 8
data/NOTICE.txt CHANGED
@@ -1,4 +1,5 @@
1
- Copyright 2020 Andrew Kane
1
+ Copyright 2010-2018 Google LLC
2
+ Copyright 2020-2021 Andrew Kane
2
3
 
3
4
  Licensed under the Apache License, Version 2.0 (the "License");
4
5
  you may not use this file except in compliance with the License.
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [OR-Tools](https://github.com/google/or-tools) - operations research tools - for Ruby
4
4
 
5
- [![Build Status](https://travis-ci.org/ankane/or-tools.svg?branch=master)](https://travis-ci.org/ankane/or-tools)
5
+ [![Build Status](https://github.com/ankane/or-tools/workflows/build/badge.svg?branch=master)](https://github.com/ankane/or-tools/actions)
6
6
 
7
7
  ## Installation
8
8
 
@@ -14,26 +14,269 @@ gem 'or-tools'
14
14
 
15
15
  Installation can take a few minutes as OR-Tools downloads and builds.
16
16
 
17
- ## Getting Started
17
+ ## Higher Level Interfaces
18
+
19
+ - [Scheduling](#scheduling)
20
+ - [Seating](#seating)
21
+ - [Traveling Salesperson Problem (TSP)](#traveling-salesperson-problem-tsp)
22
+ - [Sudoku](#sudoku)
23
+
24
+ ### Scheduling
25
+
26
+ Specify people and their availabililty
27
+
28
+ ```ruby
29
+ people = [
30
+ {
31
+ availability: [
32
+ {starts_at: Time.parse("2020-01-01 08:00:00"), ends_at: Time.parse("2020-01-01 16:00:00")},
33
+ {starts_at: Time.parse("2020-01-02 08:00:00"), ends_at: Time.parse("2020-01-02 16:00:00")}
34
+ ],
35
+ max_hours: 40 # optional, applies to entire scheduling period
36
+ },
37
+ {
38
+ availability: [
39
+ {starts_at: Time.parse("2020-01-01 08:00:00"), ends_at: Time.parse("2020-01-01 16:00:00")},
40
+ {starts_at: Time.parse("2020-01-03 08:00:00"), ends_at: Time.parse("2020-01-03 16:00:00")}
41
+ ],
42
+ max_hours: 20
43
+ }
44
+ ]
45
+ ```
46
+
47
+ Specify shifts
48
+
49
+ ```ruby
50
+ shifts = [
51
+ {starts_at: Time.parse("2020-01-01 08:00:00"), ends_at: Time.parse("2020-01-01 16:00:00")},
52
+ {starts_at: Time.parse("2020-01-02 08:00:00"), ends_at: Time.parse("2020-01-02 16:00:00")},
53
+ {starts_at: Time.parse("2020-01-03 08:00:00"), ends_at: Time.parse("2020-01-03 16:00:00")}
54
+ ]
55
+ ```
56
+
57
+ Run the scheduler
58
+
59
+ ```ruby
60
+ scheduler = ORTools::BasicScheduler.new(people: people, shifts: shifts)
61
+ ```
62
+
63
+ The scheduler maximizes the number of assigned hours. A person must be available for the entire shift to be considered for it.
64
+
65
+ Get assignments (returns indexes of people and shifts)
66
+
67
+ ```ruby
68
+ scheduler.assignments
69
+ # [
70
+ # {person: 2, shift: 0},
71
+ # {person: 0, shift: 1},
72
+ # {person: 1, shift: 2}
73
+ # ]
74
+ ```
75
+
76
+ Get assigned hours and total hours
77
+
78
+ ```ruby
79
+ scheduler.assigned_hours
80
+ scheduler.total_hours
81
+ ```
82
+
83
+ Feel free to create an issue if you have a scheduling use case that’s not covered.
84
+
85
+ ### Seating
86
+
87
+ Create a seating chart based on personal connections. Uses [this approach](https://www.improbable.com/news/2012/Optimal-seating-chart.pdf).
88
+
89
+ Specify connections
90
+
91
+ ```ruby
92
+ connections = [
93
+ {people: ["A", "B", "C"], weight: 2},
94
+ {people: ["C", "D", "E", "F"], weight: 1}
95
+ ]
96
+ ```
97
+
98
+ Use different weights to prioritize seating. For a wedding, it may look like:
99
+
100
+ ```ruby
101
+ connections = [
102
+ {people: knows_partner1, weight: 1},
103
+ {people: knows_partner2, weight: 1},
104
+ {people: relationship1, weight: 100},
105
+ {people: relationship2, weight: 100},
106
+ {people: relationship3, weight: 100},
107
+ {people: friend_group1, weight: 10},
108
+ {people: friend_group2, weight: 10},
109
+ # ...
110
+ ]
111
+ ```
112
+
113
+ If two people have multiple connections, weights are added.
114
+
115
+ Specify tables and their capacity
116
+
117
+ ```ruby
118
+ tables = [3, 3]
119
+ ```
120
+
121
+ Assign seats
122
+
123
+ ```ruby
124
+ seating = ORTools::Seating.new(connections: connections, tables: tables)
125
+ ```
126
+
127
+ Each person will have a connection with at least one other person at their table.
128
+
129
+ Get tables
130
+
131
+ ```ruby
132
+ seating.assigned_tables
133
+ ```
134
+
135
+ Get assignments by person
136
+
137
+ ```ruby
138
+ seating.assignments
139
+ ```
140
+
141
+ Get all connections for a person
142
+
143
+ ```ruby
144
+ seating.connections_for(person)
145
+ ```
146
+
147
+ Get connections for a person at their table
148
+
149
+ ```ruby
150
+ seating.connections_for(person, same_table: true)
151
+ ```
152
+
153
+ ### Traveling Salesperson Problem (TSP)
154
+
155
+ Create locations - the first location will be the starting and ending point
156
+
157
+ ```ruby
158
+ locations = [
159
+ {name: "Tokyo", latitude: 35.6762, longitude: 139.6503},
160
+ {name: "Delhi", latitude: 28.7041, longitude: 77.1025},
161
+ {name: "Shanghai", latitude: 31.2304, longitude: 121.4737},
162
+ {name: "São Paulo", latitude: -23.5505, longitude: -46.6333},
163
+ {name: "Mexico City", latitude: 19.4326, longitude: -99.1332},
164
+ {name: "Cairo", latitude: 30.0444, longitude: 31.2357},
165
+ {name: "Mumbai", latitude: 19.0760, longitude: 72.8777},
166
+ {name: "Beijing", latitude: 39.9042, longitude: 116.4074},
167
+ {name: "Dhaka", latitude: 23.8103, longitude: 90.4125},
168
+ {name: "Osaka", latitude: 34.6937, longitude: 135.5023},
169
+ {name: "New York City", latitude: 40.7128, longitude: -74.0060},
170
+ {name: "Karachi", latitude: 24.8607, longitude: 67.0011},
171
+ {name: "Buenos Aires", latitude: -34.6037, longitude: -58.3816}
172
+ ]
173
+ ```
174
+
175
+ Locations can have any fields - only `latitude` and `longitude` are required
176
+
177
+ Get route
178
+
179
+ ```ruby
180
+ tsp = ORTools::TSP.new(locations)
181
+ tsp.route # [{name: "Tokyo", ...}, {name: "Osaka", ...}, ...]
182
+ ```
183
+
184
+ Get distances between locations on route
185
+
186
+ ```ruby
187
+ tsp.distances # [392.441, 1362.926, 1067.31, ...]
188
+ ```
189
+
190
+ Distances are in kilometers - multiply by `0.6214` for miles
191
+
192
+ Get total distance
193
+
194
+ ```ruby
195
+ tsp.total_distance
196
+ ```
197
+
198
+ ### Sudoku
199
+
200
+ Create a puzzle with zeros in empty cells
201
+
202
+ ```ruby
203
+ grid = [
204
+ [0, 6, 0, 0, 5, 0, 0, 2, 0],
205
+ [0, 0, 0, 3, 0, 0, 0, 9, 0],
206
+ [7, 0, 0, 6, 0, 0, 0, 1, 0],
207
+ [0, 0, 6, 0, 3, 0, 4, 0, 0],
208
+ [0, 0, 4, 0, 7, 0, 1, 0, 0],
209
+ [0, 0, 5, 0, 9, 0, 8, 0, 0],
210
+ [0, 4, 0, 0, 0, 1, 0, 0, 6],
211
+ [0, 3, 0, 0, 0, 8, 0, 0, 0],
212
+ [0, 2, 0, 0, 4, 0, 0, 5, 0]
213
+ ]
214
+ sudoku = ORTools::Sudoku.new(grid)
215
+ sudoku.solution
216
+ ```
217
+
218
+ It can also solve more advanced puzzles like [The Miracle](https://www.youtube.com/watch?v=yKf9aUIxdb4)
219
+
220
+ ```ruby
221
+ grid = [
222
+ [0, 0, 0, 0, 0, 0, 0, 0, 0],
223
+ [0, 0, 0, 0, 0, 0, 0, 0, 0],
224
+ [0, 0, 0, 0, 0, 0, 0, 0, 0],
225
+ [0, 0, 0, 0, 0, 0, 0, 0, 0],
226
+ [0, 0, 1, 0, 0, 0, 0, 0, 0],
227
+ [0, 0, 0, 0, 0, 0, 2, 0, 0],
228
+ [0, 0, 0, 0, 0, 0, 0, 0, 0],
229
+ [0, 0, 0, 0, 0, 0, 0, 0, 0],
230
+ [0, 0, 0, 0, 0, 0, 0, 0, 0]
231
+ ]
232
+ sudoku = ORTools::Sudoku.new(grid, anti_knight: true, anti_king: true, non_consecutive: true)
233
+ sudoku.solution
234
+ ```
235
+
236
+ And [this 4-digit puzzle](https://www.youtube.com/watch?v=hAyZ9K2EBF0)
237
+
238
+ ```ruby
239
+ grid = [
240
+ [0, 0, 0, 0, 0, 0, 0, 0, 0],
241
+ [0, 0, 0, 0, 0, 0, 0, 0, 0],
242
+ [0, 0, 0, 0, 0, 0, 0, 0, 0],
243
+ [3, 8, 4, 0, 0, 0, 0, 0, 0],
244
+ [0, 0, 0, 0, 0, 0, 0, 0, 0],
245
+ [0, 0, 0, 0, 0, 0, 0, 0, 0],
246
+ [0, 0, 0, 0, 0, 0, 0, 0, 0],
247
+ [0, 0, 0, 0, 0, 0, 0, 0, 0],
248
+ [0, 0, 0, 0, 0, 0, 0, 0, 2]
249
+ ]
250
+ sudoku = ORTools::Sudoku.new(grid, x: true, anti_knight: true, magic_square: true)
251
+ sudoku.solution
252
+ ```
253
+
254
+ ## Guides
18
255
 
19
256
  Linear Optimization
20
257
 
21
258
  - [The Glop Linear Solver](#the-glop-linear-solver)
22
259
 
260
+ Integer Optimization
261
+
262
+ - [Mixed-Integer Programming](#mixed-integer-programming)
263
+
23
264
  Constraint Optimization
24
265
 
25
266
  - [CP-SAT Solver](#cp-sat-solver)
26
267
  - [Solving an Optimization Problem](#solving-an-optimization-problem)
27
268
  - [Cryptarithmetic](#cryptarithmetic)
28
269
  - [The N-queens Problem](#the-n-queens-problem)
270
+ - [Setting Solver Limits](#setting-solver-limits) [master]
29
271
 
30
- Integer Optimization
272
+ Assignment
31
273
 
32
- - [Mixed-Integer Programming](#mixed-integer-programming)
274
+ - [Assignment](#assignment)
275
+ - [Assignment with Teams](#assignment-with-teams)
33
276
 
34
277
  Routing
35
278
 
36
- - [Traveling Salesperson Problem (TSP)](#traveling-salesperson-problem-tsp)
279
+ - [Traveling Salesperson Problem (TSP)](#traveling-salesperson-problem-tsp-1)
37
280
  - [Vehicle Routing Problem (VRP)](#vehicle-routing-problem-vrp)
38
281
  - [Capacity Constraints](#capacity-constraints)
39
282
  - [Pickups and Deliveries](#pickups-and-deliveries)
@@ -52,12 +295,7 @@ Network Flows
52
295
 
53
296
  - [Maximum Flows](#maximum-flows)
54
297
  - [Minimum Cost Flows](#minimum-cost-flows)
55
-
56
- Assignment
57
-
58
- - [Assignment](#assignment)
59
- - [Assignment as a Min Cost Problem](#assignment-as-a-min-cost-problem)
60
- - [Assignment as a MIP Problem](#assignment-as-a-mip-problem)
298
+ - [Assignment as a Min Cost Flow Problem](#assignment-as-a-min-cost-flow-problem)
61
299
 
62
300
  Scheduling
63
301
 
@@ -66,7 +304,7 @@ Scheduling
66
304
 
67
305
  Other Examples
68
306
 
69
- - [Sudoku](#sudoku)
307
+ - [Sudoku](#sudoku-1)
70
308
  - [Wedding Seating Chart](#wedding-seating-chart)
71
309
  - [Set Partitioning](#set-partitioning)
72
310
 
@@ -114,6 +352,52 @@ puts "y = #{y.solution_value}"
114
352
  puts "Optimal objective value = #{opt_solution}"
115
353
  ```
116
354
 
355
+ ### Mixed-Integer Programming
356
+
357
+ [Guide](https://developers.google.com/optimization/mip/integer_opt)
358
+
359
+ ```ruby
360
+ # declare the MIP solver
361
+ solver = ORTools::Solver.new("simple_mip_program", :cbc)
362
+
363
+ # define the variables
364
+ infinity = solver.infinity
365
+ x = solver.int_var(0.0, infinity, "x")
366
+ y = solver.int_var(0.0, infinity, "y")
367
+
368
+ puts "Number of variables = #{solver.num_variables}"
369
+
370
+ # define the constraints
371
+ c0 = solver.constraint(-infinity, 17.5)
372
+ c0.set_coefficient(x, 1)
373
+ c0.set_coefficient(y, 7)
374
+
375
+ c1 = solver.constraint(-infinity, 3.5)
376
+ c1.set_coefficient(x, 1);
377
+ c1.set_coefficient(y, 0);
378
+
379
+ puts "Number of constraints = #{solver.num_constraints}"
380
+
381
+ # define the objective
382
+ objective = solver.objective
383
+ objective.set_coefficient(x, 1)
384
+ objective.set_coefficient(y, 10)
385
+ objective.set_maximization
386
+
387
+ # call the solver
388
+ status = solver.solve
389
+
390
+ # display the solution
391
+ if status == :optimal
392
+ puts "Solution:"
393
+ puts "Objective value = #{solver.objective.value}"
394
+ puts "x = #{x.solution_value}"
395
+ puts "y = #{y.solution_value}"
396
+ else
397
+ puts "The problem does not have an optimal solution."
398
+ end
399
+ ```
400
+
117
401
  ### CP-SAT Solver
118
402
 
119
403
  [Guide](https://developers.google.com/optimization/cp/cp_solver)
@@ -136,7 +420,7 @@ solver = ORTools::CpSolver.new
136
420
  status = solver.solve(model)
137
421
 
138
422
  # display the first solution
139
- if status == :feasible
423
+ if status == :optimal
140
424
  puts "x = #{solver.value(x)}"
141
425
  puts "y = #{solver.value(y)}"
142
426
  puts "z = #{solver.value(z)}"
@@ -295,50 +579,152 @@ puts
295
579
  puts "Solutions found : %i" % solution_printer.solution_count
296
580
  ```
297
581
 
298
- ### Mixed-Integer Programming
582
+ ### Setting Solver Limits
299
583
 
300
- [Guide](https://developers.google.com/optimization/mip/integer_opt)
584
+ [Guide](https://developers.google.com/optimization/cp/cp_tasks)
301
585
 
302
586
  ```ruby
303
- # declare the MIP solver
304
- solver = ORTools::Solver.new("simple_mip_program", :cbc)
587
+ # create the model
588
+ model = ORTools::CpModel.new
305
589
 
306
- # define the variables
307
- infinity = solver.infinity
308
- x = solver.int_var(0.0, infinity, "x")
309
- y = solver.int_var(0.0, infinity, "y")
590
+ # create the variables
591
+ num_vals = 3
592
+ x = model.new_int_var(0, num_vals - 1, "x")
593
+ y = model.new_int_var(0, num_vals - 1, "y")
594
+ z = model.new_int_var(0, num_vals - 1, "z")
310
595
 
311
- puts "Number of variables = #{solver.num_variables}"
596
+ # add an all-different constraint
597
+ model.add(x != y)
312
598
 
313
- # define the constraints
314
- c0 = solver.constraint(-infinity, 17.5)
315
- c0.set_coefficient(x, 1)
316
- c0.set_coefficient(y, 7)
599
+ # create the solver
600
+ solver = ORTools::CpSolver.new
317
601
 
318
- c1 = solver.constraint(-infinity, 3.5)
319
- c1.set_coefficient(x, 1);
320
- c1.set_coefficient(y, 0);
602
+ # set a time limit of 10 seconds.
603
+ solver.parameters.max_time_in_seconds = 10
321
604
 
322
- puts "Number of constraints = #{solver.num_constraints}"
605
+ # solve the model
606
+ status = solver.solve(model)
323
607
 
324
- # define the objective
325
- objective = solver.objective
326
- objective.set_coefficient(x, 1)
327
- objective.set_coefficient(y, 10)
328
- objective.set_maximization
608
+ # display the first solution
609
+ if status == :optimal
610
+ puts "x = #{solver.value(x)}"
611
+ puts "y = #{solver.value(y)}"
612
+ puts "z = #{solver.value(z)}"
613
+ end
614
+ ```
329
615
 
330
- # call the solver
331
- status = solver.solve
616
+ ### Assignment
332
617
 
333
- # display the solution
334
- if status == :optimal
335
- puts "Solution:"
336
- puts "Objective value = #{solver.objective.value}"
337
- puts "x = #{x.solution_value}"
338
- puts "y = #{y.solution_value}"
339
- else
340
- puts "The problem does not have an optimal solution."
618
+ [Guide](https://developers.google.com/optimization/assignment/assignment_example)
619
+
620
+ ```ruby
621
+ # create the data
622
+ cost = [[ 90, 76, 75, 70],
623
+ [ 35, 85, 55, 65],
624
+ [125, 95, 90, 105],
625
+ [ 45, 110, 95, 115]]
626
+
627
+ rows = cost.length
628
+ cols = cost[0].length
629
+
630
+ # create the solver
631
+ assignment = ORTools::LinearSumAssignment.new
632
+
633
+ # add the costs to the solver
634
+ rows.times do |worker|
635
+ cols.times do |task|
636
+ if cost[worker][task]
637
+ assignment.add_arc_with_cost(worker, task, cost[worker][task])
638
+ end
639
+ end
640
+ end
641
+
642
+ # invoke the solver
643
+ solve_status = assignment.solve
644
+ if solve_status == :optimal
645
+ puts "Total cost = #{assignment.optimal_cost}"
646
+ puts
647
+ assignment.num_nodes.times do |i|
648
+ puts "Worker %d assigned to task %d. Cost = %d" % [
649
+ i,
650
+ assignment.right_mate(i),
651
+ assignment.assignment_cost(i)
652
+ ]
653
+ end
654
+ elsif solve_status == :infeasible
655
+ puts "No assignment is possible."
656
+ elsif solve_status == :possible_overflow
657
+ puts "Some input costs are too large and may cause an integer overflow."
658
+ end
659
+ ```
660
+
661
+ ### Assignment with Teams
662
+
663
+ [Guide](https://developers.google.com/optimization/assignment/assignment_teams)
664
+
665
+ ```ruby
666
+ # create the solver
667
+ solver = ORTools::Solver.new("SolveAssignmentProblemMIP", :cbc)
668
+
669
+ # create the data
670
+ cost = [[90, 76, 75, 70],
671
+ [35, 85, 55, 65],
672
+ [125, 95, 90, 105],
673
+ [45, 110, 95, 115],
674
+ [60, 105, 80, 75],
675
+ [45, 65, 110, 95]]
676
+
677
+ team1 = [0, 2, 4]
678
+ team2 = [1, 3, 5]
679
+ team_max = 2
680
+
681
+ # create the variables
682
+ num_workers = cost.length
683
+ num_tasks = cost[1].length
684
+ x = {}
685
+
686
+ num_workers.times do |i|
687
+ num_tasks.times do |j|
688
+ x[[i, j]] = solver.bool_var("x[#{i},#{j}]")
689
+ end
341
690
  end
691
+
692
+ # create the objective function
693
+ solver.minimize(solver.sum(
694
+ num_workers.times.flat_map { |i| num_tasks.times.map { |j| x[[i, j]] * cost[i][j] } }
695
+ ))
696
+
697
+ # create the constraints
698
+ num_workers.times do |i|
699
+ solver.add(solver.sum(num_tasks.times.map { |j| x[[i, j]] }) <= 1)
700
+ end
701
+
702
+ num_tasks.times do |j|
703
+ solver.add(solver.sum(num_workers.times.map { |i| x[[i, j]] }) == 1)
704
+ end
705
+
706
+ solver.add(solver.sum(team1.flat_map { |i| num_tasks.times.map { |j| x[[i, j]] } }) <= team_max)
707
+ solver.add(solver.sum(team2.flat_map { |i| num_tasks.times.map { |j| x[[i, j]] } }) <= team_max)
708
+
709
+ # invoke the solver
710
+ sol = solver.solve
711
+
712
+ puts "Total cost = #{solver.objective.value}"
713
+ puts
714
+ num_workers.times do |i|
715
+ num_tasks.times do |j|
716
+ if x[[i, j]].solution_value > 0
717
+ puts "Worker %d assigned to task %d. Cost = %d" % [
718
+ i,
719
+ j,
720
+ cost[i][j]
721
+ ]
722
+ end
723
+ end
724
+ end
725
+
726
+ puts
727
+ puts "Time = #{solver.wall_time} milliseconds"
342
728
  ```
343
729
 
344
730
  ### Traveling Salesperson Problem (TSP)
@@ -380,7 +766,7 @@ transit_callback_index = routing.register_transit_callback(distance_callback)
380
766
  routing.set_arc_cost_evaluator_of_all_vehicles(transit_callback_index)
381
767
 
382
768
  # run the solver
383
- assignment = routing.solve(first_solution_strategy: :path_cheaper_arc)
769
+ assignment = routing.solve(first_solution_strategy: :path_cheapest_arc)
384
770
 
385
771
  # print the solution
386
772
  puts "Objective: #{assignment.objective_value} miles"
@@ -1276,52 +1662,7 @@ else
1276
1662
  end
1277
1663
  ```
1278
1664
 
1279
- ### Assignment
1280
-
1281
- [Guide](https://developers.google.com/optimization/assignment/simple_assignment)
1282
-
1283
- ```ruby
1284
- # create the data
1285
- cost = [[ 90, 76, 75, 70],
1286
- [ 35, 85, 55, 65],
1287
- [125, 95, 90, 105],
1288
- [ 45, 110, 95, 115]]
1289
-
1290
- rows = cost.length
1291
- cols = cost[0].length
1292
-
1293
- # create the solver
1294
- assignment = ORTools::LinearSumAssignment.new
1295
-
1296
- # add the costs to the solver
1297
- rows.times do |worker|
1298
- cols.times do |task|
1299
- if cost[worker][task]
1300
- assignment.add_arc_with_cost(worker, task, cost[worker][task])
1301
- end
1302
- end
1303
- end
1304
-
1305
- # invoke the solver
1306
- solve_status = assignment.solve
1307
- if solve_status == :optimal
1308
- puts "Total cost = #{assignment.optimal_cost}"
1309
- puts
1310
- assignment.num_nodes.times do |i|
1311
- puts "Worker %d assigned to task %d. Cost = %d" % [
1312
- i,
1313
- assignment.right_mate(i),
1314
- assignment.assignment_cost(i)
1315
- ]
1316
- end
1317
- elsif solve_status == :infeasible
1318
- puts "No assignment is possible."
1319
- elsif solve_status == :possible_overflow
1320
- puts "Some input costs are too large and may cause an integer overflow."
1321
- end
1322
- ```
1323
-
1324
- ### Assignment as a Min Cost Problem
1665
+ ### Assignment as a Min Cost Flow Problem
1325
1666
 
1326
1667
  [Guide](https://developers.google.com/optimization/assignment/assignment_min_cost_flow)
1327
1668
 
@@ -1370,75 +1711,6 @@ else
1370
1711
  end
1371
1712
  ```
1372
1713
 
1373
- ### Assignment as a MIP Problem
1374
-
1375
- [Guide](https://developers.google.com/optimization/assignment/assignment_mip)
1376
-
1377
- ```ruby
1378
- # create the solver
1379
- solver = ORTools::Solver.new("SolveAssignmentProblemMIP", :cbc)
1380
-
1381
- # create the data
1382
- cost = [[90, 76, 75, 70],
1383
- [35, 85, 55, 65],
1384
- [125, 95, 90, 105],
1385
- [45, 110, 95, 115],
1386
- [60, 105, 80, 75],
1387
- [45, 65, 110, 95]]
1388
-
1389
- team1 = [0, 2, 4]
1390
- team2 = [1, 3, 5]
1391
- team_max = 2
1392
-
1393
- # create the variables
1394
- num_workers = cost.length
1395
- num_tasks = cost[1].length
1396
- x = {}
1397
-
1398
- num_workers.times do |i|
1399
- num_tasks.times do |j|
1400
- x[[i, j]] = solver.bool_var("x[#{i},#{j}]")
1401
- end
1402
- end
1403
-
1404
- # create the objective function
1405
- solver.minimize(solver.sum(
1406
- num_workers.times.flat_map { |i| num_tasks.times.map { |j| x[[i, j]] * cost[i][j] } }
1407
- ))
1408
-
1409
- # create the constraints
1410
- num_workers.times do |i|
1411
- solver.add(solver.sum(num_tasks.times.map { |j| x[[i, j]] }) <= 1)
1412
- end
1413
-
1414
- num_tasks.times do |j|
1415
- solver.add(solver.sum(num_workers.times.map { |i| x[[i, j]] }) == 1)
1416
- end
1417
-
1418
- solver.add(solver.sum(team1.flat_map { |i| num_tasks.times.map { |j| x[[i, j]] } }) <= team_max)
1419
- solver.add(solver.sum(team2.flat_map { |i| num_tasks.times.map { |j| x[[i, j]] } }) <= team_max)
1420
-
1421
- # invoke the solver
1422
- sol = solver.solve
1423
-
1424
- puts "Total cost = #{solver.objective.value}"
1425
- puts
1426
- num_workers.times do |i|
1427
- num_tasks.times do |j|
1428
- if x[[i, j]].solution_value > 0
1429
- puts "Worker %d assigned to task %d. Cost = %d" % [
1430
- i,
1431
- j,
1432
- cost[i][j]
1433
- ]
1434
- end
1435
- end
1436
- end
1437
-
1438
- puts
1439
- puts "Time = #{solver.wall_time} milliseconds"
1440
- ```
1441
-
1442
1714
  ### Employee Scheduling
1443
1715
 
1444
1716
  [Guide](https://developers.google.com/optimization/scheduling/employee_scheduling)
@@ -1710,7 +1982,7 @@ end
1710
1982
  # solve and print solution
1711
1983
  solver = ORTools::CpSolver.new
1712
1984
  status = solver.solve(model)
1713
- if status == :feasible
1985
+ if status == :optimal
1714
1986
  line.each do |i|
1715
1987
  p line.map { |j| solver.value(grid[[i, j]]) }
1716
1988
  end
@@ -1725,8 +1997,8 @@ end
1725
1997
  # From
1726
1998
  # Meghan L. Bellows and J. D. Luc Peterson
1727
1999
  # "Finding an optimal seating chart for a wedding"
1728
- # http://www.improbable.com/news/2012/Optimal-seating-chart.pdf
1729
- # http://www.improbable.com/2012/02/12/finding-an-optimal-seating-chart-for-a-wedding
2000
+ # https://www.improbable.com/news/2012/Optimal-seating-chart.pdf
2001
+ # https://www.improbable.com/2012/02/12/finding-an-optimal-seating-chart-for-a-wedding
1730
2002
  #
1731
2003
  # Every year, millions of brides (not to mention their mothers, future
1732
2004
  # mothers-in-law, and occasionally grooms) struggle with one of the
@@ -1799,19 +2071,17 @@ all_tables.each do |t|
1799
2071
  end
1800
2072
  end
1801
2073
 
2074
+ pairs = all_guests.combination(2)
2075
+
1802
2076
  colocated = {}
1803
- (num_guests - 1).times do |g1|
1804
- (g1 + 1).upto(num_guests - 1) do |g2|
1805
- colocated[[g1, g2]] = model.new_bool_var("guest %i seats with guest %i" % [g1, g2])
1806
- end
2077
+ pairs.each do |g1, g2|
2078
+ colocated[[g1, g2]] = model.new_bool_var("guest %i seats with guest %i" % [g1, g2])
1807
2079
  end
1808
2080
 
1809
2081
  same_table = {}
1810
- (num_guests - 1).times do |g1|
1811
- (g1 + 1).upto(num_guests - 1) do |g2|
1812
- all_tables.each do |t|
1813
- same_table[[g1, g2, t]] = model.new_bool_var("guest %i seats with guest %i on table %i" % [g1, g2, t])
1814
- end
2082
+ pairs.each do |g1, g2|
2083
+ all_tables.each do |t|
2084
+ same_table[[g1, g2, t]] = model.new_bool_var("guest %i seats with guest %i on table %i" % [g1, g2, t])
1815
2085
  end
1816
2086
  end
1817
2087
 
@@ -1833,29 +2103,32 @@ all_tables.each do |t|
1833
2103
  end
1834
2104
 
1835
2105
  # Link colocated with seats
1836
- (num_guests - 1).times do |g1|
1837
- (g1 + 1).upto(num_guests - 1) do |g2|
1838
- all_tables.each do |t|
1839
- # Link same_table and seats.
1840
- model.add_bool_or([seats[[t, g1]].not, seats[[t, g2]].not, same_table[[g1, g2, t]]])
1841
- model.add_implication(same_table[[g1, g2, t]], seats[[t, g1]])
1842
- model.add_implication(same_table[[g1, g2, t]], seats[[t, g2]])
1843
- end
1844
-
1845
- # Link colocated and same_table.
1846
- model.add(model.sum(all_tables.map { |t| same_table[[g1, g2, t]] }) == colocated[[g1, g2]])
2106
+ pairs.each do |g1, g2|
2107
+ all_tables.each do |t|
2108
+ # Link same_table and seats.
2109
+ model.add_bool_or([seats[[t, g1]].not, seats[[t, g2]].not, same_table[[g1, g2, t]]])
2110
+ model.add_implication(same_table[[g1, g2, t]], seats[[t, g1]])
2111
+ model.add_implication(same_table[[g1, g2, t]], seats[[t, g2]])
1847
2112
  end
2113
+
2114
+ # Link colocated and same_table.
2115
+ model.add(model.sum(all_tables.map { |t| same_table[[g1, g2, t]] }) == colocated[[g1, g2]])
1848
2116
  end
1849
2117
 
1850
2118
  # Min known neighbors rule.
1851
- all_tables.each do |t|
2119
+ all_guests.each do |g|
1852
2120
  model.add(
1853
2121
  model.sum(
1854
- (num_guests - 1).times.flat_map { |g1|
1855
- (g1 + 1).upto(num_guests - 1).select { |g2| c[g1][g2] > 0 }.flat_map { |g2|
1856
- all_tables.map { |t2| same_table[[g1, g2, t2]] }
1857
- }
1858
- }
2122
+ (g + 1).upto(num_guests - 1).
2123
+ select { |g2| c[g][g2] > 0 }.
2124
+ product(all_tables).
2125
+ map { |g2, t| same_table[[g, g2, t]] }
2126
+ ) +
2127
+ model.sum(
2128
+ g.times.
2129
+ select { |g1| c[g1][g] > 0 }.
2130
+ product(all_tables).
2131
+ map { |g1, t| same_table[[g1, g, t]] }
1859
2132
  ) >= min_known_neighbors
1860
2133
  )
1861
2134
  end