or-tools 0.7.0 → 0.7.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 +8 -0
- data/README.md +280 -218
- data/ext/or-tools/ext.cpp +12 -0
- data/ext/or-tools/linear.cpp +20 -1
- data/lib/or_tools/cp_solver.rb +6 -0
- data/lib/or_tools/linear_expr.rb +13 -1
- data/lib/or_tools/mp_variable.rb +1 -1
- data/lib/or_tools/solver.rb +25 -5
- data/lib/or_tools/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d3cfb5b8fd7f6a4563b2d50a2357e4bfc72fc5ef69e2c7c1a6ad7e7736a66235
|
4
|
+
data.tar.gz: a1e2307c4a94495f9fe28e85e68171a9a3474a45ed98344250920360adffbd0f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b62fd4b68b1a3b119d8da2987d263ad2fb0c4ca4d1062ef274f3dea440db62414af6188b769180a740a22898d26657569e71fefd0af9e754442061bcf481a1a
|
7
|
+
data.tar.gz: c4fbe636cd22654c867f25c954d1652540b54239a9427a7795033027a60ca7538b864e74c0134296040f57bf945750dbc80384965e7d449f9a5fb1474163ce9d
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## 0.7.1 (2022-05-27)
|
2
|
+
|
3
|
+
- Added support for time limit for `Solver`
|
4
|
+
- Added `enable_output` and `suppress_output` to `Solver`
|
5
|
+
- Improved `new` method for `Solver`
|
6
|
+
- Fixed error with offset with `Solver`
|
7
|
+
- Fixed segfault with `CpSolver`
|
8
|
+
|
1
9
|
## 0.7.0 (2022-03-23)
|
2
10
|
|
3
11
|
- Updated OR-Tools to 9.3
|
data/README.md
CHANGED
@@ -14,13 +14,73 @@ gem "or-tools"
|
|
14
14
|
|
15
15
|
Installation can take a few minutes as OR-Tools downloads and builds. For Mac ARM, also follow [these instructions](#additional-instructions).
|
16
16
|
|
17
|
-
##
|
17
|
+
## Guides
|
18
|
+
|
19
|
+
Higher Level Interfaces
|
18
20
|
|
19
21
|
- [Scheduling](#scheduling)
|
20
22
|
- [Seating](#seating)
|
21
23
|
- [Traveling Salesperson Problem (TSP)](#traveling-salesperson-problem-tsp)
|
22
24
|
- [Sudoku](#sudoku)
|
23
25
|
|
26
|
+
Linear Optimization
|
27
|
+
|
28
|
+
- [Solving an LP Problem](#solving-an-lp-problem)
|
29
|
+
|
30
|
+
Integer Optimization
|
31
|
+
|
32
|
+
- [Solving a MIP Problem](#solving-a-mip-problem)
|
33
|
+
|
34
|
+
Constraint Optimization
|
35
|
+
|
36
|
+
- [CP-SAT Solver](#cp-sat-solver)
|
37
|
+
- [Solving a CP Problem](#solving-a-cp-problem)
|
38
|
+
- [Cryptarithmetic](#cryptarithmetic)
|
39
|
+
- [The N-queens Problem](#the-n-queens-problem)
|
40
|
+
- [Setting Solver Limits](#setting-solver-limits)
|
41
|
+
|
42
|
+
Assignment
|
43
|
+
|
44
|
+
- [Solving an Assignment Problem](#solving-an-assignment-problem)
|
45
|
+
- [Assignment with Teams of Workers](#assignment-with-teams-of-workers)
|
46
|
+
- [Linear Sum Assignment Solver](#linear-sum-assignment-solver)
|
47
|
+
|
48
|
+
Routing
|
49
|
+
|
50
|
+
- [Traveling Salesperson Problem (TSP)](#traveling-salesperson-problem-tsp-1)
|
51
|
+
- [Vehicle Routing Problem (VRP)](#vehicle-routing-problem-vrp)
|
52
|
+
- [Capacity Constraints](#capacity-constraints)
|
53
|
+
- [Pickups and Deliveries](#pickups-and-deliveries)
|
54
|
+
- [Time Window Constraints](#time-window-constraints)
|
55
|
+
- [Resource Constraints](#resource-constraints)
|
56
|
+
- [Penalties and Dropping Visits](#penalties-and-dropping-visits)
|
57
|
+
- [Routing Options](#routing-options)
|
58
|
+
|
59
|
+
Bin Packing
|
60
|
+
|
61
|
+
- [The Knapsack Problem](#the-knapsack-problem)
|
62
|
+
- [Multiple Knapsacks](#multiple-knapsacks)
|
63
|
+
- [Bin Packing Problem](#bin-packing-problem)
|
64
|
+
|
65
|
+
Network Flows
|
66
|
+
|
67
|
+
- [Maximum Flows](#maximum-flows)
|
68
|
+
- [Minimum Cost Flows](#minimum-cost-flows)
|
69
|
+
- [Assignment as a Min Cost Flow Problem](#assignment-as-a-min-cost-flow-problem)
|
70
|
+
|
71
|
+
Scheduling
|
72
|
+
|
73
|
+
- [Employee Scheduling](#employee-scheduling)
|
74
|
+
- [The Job Shop Problem](#the-job-shop-problem)
|
75
|
+
|
76
|
+
Other Examples
|
77
|
+
|
78
|
+
- [Sudoku](#sudoku-1)
|
79
|
+
- [Wedding Seating Chart](#wedding-seating-chart)
|
80
|
+
- [Set Partitioning](#set-partitioning)
|
81
|
+
|
82
|
+
## Higher Level Interfaces
|
83
|
+
|
24
84
|
### Scheduling
|
25
85
|
|
26
86
|
Specify people and their availabililty
|
@@ -251,138 +311,69 @@ sudoku = ORTools::Sudoku.new(grid, x: true, anti_knight: true, magic_square: tru
|
|
251
311
|
sudoku.solution
|
252
312
|
```
|
253
313
|
|
254
|
-
##
|
314
|
+
## Linear Optimization
|
255
315
|
|
256
|
-
|
257
|
-
|
258
|
-
- [The Glop Linear Solver](#the-glop-linear-solver)
|
316
|
+
### Solving an LP Problem
|
259
317
|
|
260
|
-
|
261
|
-
|
262
|
-
- [Mixed-Integer Programming](#mixed-integer-programming)
|
263
|
-
|
264
|
-
Constraint Optimization
|
265
|
-
|
266
|
-
- [CP-SAT Solver](#cp-sat-solver)
|
267
|
-
- [Solving an Optimization Problem](#solving-an-optimization-problem)
|
268
|
-
- [Cryptarithmetic](#cryptarithmetic)
|
269
|
-
- [The N-queens Problem](#the-n-queens-problem)
|
270
|
-
- [Setting Solver Limits](#setting-solver-limits)
|
271
|
-
|
272
|
-
Assignment
|
273
|
-
|
274
|
-
- [Assignment](#assignment)
|
275
|
-
- [Assignment with Teams](#assignment-with-teams)
|
276
|
-
|
277
|
-
Routing
|
278
|
-
|
279
|
-
- [Traveling Salesperson Problem (TSP)](#traveling-salesperson-problem-tsp-1)
|
280
|
-
- [Vehicle Routing Problem (VRP)](#vehicle-routing-problem-vrp)
|
281
|
-
- [Capacity Constraints](#capacity-constraints)
|
282
|
-
- [Pickups and Deliveries](#pickups-and-deliveries)
|
283
|
-
- [Time Window Constraints](#time-window-constraints)
|
284
|
-
- [Resource Constraints](#resource-constraints)
|
285
|
-
- [Penalties and Dropping Visits](#penalties-and-dropping-visits)
|
286
|
-
- [Routing Options](#routing-options)
|
287
|
-
|
288
|
-
Bin Packing
|
289
|
-
|
290
|
-
- [The Knapsack Problem](#the-knapsack-problem)
|
291
|
-
- [Multiple Knapsacks](#multiple-knapsacks)
|
292
|
-
- [Bin Packing Problem](#bin-packing-problem)
|
293
|
-
|
294
|
-
Network Flows
|
295
|
-
|
296
|
-
- [Maximum Flows](#maximum-flows)
|
297
|
-
- [Minimum Cost Flows](#minimum-cost-flows)
|
298
|
-
- [Assignment as a Min Cost Flow Problem](#assignment-as-a-min-cost-flow-problem)
|
299
|
-
|
300
|
-
Scheduling
|
301
|
-
|
302
|
-
- [Employee Scheduling](#employee-scheduling)
|
303
|
-
- [The Job Shop Problem](#the-job-shop-problem)
|
304
|
-
|
305
|
-
Other Examples
|
306
|
-
|
307
|
-
- [Sudoku](#sudoku-1)
|
308
|
-
- [Wedding Seating Chart](#wedding-seating-chart)
|
309
|
-
- [Set Partitioning](#set-partitioning)
|
310
|
-
|
311
|
-
### The Glop Linear Solver
|
312
|
-
|
313
|
-
[Guide](https://developers.google.com/optimization/lp/glop)
|
318
|
+
[Guide](https://developers.google.com/optimization/lp/lp_example)
|
314
319
|
|
315
320
|
```ruby
|
316
321
|
# declare the solver
|
317
|
-
solver = ORTools::Solver.new("
|
322
|
+
solver = ORTools::Solver.new("GLOP")
|
318
323
|
|
319
324
|
# create the variables
|
320
325
|
x = solver.num_var(0, solver.infinity, "x")
|
321
326
|
y = solver.num_var(0, solver.infinity, "y")
|
327
|
+
puts "Number of variables = #{solver.num_variables}"
|
322
328
|
|
323
329
|
# define the constraints
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
constraint1 = solver.constraint(0, solver.infinity)
|
329
|
-
constraint1.set_coefficient(x, 3)
|
330
|
-
constraint1.set_coefficient(y, -1)
|
331
|
-
|
332
|
-
constraint2 = solver.constraint(-solver.infinity, 2)
|
333
|
-
constraint2.set_coefficient(x, 1)
|
334
|
-
constraint2.set_coefficient(y, -1)
|
330
|
+
solver.add(x + 2 * y <= 14)
|
331
|
+
solver.add(3 * x - y >= 0)
|
332
|
+
solver.add(x - y <= 2)
|
333
|
+
puts "Number of constraints = #{solver.num_constraints}"
|
335
334
|
|
336
335
|
# define the objective function
|
337
|
-
|
338
|
-
objective.set_coefficient(x, 3)
|
339
|
-
objective.set_coefficient(y, 4)
|
340
|
-
objective.set_maximization
|
336
|
+
solver.maximize(3 * x + 4 * y)
|
341
337
|
|
342
338
|
# invoke the solver
|
343
|
-
solver.solve
|
339
|
+
status = solver.solve
|
344
340
|
|
345
341
|
# display the solution
|
346
|
-
|
347
|
-
puts "
|
348
|
-
puts "
|
349
|
-
puts "
|
350
|
-
puts "
|
351
|
-
|
352
|
-
puts "
|
342
|
+
if status == :optimal
|
343
|
+
puts "Solution:"
|
344
|
+
puts "Objective value = #{solver.objective.value}"
|
345
|
+
puts "x = #{x.solution_value}"
|
346
|
+
puts "y = #{y.solution_value}"
|
347
|
+
else
|
348
|
+
puts "The problem does not have an optimal solution."
|
349
|
+
end
|
353
350
|
```
|
354
351
|
|
355
|
-
|
352
|
+
## Integer Optimization
|
356
353
|
|
357
|
-
|
354
|
+
### Solving a MIP Problem
|
355
|
+
|
356
|
+
[Guide](https://developers.google.com/optimization/mip/mip_example)
|
358
357
|
|
359
358
|
```ruby
|
360
359
|
# declare the MIP solver
|
361
|
-
solver = ORTools::Solver.new("
|
360
|
+
solver = ORTools::Solver.new("CBC")
|
362
361
|
|
363
362
|
# define the variables
|
364
363
|
infinity = solver.infinity
|
365
|
-
x = solver.int_var(0
|
366
|
-
y = solver.int_var(0
|
364
|
+
x = solver.int_var(0, infinity, "x")
|
365
|
+
y = solver.int_var(0, infinity, "y")
|
367
366
|
|
368
367
|
puts "Number of variables = #{solver.num_variables}"
|
369
368
|
|
370
369
|
# define the constraints
|
371
|
-
|
372
|
-
|
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);
|
370
|
+
solver.add(x + 7 * y <= 17.5)
|
371
|
+
solver.add(x <= 3.5)
|
378
372
|
|
379
373
|
puts "Number of constraints = #{solver.num_constraints}"
|
380
374
|
|
381
375
|
# define the objective
|
382
|
-
|
383
|
-
objective.set_coefficient(x, 1)
|
384
|
-
objective.set_coefficient(y, 10)
|
385
|
-
objective.set_maximization
|
376
|
+
solver.maximize(x + 10 * y)
|
386
377
|
|
387
378
|
# call the solver
|
388
379
|
status = solver.solve
|
@@ -398,6 +389,8 @@ else
|
|
398
389
|
end
|
399
390
|
```
|
400
391
|
|
392
|
+
## Constraint Optimization
|
393
|
+
|
401
394
|
### CP-SAT Solver
|
402
395
|
|
403
396
|
[Guide](https://developers.google.com/optimization/cp/cp_solver)
|
@@ -420,16 +413,18 @@ solver = ORTools::CpSolver.new
|
|
420
413
|
status = solver.solve(model)
|
421
414
|
|
422
415
|
# display the first solution
|
423
|
-
if status == :optimal
|
416
|
+
if status == :optimal || status == :feasible
|
424
417
|
puts "x = #{solver.value(x)}"
|
425
418
|
puts "y = #{solver.value(y)}"
|
426
419
|
puts "z = #{solver.value(z)}"
|
420
|
+
else
|
421
|
+
puts "No solution found."
|
427
422
|
end
|
428
423
|
```
|
429
424
|
|
430
|
-
### Solving
|
425
|
+
### Solving a CP Problem
|
431
426
|
|
432
|
-
[Guide](https://developers.google.com/optimization/cp/
|
427
|
+
[Guide](https://developers.google.com/optimization/cp/cp_example)
|
433
428
|
|
434
429
|
```ruby
|
435
430
|
# declare the model
|
@@ -453,12 +448,14 @@ model.maximize(x*2 + y*2 + z*3)
|
|
453
448
|
solver = ORTools::CpSolver.new
|
454
449
|
status = solver.solve(model)
|
455
450
|
|
456
|
-
|
451
|
+
# display the solution
|
452
|
+
if status == :optimal || status == :feasible
|
457
453
|
puts "Maximum of objective function: #{solver.objective_value}"
|
458
|
-
puts
|
459
|
-
puts "
|
460
|
-
puts "
|
461
|
-
|
454
|
+
puts "x = #{solver.value(x)}"
|
455
|
+
puts "y = #{solver.value(y)}"
|
456
|
+
puts "z = #{solver.value(z)}"
|
457
|
+
else
|
458
|
+
puts "No solution found."
|
462
459
|
end
|
463
460
|
```
|
464
461
|
|
@@ -613,120 +610,190 @@ if status == :optimal
|
|
613
610
|
end
|
614
611
|
```
|
615
612
|
|
616
|
-
|
613
|
+
## Assignment
|
614
|
+
|
615
|
+
### Solving an Assignment Problem
|
617
616
|
|
618
617
|
[Guide](https://developers.google.com/optimization/assignment/assignment_example)
|
619
618
|
|
620
619
|
```ruby
|
621
620
|
# create the data
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
621
|
+
costs = [
|
622
|
+
[90, 80, 75, 70],
|
623
|
+
[35, 85, 55, 65],
|
624
|
+
[125, 95, 90, 95],
|
625
|
+
[45, 110, 95, 115],
|
626
|
+
[50, 100, 90, 100]
|
627
|
+
]
|
628
|
+
num_workers = costs.length
|
629
|
+
num_tasks = costs[0].length
|
629
630
|
|
630
631
|
# create the solver
|
631
|
-
|
632
|
+
solver = ORTools::Solver.new("CBC")
|
632
633
|
|
633
|
-
#
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
end
|
634
|
+
# create the variables
|
635
|
+
x = {}
|
636
|
+
num_workers.times do |i|
|
637
|
+
num_tasks.times do |j|
|
638
|
+
x[[i, j]] = solver.int_var(0, 1, "")
|
639
639
|
end
|
640
640
|
end
|
641
641
|
|
642
|
+
# create the constraints
|
643
|
+
# each worker is assigned to at most 1 task
|
644
|
+
num_workers.times do |i|
|
645
|
+
solver.add(num_tasks.times.sum { |j| x[[i, j]] } <= 1)
|
646
|
+
end
|
647
|
+
|
648
|
+
# each task is assigned to exactly one worker
|
649
|
+
num_tasks.times do |j|
|
650
|
+
solver.add(num_workers.times.sum { |i| x[[i, j]] } == 1)
|
651
|
+
end
|
652
|
+
|
653
|
+
# create the objective function
|
654
|
+
objective_terms = []
|
655
|
+
num_workers.times do |i|
|
656
|
+
num_tasks.times do |j|
|
657
|
+
objective_terms << (costs[i][j] * x[[i, j]])
|
658
|
+
end
|
659
|
+
end
|
660
|
+
solver.minimize(objective_terms.sum)
|
661
|
+
|
642
662
|
# invoke the solver
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
663
|
+
status = solver.solve
|
664
|
+
|
665
|
+
# print the solution
|
666
|
+
if status == :optimal || status == :feasible
|
667
|
+
puts "Total cost = #{solver.objective.value}"
|
668
|
+
num_workers.times do |i|
|
669
|
+
num_tasks.times do |j|
|
670
|
+
# test if x[i,j] is 1 (with tolerance for floating point arithmetic)
|
671
|
+
if x[[i, j]].solution_value > 0.5
|
672
|
+
puts "Worker #{i} assigned to task #{j}. Cost = #{costs[i][j]}"
|
673
|
+
end
|
674
|
+
end
|
653
675
|
end
|
654
|
-
|
655
|
-
puts "No
|
656
|
-
elsif solve_status == :possible_overflow
|
657
|
-
puts "Some input costs are too large and may cause an integer overflow."
|
676
|
+
else
|
677
|
+
puts "No solution found."
|
658
678
|
end
|
659
679
|
```
|
660
680
|
|
661
|
-
### Assignment with Teams
|
681
|
+
### Assignment with Teams of Workers
|
662
682
|
|
663
|
-
[Guide](https://developers.google.com/optimization/assignment/assignment_teams)
|
683
|
+
[Guide](https://developers.google.com/optimization/assignment/assignment_teams#mip)
|
664
684
|
|
665
685
|
```ruby
|
666
|
-
# create the solver
|
667
|
-
solver = ORTools::Solver.new("SolveAssignmentProblemMIP", :cbc)
|
668
|
-
|
669
686
|
# create the data
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
687
|
+
costs = [
|
688
|
+
[90, 76, 75, 70],
|
689
|
+
[35, 85, 55, 65],
|
690
|
+
[125, 95, 90, 105],
|
691
|
+
[45, 110, 95, 115],
|
692
|
+
[60, 105, 80, 75],
|
693
|
+
[45, 65, 110, 95]
|
694
|
+
]
|
695
|
+
num_workers = costs.length
|
696
|
+
num_tasks = costs[1].length
|
676
697
|
|
677
698
|
team1 = [0, 2, 4]
|
678
699
|
team2 = [1, 3, 5]
|
679
700
|
team_max = 2
|
680
701
|
|
702
|
+
# create the solver
|
703
|
+
solver = ORTools::Solver.new("CBC")
|
704
|
+
|
681
705
|
# create the variables
|
682
|
-
num_workers = cost.length
|
683
|
-
num_tasks = cost[1].length
|
684
706
|
x = {}
|
685
|
-
|
686
707
|
num_workers.times do |i|
|
687
708
|
num_tasks.times do |j|
|
688
709
|
x[[i, j]] = solver.bool_var("x[#{i},#{j}]")
|
689
710
|
end
|
690
711
|
end
|
691
712
|
|
692
|
-
#
|
693
|
-
|
694
|
-
num_workers.times.flat_map { |i| num_tasks.times.map { |j| x[[i, j]] * cost[i][j] } }
|
695
|
-
))
|
696
|
-
|
697
|
-
# create the constraints
|
713
|
+
# add the constraints
|
714
|
+
# each worker is assigned at most 1 task
|
698
715
|
num_workers.times do |i|
|
699
|
-
solver.add(
|
716
|
+
solver.add(num_tasks.times.sum { |j| x[[i, j]] } <= 1)
|
700
717
|
end
|
701
718
|
|
719
|
+
# each task is assigned to exactly one worker
|
702
720
|
num_tasks.times do |j|
|
703
|
-
solver.add(
|
721
|
+
solver.add(num_workers.times.sum { |i| x[[i, j]] } == 1)
|
704
722
|
end
|
705
723
|
|
706
|
-
|
707
|
-
solver.add(
|
724
|
+
# each team takes at most two tasks
|
725
|
+
solver.add(team1.flat_map { |i| num_tasks.times.map { |j| x[[i, j]] } }.sum <= team_max)
|
726
|
+
solver.add(team2.flat_map { |i| num_tasks.times.map { |j| x[[i, j]] } }.sum <= team_max)
|
727
|
+
|
728
|
+
# create the objective
|
729
|
+
solver.minimize(
|
730
|
+
num_workers.times.flat_map { |i| num_tasks.times.map { |j| x[[i, j]] * costs[i][j] } }.sum
|
731
|
+
)
|
708
732
|
|
709
733
|
# invoke the solver
|
710
|
-
|
734
|
+
status = solver.solve
|
711
735
|
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
cost[i][j]
|
721
|
-
]
|
736
|
+
# display the results
|
737
|
+
if status == :optimal || status == :feasible
|
738
|
+
puts "Total cost = #{solver.objective.value}"
|
739
|
+
num_workers.times do |worker|
|
740
|
+
num_tasks.times do |task|
|
741
|
+
if x[[worker, task]].solution_value > 0.5
|
742
|
+
puts "Worker #{worker} assigned to task #{task}. Cost = #{costs[worker][task]}"
|
743
|
+
end
|
722
744
|
end
|
723
745
|
end
|
746
|
+
else
|
747
|
+
puts "No solution found."
|
724
748
|
end
|
749
|
+
```
|
725
750
|
|
726
|
-
|
727
|
-
|
751
|
+
### Linear Sum Assignment Solver
|
752
|
+
|
753
|
+
[Guide](https://developers.google.com/optimization/assignment/linear_assignment)
|
754
|
+
|
755
|
+
```ruby
|
756
|
+
# create the data
|
757
|
+
costs = [
|
758
|
+
[90, 76, 75, 70],
|
759
|
+
[35, 85, 55, 65],
|
760
|
+
[125, 95, 90, 105],
|
761
|
+
[45, 110, 95, 115],
|
762
|
+
]
|
763
|
+
num_workers = costs.length
|
764
|
+
num_tasks = costs[0].length
|
765
|
+
|
766
|
+
# create the solver
|
767
|
+
assignment = ORTools::LinearSumAssignment.new
|
768
|
+
|
769
|
+
# add the constraints
|
770
|
+
num_workers.times do |worker|
|
771
|
+
num_tasks.times do |task|
|
772
|
+
if costs[worker][task]
|
773
|
+
assignment.add_arc_with_cost(worker, task, costs[worker][task])
|
774
|
+
end
|
775
|
+
end
|
776
|
+
end
|
777
|
+
|
778
|
+
# invoke the solver
|
779
|
+
status = assignment.solve
|
780
|
+
|
781
|
+
# display the results
|
782
|
+
case status
|
783
|
+
when :optimal
|
784
|
+
puts "Total cost = #{assignment.optimal_cost}"
|
785
|
+
assignment.num_nodes.times do |i|
|
786
|
+
puts "Worker #{i} assigned to task #{assignment.right_mate(i)}. Cost = #{assignment.assignment_cost(i)}"
|
787
|
+
end
|
788
|
+
when :infeasible
|
789
|
+
puts "No assignment is possible."
|
790
|
+
when :possible_overflow
|
791
|
+
puts "Some input costs are too large and may cause an integer overflow."
|
792
|
+
end
|
728
793
|
```
|
729
794
|
|
795
|
+
## Routing
|
796
|
+
|
730
797
|
### Traveling Salesperson Problem (TSP)
|
731
798
|
|
732
799
|
[Guide](https://developers.google.com/optimization/routing/tsp.html)
|
@@ -1376,6 +1443,8 @@ routing.solve(
|
|
1376
1443
|
)
|
1377
1444
|
```
|
1378
1445
|
|
1446
|
+
## Bin Packing
|
1447
|
+
|
1379
1448
|
### The Knapsack Problem
|
1380
1449
|
|
1381
1450
|
[Guide](https://developers.google.com/optimization/bin/knapsack)
|
@@ -1425,53 +1494,40 @@ puts "Packed weights: #{packed_weights}"
|
|
1425
1494
|
```ruby
|
1426
1495
|
# create the data
|
1427
1496
|
data = {}
|
1428
|
-
weights = [48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36]
|
1429
|
-
values = [10, 30, 25, 50, 35, 30, 15, 40, 30, 35, 45, 10, 20, 30, 25]
|
1430
|
-
data[:
|
1431
|
-
data[:
|
1432
|
-
data[:items] = (0...weights.length).to_a
|
1433
|
-
data[:num_items] = weights.length
|
1434
|
-
num_bins = 5
|
1435
|
-
data[:bins] = (0...num_bins).to_a
|
1497
|
+
data[:weights] = [48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36]
|
1498
|
+
data[:values] = [10, 30, 25, 50, 35, 30, 15, 40, 30, 35, 45, 10, 20, 30, 25]
|
1499
|
+
data[:num_items] = data[:weights].length
|
1500
|
+
data[:all_items] = data[:num_items].times.to_a
|
1436
1501
|
data[:bin_capacities] = [100, 100, 100, 100, 100]
|
1502
|
+
data[:num_bins] = data[:bin_capacities].length
|
1503
|
+
data[:all_bins] = data[:num_bins].times.to_a
|
1437
1504
|
|
1438
1505
|
# declare the solver
|
1439
|
-
solver = ORTools::Solver.new("
|
1506
|
+
solver = ORTools::Solver.new("CBC")
|
1440
1507
|
|
1441
1508
|
# create the variables
|
1442
|
-
# x[i, j] = 1 if item i is packed in bin j
|
1443
1509
|
x = {}
|
1444
|
-
data[:
|
1445
|
-
data[:
|
1446
|
-
x[[i,
|
1510
|
+
data[:all_items].each do |i|
|
1511
|
+
data[:all_bins].each do |b|
|
1512
|
+
x[[i, b]] = solver.bool_var("x_#{i}_#{b}")
|
1447
1513
|
end
|
1448
1514
|
end
|
1449
1515
|
|
1450
|
-
#
|
1451
|
-
|
1452
|
-
data[:
|
1453
|
-
sum = ORTools::LinearExpr.new
|
1454
|
-
data[:bins].each do |j|
|
1455
|
-
sum += x[[i, j]]
|
1456
|
-
end
|
1457
|
-
solver.add(sum <= 1.0)
|
1516
|
+
# each item is assigned to at most one bin
|
1517
|
+
data[:all_items].each do |i|
|
1518
|
+
solver.add(data[:all_bins].sum { |b| x[[i, b]] } <= 1)
|
1458
1519
|
end
|
1459
1520
|
|
1460
1521
|
# the amount packed in each bin cannot exceed its capacity
|
1461
|
-
data[:
|
1462
|
-
|
1463
|
-
data[:items].each do |i|
|
1464
|
-
weight += x[[i, j]] * data[:weights][i]
|
1465
|
-
end
|
1466
|
-
solver.add(weight <= data[:bin_capacities][j])
|
1522
|
+
data[:all_bins].each do |b|
|
1523
|
+
solver.add(data[:all_items].sum { |i| x[[i, b]] * data[:weights][i] } <= data[:bin_capacities][b])
|
1467
1524
|
end
|
1468
1525
|
|
1469
|
-
#
|
1526
|
+
# maximize total value of packed items
|
1470
1527
|
objective = solver.objective
|
1471
|
-
|
1472
|
-
data[:
|
1473
|
-
|
1474
|
-
objective.set_coefficient(x[[i, j]], data[:values][i])
|
1528
|
+
data[:all_items].each do |i|
|
1529
|
+
data[:all_bins].each do |b|
|
1530
|
+
objective.set_coefficient(x[[i, b]], data[:values][i])
|
1475
1531
|
end
|
1476
1532
|
end
|
1477
1533
|
objective.set_maximization
|
@@ -1482,12 +1538,12 @@ status = solver.solve
|
|
1482
1538
|
if status == :optimal
|
1483
1539
|
puts "Total packed value: #{objective.value}"
|
1484
1540
|
total_weight = 0
|
1485
|
-
data[:
|
1541
|
+
data[:all_bins].each do |b|
|
1486
1542
|
bin_weight = 0
|
1487
1543
|
bin_value = 0
|
1488
|
-
puts "Bin
|
1489
|
-
data[:
|
1490
|
-
if x[[i,
|
1544
|
+
puts "Bin #{b}\n\n"
|
1545
|
+
data[:all_items].each do |i|
|
1546
|
+
if x[[i, b]].solution_value > 0
|
1491
1547
|
puts "Item #{i} - weight: #{data[:weights][i]} value: #{data[:values][i]}"
|
1492
1548
|
bin_weight += data[:weights][i]
|
1493
1549
|
bin_value += data[:values][i]
|
@@ -1518,7 +1574,7 @@ data[:bins] = data[:items]
|
|
1518
1574
|
data[:bin_capacity] = 100
|
1519
1575
|
|
1520
1576
|
# create the mip solver with the CBC backend
|
1521
|
-
solver = ORTools::Solver.new("
|
1577
|
+
solver = ORTools::Solver.new("CBC")
|
1522
1578
|
|
1523
1579
|
# variables
|
1524
1580
|
# x[i, j] = 1 if item i is packed in bin j
|
@@ -1538,17 +1594,17 @@ end
|
|
1538
1594
|
# constraints
|
1539
1595
|
# each item must be in exactly one bin
|
1540
1596
|
data[:items].each do |i|
|
1541
|
-
solver.add(
|
1597
|
+
solver.add(data[:bins].sum { |j| x[[i, j]] } == 1)
|
1542
1598
|
end
|
1543
1599
|
|
1544
1600
|
# the amount packed in each bin cannot exceed its capacity
|
1545
1601
|
data[:bins].each do |j|
|
1546
|
-
sum =
|
1602
|
+
sum = data[:items].sum { |i| x[[i, j]] * data[:weights][i] }
|
1547
1603
|
solver.add(sum <= y[j] * data[:bin_capacity])
|
1548
1604
|
end
|
1549
1605
|
|
1550
1606
|
# objective: minimize the number of bins used
|
1551
|
-
solver.minimize(
|
1607
|
+
solver.minimize(data[:bins].sum { |j| y[j] })
|
1552
1608
|
|
1553
1609
|
# call the solver and print the solution
|
1554
1610
|
if status == :optimal
|
@@ -1580,6 +1636,8 @@ else
|
|
1580
1636
|
end
|
1581
1637
|
```
|
1582
1638
|
|
1639
|
+
## Network Flows
|
1640
|
+
|
1583
1641
|
### Maximum Flows
|
1584
1642
|
|
1585
1643
|
[Guide](https://developers.google.com/optimization/flow/maxflow)
|
@@ -1664,7 +1722,7 @@ end
|
|
1664
1722
|
|
1665
1723
|
### Assignment as a Min Cost Flow Problem
|
1666
1724
|
|
1667
|
-
[Guide](https://developers.google.com/optimization/
|
1725
|
+
[Guide](https://developers.google.com/optimization/flow/assignment_min_cost_flow)
|
1668
1726
|
|
1669
1727
|
```ruby
|
1670
1728
|
# create the solver
|
@@ -1711,6 +1769,8 @@ else
|
|
1711
1769
|
end
|
1712
1770
|
```
|
1713
1771
|
|
1772
|
+
## Scheduling
|
1773
|
+
|
1714
1774
|
### Employee Scheduling
|
1715
1775
|
|
1716
1776
|
[Guide](https://developers.google.com/optimization/scheduling/employee_scheduling)
|
@@ -1915,6 +1975,8 @@ puts "Optimal Schedule Length: %i" % solver.objective_value
|
|
1915
1975
|
puts output
|
1916
1976
|
```
|
1917
1977
|
|
1978
|
+
## Other Examples
|
1979
|
+
|
1918
1980
|
### Sudoku
|
1919
1981
|
|
1920
1982
|
[Example](https://github.com/google/or-tools/blob/stable/examples/python/sudoku_sat.py)
|
@@ -2172,7 +2234,7 @@ possible_tables = []
|
|
2172
2234
|
possible_tables += guests.combination(i).to_a
|
2173
2235
|
end
|
2174
2236
|
|
2175
|
-
solver = ORTools::Solver.new("
|
2237
|
+
solver = ORTools::Solver.new("CBC")
|
2176
2238
|
|
2177
2239
|
# create a binary variable to state that a table setting is used
|
2178
2240
|
x = {}
|
@@ -2180,15 +2242,15 @@ possible_tables.each do |table|
|
|
2180
2242
|
x[table] = solver.int_var(0, 1, "table #{table.join(", ")}")
|
2181
2243
|
end
|
2182
2244
|
|
2183
|
-
solver.minimize(
|
2245
|
+
solver.minimize(possible_tables.sum { |table| x[table] * happiness(table) })
|
2184
2246
|
|
2185
2247
|
# specify the maximum number of tables
|
2186
|
-
solver.add(
|
2248
|
+
solver.add(x.values.sum <= max_tables)
|
2187
2249
|
|
2188
2250
|
# a guest must seated at one and only one table
|
2189
2251
|
guests.each do |guest|
|
2190
2252
|
tables_with_guest = possible_tables.select { |table| table.include?(guest) }
|
2191
|
-
solver.add(
|
2253
|
+
solver.add(tables_with_guest.sum { |table| x[table] } == 1)
|
2192
2254
|
end
|
2193
2255
|
|
2194
2256
|
status = solver.solve
|
data/ext/or-tools/ext.cpp
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
#include <ortools/base/version.h>
|
2
|
+
#include <ortools/init/init.h>
|
2
3
|
|
3
4
|
#include "ext.h"
|
4
5
|
|
6
|
+
using operations_research::CppBridge;
|
7
|
+
using operations_research::CppFlags;
|
8
|
+
|
5
9
|
void init_assignment(Rice::Module& m);
|
6
10
|
void init_bin_packing(Rice::Module& m);
|
7
11
|
void init_constraint(Rice::Module& m);
|
@@ -14,6 +18,7 @@ void Init_ext()
|
|
14
18
|
{
|
15
19
|
auto m = Rice::define_module("ORTools");
|
16
20
|
|
21
|
+
// TODO use operations_research::OrToolsVersionString() in 0.8.0
|
17
22
|
m.define_singleton_function(
|
18
23
|
"lib_version",
|
19
24
|
[]() {
|
@@ -27,4 +32,11 @@ void Init_ext()
|
|
27
32
|
init_linear(m);
|
28
33
|
init_network_flows(m);
|
29
34
|
init_routing(m);
|
35
|
+
|
36
|
+
// fix logging warning
|
37
|
+
CppBridge::InitLogging("");
|
38
|
+
CppFlags flags = CppFlags();
|
39
|
+
flags.logtostderr = true;
|
40
|
+
flags.log_prefix = false;
|
41
|
+
CppBridge::SetFlags(flags);
|
30
42
|
}
|
data/ext/or-tools/linear.cpp
CHANGED
@@ -47,7 +47,7 @@ namespace Rice::detail
|
|
47
47
|
void init_linear(Rice::Module& m) {
|
48
48
|
Rice::define_class_under<LinearRange>(m, "LinearRange");
|
49
49
|
|
50
|
-
// TODO remove in 0.
|
50
|
+
// TODO remove in 0.8.0
|
51
51
|
auto rb_cLinearExpr = Rice::define_class_under<LinearExpr>(m, "LinearExpr");
|
52
52
|
rb_cLinearExpr.define_constructor(Rice::Constructor<LinearExpr>());
|
53
53
|
|
@@ -60,12 +60,29 @@ void init_linear(Rice::Module& m) {
|
|
60
60
|
|
61
61
|
Rice::define_class_under<MPObjective>(m, "MPObjective")
|
62
62
|
.define_method("value", &MPObjective::Value)
|
63
|
+
.define_method("clear", &MPObjective::Clear)
|
63
64
|
.define_method("set_coefficient", &MPObjective::SetCoefficient)
|
65
|
+
.define_method("set_offset", &MPObjective::SetOffset)
|
64
66
|
.define_method("set_maximization", &MPObjective::SetMaximization)
|
65
67
|
.define_method("set_minimization", &MPObjective::SetMinimization);
|
66
68
|
|
67
69
|
Rice::define_class_under<MPSolver>(m, "Solver")
|
68
70
|
.define_constructor(Rice::Constructor<MPSolver, std::string, MPSolver::OptimizationProblemType>())
|
71
|
+
.define_singleton_function(
|
72
|
+
"_create",
|
73
|
+
[](const std::string& solver_id) {
|
74
|
+
std::unique_ptr<MPSolver> solver(MPSolver::CreateSolver(solver_id));
|
75
|
+
if (!solver) {
|
76
|
+
throw std::runtime_error("Unrecognized solver type");
|
77
|
+
}
|
78
|
+
return solver;
|
79
|
+
})
|
80
|
+
.define_method(
|
81
|
+
"time_limit=",
|
82
|
+
[](MPSolver& self, double time_limit) {
|
83
|
+
// use milliseconds to match Python
|
84
|
+
self.SetTimeLimit(absl::Milliseconds(time_limit));
|
85
|
+
})
|
69
86
|
.define_method(
|
70
87
|
"infinity",
|
71
88
|
[](MPSolver& self) {
|
@@ -81,6 +98,8 @@ void init_linear(Rice::Module& m) {
|
|
81
98
|
.define_method("num_variables", &MPSolver::NumVariables)
|
82
99
|
.define_method("num_constraints", &MPSolver::NumConstraints)
|
83
100
|
.define_method("wall_time", &MPSolver::wall_time)
|
101
|
+
.define_method("enable_output", &MPSolver::EnableOutput)
|
102
|
+
.define_method("suppress_output", &MPSolver::SuppressOutput)
|
84
103
|
.define_method("iterations", &MPSolver::iterations)
|
85
104
|
.define_method("nodes", &MPSolver::nodes)
|
86
105
|
.define_method("objective", &MPSolver::MutableObjective)
|
data/lib/or_tools/cp_solver.rb
CHANGED
@@ -12,6 +12,12 @@ module ORTools
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def value(var)
|
15
|
+
# could also check solution_size == 0
|
16
|
+
unless [:feasible, :optimal].include?(@response.status)
|
17
|
+
# could return nil, but raise error like Python library
|
18
|
+
raise Error, "No solution found"
|
19
|
+
end
|
20
|
+
|
15
21
|
if var.is_a?(BoolVar)
|
16
22
|
_solution_boolean_value(@response, var)
|
17
23
|
else
|
data/lib/or_tools/linear_expr.rb
CHANGED
@@ -27,7 +27,11 @@ module ORTools
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def *(other)
|
30
|
-
|
30
|
+
if is_a?(Constant)
|
31
|
+
ProductCst.new(other, @val)
|
32
|
+
else
|
33
|
+
ProductCst.new(self, other)
|
34
|
+
end
|
31
35
|
end
|
32
36
|
|
33
37
|
def /(cst)
|
@@ -69,5 +73,13 @@ module ORTools
|
|
69
73
|
def inspect
|
70
74
|
"#<#{self.class.name} #{to_s}>"
|
71
75
|
end
|
76
|
+
|
77
|
+
def coerce(other)
|
78
|
+
if other.is_a?(Numeric)
|
79
|
+
[Constant.new(other), self]
|
80
|
+
else
|
81
|
+
raise TypeError, "#{self.class} can't be coerced into #{other.class}"
|
82
|
+
end
|
83
|
+
end
|
72
84
|
end
|
73
85
|
end
|
data/lib/or_tools/mp_variable.rb
CHANGED
data/lib/or_tools/solver.rb
CHANGED
@@ -9,17 +9,37 @@ module ORTools
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def maximize(expr)
|
12
|
-
expr
|
13
|
-
objective.set_coefficient(v, c)
|
14
|
-
end
|
12
|
+
set_objective(expr)
|
15
13
|
objective.set_maximization
|
16
14
|
end
|
17
15
|
|
18
16
|
def minimize(expr)
|
19
|
-
expr
|
17
|
+
set_objective(expr)
|
18
|
+
objective.set_minimization
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def set_objective(expr)
|
24
|
+
objective.clear
|
25
|
+
coeffs = expr.coeffs
|
26
|
+
offset = coeffs.delete(OFFSET_KEY)
|
27
|
+
objective.set_offset(offset) if offset
|
28
|
+
coeffs.each do |v, c|
|
20
29
|
objective.set_coefficient(v, c)
|
21
30
|
end
|
22
|
-
objective.set_minimization
|
23
31
|
end
|
32
|
+
|
33
|
+
# hack to work with Rice constructor
|
34
|
+
m = Module.new do
|
35
|
+
def new(solver_id, *args)
|
36
|
+
if args.empty?
|
37
|
+
_create(solver_id)
|
38
|
+
else
|
39
|
+
super
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
singleton_class.prepend(m)
|
24
44
|
end
|
25
45
|
end
|
data/lib/or_tools/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: or-tools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-05-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rice
|
@@ -75,7 +75,7 @@ files:
|
|
75
75
|
- lib/or_tools/version.rb
|
76
76
|
homepage: https://github.com/ankane/or-tools-ruby
|
77
77
|
licenses:
|
78
|
-
-
|
78
|
+
- Apache-2.0
|
79
79
|
metadata: {}
|
80
80
|
post_install_message:
|
81
81
|
rdoc_options: []
|