or-tools 0.7.0 → 0.7.3

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 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: []