or-tools 0.7.0 → 0.7.3

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: 03544e925041f7337e6e6ba1b00b6d6c4d97353bcd7ee3785a46959a952bf09c
4
- data.tar.gz: eddbab6814900afc7ddda651da92a53aa06b0db857659ab2ccd1c1adb9770fe0
3
+ metadata.gz: 976e5d3e7d64ac20e1d445baa12720acd4e91469d1fddbaf8c71cf32e1bb7be6
4
+ data.tar.gz: 38d1b76452dc3df179cc7536e466846bf6030dfcd1da283393cbfa3c9c68b4b6
5
5
  SHA512:
6
- metadata.gz: '041939b99da310567a099f7654658697b93e36436abf06779336ca51e0d02be94473c0474d238041285975f293734cde65c37e5db565b8f30b49f856fe519100'
7
- data.tar.gz: '09f6a2f0701acb005f356555df0d0e19befe36090d4de1b2188e7944a55836c80022eed5d4e185dc3b3b016c9c73fa3d5957e8c3cf8adf83bf4773b1090e5f0a'
6
+ metadata.gz: 2612839346ab70b75cb4f307ae2116816a4638580a1e943e3c09247af4ff0e27177549ae2c170aa1b67d6453922883265c7c7c93d8dfbf573ec1bce650864bc6
7
+ data.tar.gz: 256087097f368c7c34d4affc531ef744184c897e99b0727f1c00a620644399de77b9c874e5cfc0f5a519552e965c55647146ad3b29346ebfd4ecc595a8d35068
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## 0.7.3 (2022-07-23)
2
+
3
+ - Added more methods to `RoutingModel` and `RoutingDimension`
4
+
5
+ ## 0.7.2 (2022-05-28)
6
+
7
+ - Fixed library not loaded error on Mac
8
+
9
+ ## 0.7.1 (2022-05-27)
10
+
11
+ - Added support for time limit for `Solver`
12
+ - Added `enable_output` and `suppress_output` to `Solver`
13
+ - Improved `new` method for `Solver`
14
+ - Fixed error with offset with `Solver`
15
+ - Fixed segfault with `CpSolver`
16
+
1
17
  ## 0.7.0 (2022-03-23)
2
18
 
3
19
  - 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
- ## Higher Level Interfaces
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
- ## Guides
314
+ ## Linear Optimization
255
315
 
256
- Linear Optimization
257
-
258
- - [The Glop Linear Solver](#the-glop-linear-solver)
316
+ ### Solving an LP Problem
259
317
 
260
- Integer Optimization
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("LinearProgrammingExample", :glop)
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
- constraint0 = solver.constraint(-solver.infinity, 14)
325
- constraint0.set_coefficient(x, 1)
326
- constraint0.set_coefficient(y, 2)
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
- objective = solver.objective
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
- opt_solution = 3 * x.solution_value + 4 * y.solution_value
347
- puts "Number of variables = #{solver.num_variables}"
348
- puts "Number of constraints = #{solver.num_constraints}"
349
- puts "Solution:"
350
- puts "x = #{x.solution_value}"
351
- puts "y = #{y.solution_value}"
352
- puts "Optimal objective value = #{opt_solution}"
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
- ### Mixed-Integer Programming
352
+ ## Integer Optimization
356
353
 
357
- [Guide](https://developers.google.com/optimization/mip/integer_opt)
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("simple_mip_program", :cbc)
360
+ solver = ORTools::Solver.new("CBC")
362
361
 
363
362
  # define the variables
364
363
  infinity = solver.infinity
365
- x = solver.int_var(0.0, infinity, "x")
366
- y = solver.int_var(0.0, infinity, "y")
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
- 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);
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
- objective = solver.objective
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 an Optimization Problem
425
+ ### Solving a CP Problem
431
426
 
432
- [Guide](https://developers.google.com/optimization/cp/integer_opt_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
- if status == :optimal
451
+ # display the solution
452
+ if status == :optimal || status == :feasible
457
453
  puts "Maximum of objective function: #{solver.objective_value}"
458
- puts
459
- puts "x value: #{solver.value(x)}"
460
- puts "y value: #{solver.value(y)}"
461
- puts "z value: #{solver.value(z)}"
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
- ### Assignment
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
- 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
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
- assignment = ORTools::LinearSumAssignment.new
632
+ solver = ORTools::Solver.new("CBC")
632
633
 
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
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
- 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
- ]
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
- 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."
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
- 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]]
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
- # 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
713
+ # add the constraints
714
+ # each worker is assigned at most 1 task
698
715
  num_workers.times do |i|
