or-tools 0.7.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|