699
- solver.add(solver.sum(num_tasks.times.map { |j| x[[i, j]] }) <= 1)
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(solver.sum(num_workers.times.map { |i| x[[i, j]] }) == 1)
721
+ solver.add(num_workers.times.sum { |i| x[[i, j]] } == 1)
704
722
  end
705
723
 
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)
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
- sol = solver.solve
734
+ status = solver.solve
711
735
 
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
- ]
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
- puts
727
- puts "Time = #{solver.wall_time} milliseconds"
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[:weights] = weights
1431
- data[:values] = values
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("simple_mip_program", :cbc)
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[:items].each do |i|
1445
- data[:bins].each do |j|
1446
- x[[i, j]] = solver.int_var(0, 1, "x_%i_%i" % [i, j])
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
- # define the constraints
1451
- # each item can be in at most one bin
1452
- data[:items].each do |i|
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[:bins].each do |j|
1462
- weight = ORTools::LinearExpr.new
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
- # define the objective
1526
+ # maximize total value of packed items
1470
1527
  objective = solver.objective
1471
-
1472
- data[:items].each do |i|
1473
- data[:bins].each do |j|
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[:bins].each do |j|
1541
+ data[:all_bins].each do |b|
1486
1542
  bin_weight = 0
1487
1543
  bin_value = 0
1488
- puts "Bin #{j}\n\n"
1489
- data[:items].each do |i|
1490
- if x[[i, j]].solution_value > 0
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("simple_mip_program", :cbc)
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(solver.sum(data[:bins].map { |j| x[[i, j]] }) == 1)
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 = solver.sum(data[:items].map { |i| x[[i, j]] * data[:weights][i] })
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(solver.sum(data[:bins].map { |j| y[j] }))
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/assignment/assignment_min_cost_flow)
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("Wedding Seating Model", :cbc)
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(solver.sum(possible_tables.map { |table| x[table] * happiness(table) }))
2245
+ solver.minimize(possible_tables.sum { |table| x[table] * happiness(table) })
2184
2246
 
2185
2247
  # specify the maximum number of tables
2186
- solver.add(solver.sum(x.values) <= max_tables)
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(solver.sum(tables_with_guest.map { |table| x[table] }) == 1)
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
  }
@@ -23,8 +23,9 @@ else
23
23
  rpath = "'#{rpath_prefix}/../../tmp/or-tools/lib'"
24
24
  end
25
25
 
26
+ # find_header and find_library first check without adding path
27
+ # which can cause them to find system library
26
28
  $INCFLAGS << " -I#{inc}"
27
-
28
29
  $LDFLAGS.prepend("-Wl,-rpath,#{rpath} -L#{lib} ")
29
30
  raise "OR-Tools not found" unless have_library("ortools")
30
31
 
@@ -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.7.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)
@@ -9,6 +9,7 @@ using operations_research::DefaultRoutingSearchParameters;
9
9
  using operations_research::FirstSolutionStrategy;
10
10
  using operations_research::LocalSearchMetaheuristic;
11
11
  using operations_research::RoutingDimension;
12
+ using operations_research::RoutingDisjunctionIndex;
12
13
  using operations_research::RoutingIndexManager;
13
14
  using operations_research::RoutingModel;
14
15
  using operations_research::RoutingModelParameters;
@@ -55,6 +56,19 @@ namespace Rice::detail
55
56
  };
56
57
  }
57
58
 
59
+ namespace Rice::detail
60
+ {
61
+ template<class T, class U>
62
+ class To_Ruby<std::pair<T, U>>
63
+ {
64
+ public:
65
+ VALUE convert(std::pair<T, U> const & x)
66
+ {
67
+ return rb_ary_new3(2, To_Ruby<T>().convert(x.first), To_Ruby<U>().convert(x.second));
68
+ }
69
+ };
70
+ }
71
+
58
72
  void init_routing(Rice::Module& m) {
59
73
  auto rb_cRoutingSearchParameters = Rice::define_class_under<RoutingSearchParameters>(m, "RoutingSearchParameters");
60
74
  auto rb_cIntVar = Rice::define_class_under<operations_research::IntVar>(m, "IntVar");
@@ -198,8 +212,28 @@ void init_routing(Rice::Module& m) {
198
212
  .define_method("old_end_max", &operations_research::IntervalVar::OldEndMax);
199
213
 
200
214
  Rice::define_class_under<RoutingDimension>(m, "RoutingDimension")
215
+ .define_method("transit_value", &RoutingDimension::GetTransitValue)
216
+ // TODO GetTransitValueFromClass
217
+ .define_method("cumul_var", &RoutingDimension::CumulVar)
218
+ .define_method("transit_var", &RoutingDimension::TransitVar)
219
+ .define_method("fixed_transit_var", &RoutingDimension::FixedTransitVar)
220
+ .define_method("slack_var", &RoutingDimension::SlackVar)
221
+ .define_method("set_span_upper_bound_for_vehicle", &RoutingDimension::SetSpanUpperBoundForVehicle)
222
+ .define_method("set_span_cost_coefficient_for_vehicle", &RoutingDimension::SetSpanCostCoefficientForVehicle)
223
+ .define_method("set_span_cost_coefficient_for_all_vehicles", &RoutingDimension::SetSpanCostCoefficientForAllVehicles)
224
+ .define_method("set_global_span_cost_coefficient", &RoutingDimension::SetGlobalSpanCostCoefficient)
225
+ // alias
201
226
  .define_method("global_span_cost_coefficient=", &RoutingDimension::SetGlobalSpanCostCoefficient)
202
- .define_method("cumul_var", &RoutingDimension::CumulVar);
227
+ .define_method("set_cumul_var_soft_upper_bound", &RoutingDimension::SetCumulVarSoftUpperBound)
228
+ .define_method("cumul_var_soft_upper_bound?", &RoutingDimension::HasCumulVarSoftUpperBound)
229
+ .define_method("cumul_var_soft_upper_bound", &RoutingDimension::GetCumulVarSoftUpperBound)
230
+ .define_method("cumul_var_soft_upper_bound_coefficient", &RoutingDimension::GetCumulVarSoftUpperBoundCoefficient)
231
+ .define_method("set_cumul_var_soft_lower_bound", &RoutingDimension::SetCumulVarSoftLowerBound)
232
+ .define_method("cumul_var_soft_lower_bound?", &RoutingDimension::HasCumulVarSoftLowerBound)
233
+ .define_method("cumul_var_soft_lower_bound", &RoutingDimension::GetCumulVarSoftLowerBound)
234
+ .define_method("cumul_var_soft_lower_bound_coefficient", &RoutingDimension::GetCumulVarSoftLowerBoundCoefficient);
235
+
236
+ Rice::define_class_under<RoutingDisjunctionIndex>(m, "RoutingDisjunctionIndex");
203
237
 
204
238
  Rice::define_class_under<operations_research::Constraint>(m, "Constraint")
205
239
  .define_method("post", &operations_research::Constraint::Post)
@@ -264,27 +298,103 @@ void init_routing(Rice::Module& m) {
264
298
 
265
299
  Rice::define_class_under<RoutingModel>(m, "RoutingModel")
266
300
  .define_constructor(Rice::Constructor<RoutingModel, RoutingIndexManager, RoutingModelParameters>(), Rice::Arg("index_manager"), Rice::Arg("parameters") = operations_research::DefaultRoutingModelParameters())
301
+ .define_method("register_unary_transit_vector", &RoutingModel::RegisterUnaryTransitVector)
267
302
  .define_method(
268
- "register_transit_callback",
303
+ "register_unary_transit_callback",
269
304
  [](RoutingModel& self, Object callback) {
270
- return self.RegisterTransitCallback(
271
- [callback](int64_t from_index, int64_t to_index) -> int64_t {
272
- return Rice::detail::From_Ruby<int64_t>().convert(callback.call("call", from_index, to_index));
305
+ return self.RegisterUnaryTransitCallback(
306
+ [callback](int64_t from_index) -> int64_t {
307
+ return Rice::detail::From_Ruby<int64_t>().convert(callback.call("call", from_index));
273
308
  }
274
309
  );
275
310
  })
311
+ .define_method("register_transit_matrix", &RoutingModel::RegisterTransitMatrix)
276
312
  .define_method(
277
- "register_unary_transit_callback",
313
+ "register_transit_callback",
278
314
  [](RoutingModel& self, Object callback) {
279
- return self.RegisterUnaryTransitCallback(
280
- [callback](int64_t from_index) -> int64_t {
281
- return Rice::detail::From_Ruby<int64_t>().convert(callback.call("call", from_index));
315
+ return self.RegisterTransitCallback(
316
+ [callback](int64_t from_index, int64_t to_index) -> int64_t {
317
+ return Rice::detail::From_Ruby<int64_t>().convert(callback.call("call", from_index, to_index));
282
318
  }
283
319
  );
284
320
  })
321
+ .define_method("add_dimension", &RoutingModel::AddDimension)
322
+ .define_method("add_dimension_with_vehicle_transits", &RoutingModel::AddDimensionWithVehicleTransits)
323
+ .define_method("add_dimension_with_vehicle_capacity", &RoutingModel::AddDimensionWithVehicleCapacity)
324
+ .define_method("add_dimension_with_vehicle_transit_and_capacity", &RoutingModel::AddDimensionWithVehicleTransitAndCapacity)
325
+ .define_method("add_constant_dimension_with_slack", &RoutingModel::AddConstantDimensionWithSlack)
326
+ .define_method("add_constant_dimension", &RoutingModel::AddConstantDimension)
327
+ .define_method("add_vector_dimension", &RoutingModel::AddVectorDimension)
328
+ .define_method("add_matrix_dimension", &RoutingModel::AddMatrixDimension)
329
+ // TODO AddDimensionDependentDimensionWithVehicleCapacity
330
+ // .define_method("make_path_spans_and_total_slacks", &RoutingModel::MakePathSpansAndTotalSlacks)
331
+ .define_method("all_dimension_names", &RoutingModel::GetAllDimensionNames)
332
+ // .define_method("dimensions", &RoutingModel::GetDimensions)
333
+ // .define_method("dimensions_with_soft_or_span_costs", &RoutingModel::GetDimensionsWithSoftOrSpanCosts)
334
+ .define_method("dimension?", &RoutingModel::HasDimension)
335
+ // .define_method("dimension_or_die", &RoutingModel::GetDimensionOrDie)
336
+ .define_method("mutable_dimension", &RoutingModel::GetMutableDimension)
337
+ .define_method("set_primary_constrained_dimension", &RoutingModel::SetPrimaryConstrainedDimension)
338
+ .define_method("primary_constrained_dimension", &RoutingModel::GetPrimaryConstrainedDimension)
339
+ .define_method("add_resource_group", &RoutingModel::AddResourceGroup)
340
+ .define_method("dimension_resource_group_indices", &RoutingModel::GetDimensionResourceGroupIndices)
341
+ .define_method("dimension_resource_group_index", &RoutingModel::GetDimensionResourceGroupIndex)
342
+ .define_method("add_disjunction", &RoutingModel::AddDisjunction, Rice::Arg("indices"), Rice::Arg("penalty"), Rice::Arg("max_cardinality") = (int64_t)1)
343
+ .define_method("disjunction_indices", &RoutingModel::GetDisjunctionIndices)
344
+ .define_method("disjunction_penalty", &RoutingModel::GetDisjunctionPenalty)
345
+ .define_method("disjunction_max_cardinality", &RoutingModel::GetDisjunctionMaxCardinality)
346
+ .define_method("number_of_disjunctions", &RoutingModel::GetNumberOfDisjunctions)
347
+ .define_method("mandatory_disjunctions?", &RoutingModel::HasMandatoryDisjunctions)
348
+ .define_method("max_cardinality_constrained_disjunctions?", &RoutingModel::HasMaxCardinalityConstrainedDisjunctions)
349
+ .define_method("perfect_binary_disjunctions", &RoutingModel::GetPerfectBinaryDisjunctions)
350
+ .define_method("ignore_disjunctions_already_forced_to_zero", &RoutingModel::IgnoreDisjunctionsAlreadyForcedToZero)
351
+ .define_method("add_soft_same_vehicle_constraint", &RoutingModel::AddSoftSameVehicleConstraint)
352
+ .define_method("set_allowed_vehicles_for_index", &RoutingModel::SetAllowedVehiclesForIndex)
353
+ .define_method("vehicle_allowed_for_index?", &RoutingModel::IsVehicleAllowedForIndex)
354
+ .define_method("add_pickup_and_delivery", &RoutingModel::AddPickupAndDelivery)
355
+ .define_method("add_pickup_and_delivery_sets", &RoutingModel::AddPickupAndDeliverySets)
356
+ .define_method("pickup_index_pairs", &RoutingModel::GetPickupIndexPairs)
357
+ .define_method("delivery_index_pairs", &RoutingModel::GetDeliveryIndexPairs)
358
+ // TODO SetPickupAndDeliveryPolicyOfAllVehicles
359
+ // TODO SetPickupAndDeliveryPolicyOfVehicle
360
+ // TODO GetPickupAndDeliveryPolicyOfVehicle
361
+ .define_method("num_of_singleton_nodes", &RoutingModel::GetNumOfSingletonNodes)
362
+ .define_method("unperformed_penalty", &RoutingModel::UnperformedPenalty)
363
+ .define_method("unperformed_penalty_or_value", &RoutingModel::UnperformedPenaltyOrValue)
285
364
  .define_method("depot", &RoutingModel::GetDepot)
286
- .define_method("size", &RoutingModel::Size)
287
- .define_method("status", [](RoutingModel& self) {
365
+ .define_method("set_maximum_number_of_active_vehicles", &RoutingModel::SetMaximumNumberOfActiveVehicles)
366
+ .define_method("maximum_number_of_active_vehicles", &RoutingModel::GetMaximumNumberOfActiveVehicles)
367
+ .define_method("set_arc_cost_evaluator_of_all_vehicles", &RoutingModel::SetArcCostEvaluatorOfAllVehicles)
368
+ .define_method("set_arc_cost_evaluator_of_vehicle", &RoutingModel::SetArcCostEvaluatorOfVehicle)
369
+ .define_method("set_fixed_cost_of_all_vehicles", &RoutingModel::SetFixedCostOfAllVehicles)
370
+ .define_method("set_fixed_cost_of_vehicle", &RoutingModel::SetFixedCostOfVehicle)
371
+ .define_method("fixed_cost_of_vehicle", &RoutingModel::GetFixedCostOfVehicle)
372
+ .define_method("set_amortized_cost_factors_of_all_vehicles", &RoutingModel::SetAmortizedCostFactorsOfAllVehicles)
373
+ .define_method("set_amortized_cost_factors_of_vehicle", &RoutingModel::SetAmortizedCostFactorsOfVehicle)
374
+ .define_method("amortized_linear_cost_factor_of_vehicles", &RoutingModel::GetAmortizedLinearCostFactorOfVehicles)
375
+ .define_method("amortized_quadratic_cost_factor_of_vehicles", &RoutingModel::GetAmortizedQuadraticCostFactorOfVehicles)
376
+ .define_method("set_vehicle_used_when_empty", &RoutingModel::SetVehicleUsedWhenEmpty)
377
+ .define_method("vehicle_used_when_empty?", &RoutingModel::IsVehicleUsedWhenEmpty)
378
+ .define_method("add_variable_minimized_by_finalizer", &RoutingModel::AddVariableMinimizedByFinalizer)
379
+ .define_method("add_variable_maximized_by_finalizer", &RoutingModel::AddVariableMaximizedByFinalizer)
380
+ .define_method("add_weighted_variable_minimized_by_finalizer", &RoutingModel::AddWeightedVariableMinimizedByFinalizer)
381
+ .define_method("add_weighted_variable_maximized_by_finalizer", &RoutingModel::AddWeightedVariableMaximizedByFinalizer)
382
+ .define_method("add_variable_target_to_finalizer", &RoutingModel::AddVariableTargetToFinalizer)
383
+ .define_method("add_weighted_variable_target_to_finalizer", &RoutingModel::AddWeightedVariableTargetToFinalizer)
384
+ .define_method("close_model", &RoutingModel::CloseModel)
385
+ .define_method(
386
+ "solve",
387
+ [](RoutingModel& self) {
388
+ return self.Solve();
389
+ })
390
+ .define_method(
391
+ "solve_with_parameters",
392
+ [](RoutingModel& self, const RoutingSearchParameters& search_parameters) {
393
+ return self.SolveWithParameters(search_parameters);
394
+ })
395
+ .define_method("compute_lower_bound", &RoutingModel::ComputeLowerBound)
396
+ .define_method("status",
397
+ [](RoutingModel& self) {
288
398
  auto status = self.status();
289
399
 
290
400
  if (status == RoutingModel::ROUTING_NOT_SOLVED) {
@@ -301,30 +411,22 @@ void init_routing(Rice::Module& m) {
301
411
  throw std::runtime_error("Unknown solver status");
302
412
  }
303
413
  })
304
- .define_method("vehicle_var", &RoutingModel::VehicleVar)
305
- .define_method("set_arc_cost_evaluator_of_all_vehicles", &RoutingModel::SetArcCostEvaluatorOfAllVehicles)
306
- .define_method("set_arc_cost_evaluator_of_vehicle", &RoutingModel::SetArcCostEvaluatorOfVehicle)
307
- .define_method("set_fixed_cost_of_all_vehicles", &RoutingModel::SetFixedCostOfAllVehicles)
308
- .define_method("set_fixed_cost_of_vehicle", &RoutingModel::SetFixedCostOfVehicle)
309
- .define_method("fixed_cost_of_vehicle", &RoutingModel::GetFixedCostOfVehicle)
310
- .define_method("add_dimension", &RoutingModel::AddDimension)
311
- .define_method(
312
- "add_dimension_with_vehicle_capacity",
313
- [](RoutingModel& self, int evaluator_index, int64_t slack_max, std::vector<int64_t> vehicle_capacities, bool fix_start_cumul_to_zero, const std::string& name) {
314
- self.AddDimensionWithVehicleCapacity(evaluator_index, slack_max, vehicle_capacities, fix_start_cumul_to_zero, name);
315
- })
316
- .define_method(
317
- "add_dimension_with_vehicle_transits",
318
- [](RoutingModel& self, std::vector<int> evaluator_indices, int64_t slack_max, int64_t capacity, bool fix_start_cumul_to_zero, const std::string& name) {
319
- self.AddDimensionWithVehicleTransits(evaluator_indices, slack_max, capacity, fix_start_cumul_to_zero, name);
320
- })
321
- .define_method(
322
- "add_disjunction",
323
- [](RoutingModel& self, std::vector<int64_t> indices, int64_t penalty) {
324
- self.AddDisjunction(indices, penalty);
325
- })
326
- .define_method("add_pickup_and_delivery", &RoutingModel::AddPickupAndDelivery)
327
- .define_method("solver", &RoutingModel::solver)
414
+ .define_method("apply_locks", &RoutingModel::ApplyLocks)
415
+ .define_method("apply_locks_to_all_vehicles", &RoutingModel::ApplyLocksToAllVehicles)
416
+ .define_method("pre_assignment", &RoutingModel::PreAssignment)
417
+ .define_method("mutable_pre_assignment", &RoutingModel::MutablePreAssignment)
418
+ .define_method("write_assignment", &RoutingModel::WriteAssignment)
419
+ .define_method("read_assignment", &RoutingModel::ReadAssignment)
420
+ .define_method("restore_assignment", &RoutingModel::RestoreAssignment)
421
+ .define_method("read_assignment_from_routes", &RoutingModel::ReadAssignmentFromRoutes)
422
+ .define_method("routes_to_assignment", &RoutingModel::RoutesToAssignment)
423
+ .define_method("assignment_to_routes", &RoutingModel::AssignmentToRoutes)
424
+ .define_method("compact_assignment", &RoutingModel::CompactAssignment)
425
+ .define_method("compact_and_check_assignment", &RoutingModel::CompactAndCheckAssignment)
426
+ .define_method("add_to_assignment", &RoutingModel::AddToAssignment)
427
+ .define_method("add_interval_to_assignment", &RoutingModel::AddIntervalToAssignment)
428
+ // TODO PackCumulsOfOptimizerDimensionsFromAssignment
429
+ // TODO AddLocalSearchFilter
328
430
  .define_method("start", &RoutingModel::Start)
329
431
  .define_method("end", &RoutingModel::End)
330
432
  .define_method("start?", &RoutingModel::IsStart)
@@ -333,12 +435,23 @@ void init_routing(Rice::Module& m) {
333
435
  .define_method("next", &RoutingModel::Next)
334
436
  .define_method("vehicle_used?", &RoutingModel::IsVehicleUsed)
335
437
  .define_method("next_var", &RoutingModel::NextVar)
438
+ .define_method("active_var", &RoutingModel::ActiveVar)
439
+ .define_method("active_vehicle_var", &RoutingModel::ActiveVehicleVar)
440
+ .define_method("vehicle_route_considered_var", &RoutingModel::VehicleRouteConsideredVar)
441
+ .define_method("vehicle_var", &RoutingModel::VehicleVar)
442
+ .define_method("resource_var", &RoutingModel::ResourceVar)
443
+ .define_method("cost_var", &RoutingModel::CostVar)
336
444
  .define_method("arc_cost_for_vehicle", &RoutingModel::GetArcCostForVehicle)
337
- .define_method("mutable_dimension", &RoutingModel::GetMutableDimension)
338
- .define_method("add_variable_minimized_by_finalizer", &RoutingModel::AddVariableMinimizedByFinalizer)
339
- .define_method(
340
- "solve_with_parameters",
341
- [](RoutingModel& self, const RoutingSearchParameters& search_parameters) {
342
- return self.SolveWithParameters(search_parameters);
343
- });
445
+ .define_method("costs_are_homogeneous_across_vehicles?", &RoutingModel::CostsAreHomogeneousAcrossVehicles)
446
+ .define_method("homogeneous_cost", &RoutingModel::GetHomogeneousCost)
447
+ .define_method("arc_cost_for_first_solution", &RoutingModel::GetArcCostForFirstSolution)
448
+ .define_method("cost_classes_count", &RoutingModel::GetCostClassesCount)
449
+ .define_method("non_zero_cost_classes_count", &RoutingModel::GetNonZeroCostClassesCount)
450
+ .define_method("vehicle_classes_count", &RoutingModel::GetVehicleClassesCount)
451
+ .define_method("arc_is_more_constrained_than_arc?", &RoutingModel::ArcIsMoreConstrainedThanArc)
452
+ .define_method("solver", &RoutingModel::solver)
453
+ .define_method("nodes", &RoutingModel::nodes)
454
+ .define_method("vehicles", &RoutingModel::vehicles)
455
+ .define_method("size", &RoutingModel::Size)
456
+ .define_method("matching_model?", &RoutingModel::IsMatchingModel);
344
457
  }
@@ -142,12 +142,11 @@ Dir.mktmpdir do |extract_path|
142
142
 
143
143
  # shared library
144
144
  FileUtils.mkdir(File.join(path, "lib"))
145
- Dir.glob("lib/libortools.{dylib,so.9}", base: extract_path) do |file|
146
- FileUtils.cp(File.join(extract_path, file), File.join(path, file))
147
- end
148
- so_path = File.join(path, "lib/libortools.so.9")
149
- if File.exist?(so_path)
150
- File.symlink(so_path, File.join(path, "lib/libortools.so"))
145
+ Dir.glob("lib/libortools.{9.dylib,so.9}", base: extract_path) do |file|
146
+ so_path = File.join(path, file)
147
+ FileUtils.cp(File.join(extract_path, file), so_path)
148
+ ext = file.end_with?(".dylib") ? "dylib" : "so"
149
+ File.symlink(so_path, File.join(path, "lib/libortools.#{ext}"))
151
150
  end
152
151
  end
153
152
 
@@ -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
@@ -27,7 +27,11 @@ module ORTools
27
27
  end
28
28
 
29
29
  def *(other)
30
- ProductCst.new(self, other)
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
@@ -1,5 +1,5 @@
1
1
  module ORTools
2
- # TODO change to VariableExpr in 0.7.0
2
+ # TODO change to VariableExpr in 0.8.0
3
3
  class MPVariable < LinearExpr
4
4
  def add_self_to_coeff_map_or_stack(coeffs, multiplier, stack)
5
5
  coeffs[self] += multiplier
@@ -9,17 +9,37 @@ module ORTools
9
9
  end
10
10
 
11
11
  def maximize(expr)
12
- expr.coeffs.each do |v, c|
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.coeffs.each do |v, c|
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/tsp.rb CHANGED
@@ -22,13 +22,7 @@ module ORTools
22
22
  manager = ORTools::RoutingIndexManager.new(locations.size, 1, 0)
23
23
  routing = ORTools::RoutingModel.new(manager)
24
24
 
25
- distance_callback = lambda do |from_index, to_index|
26
- from_node = manager.index_to_node(from_index)
27
- to_node = manager.index_to_node(to_index)
28
- distance_matrix[from_node][to_node]
29
- end
30
-
31
- transit_callback_index = routing.register_transit_callback(distance_callback)
25
+ transit_callback_index = routing.register_transit_matrix(distance_matrix)
32
26
  routing.set_arc_cost_evaluator_of_all_vehicles(transit_callback_index)
33
27
  assignment = routing.solve(first_solution_strategy: :path_cheapest_arc)
34
28
 
@@ -1,3 +1,3 @@
1
1
  module ORTools
2
- VERSION = "0.7.0"
2
+ VERSION = "0.7.3"
3
3
  end
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.0
4
+ version: 0.7.3
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-03-23 00:00:00.000000000 Z
11
+ date: 2022-07-23 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
- - MIT
78
+ - Apache-2.0
79
79
  metadata: {}
80
80
  post_install_message:
81
81
  rdoc_options: []