or-tools 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 03544e925041f7337e6e6ba1b00b6d6c4d97353bcd7ee3785a46959a952bf09c
4
- data.tar.gz: eddbab6814900afc7ddda651da92a53aa06b0db857659ab2ccd1c1adb9770fe0
3
+ metadata.gz: d3cfb5b8fd7f6a4563b2d50a2357e4bfc72fc5ef69e2c7c1a6ad7e7736a66235
4
+ data.tar.gz: a1e2307c4a94495f9fe28e85e68171a9a3474a45ed98344250920360adffbd0f
5
5
  SHA512:
6
- metadata.gz: '041939b99da310567a099f7654658697b93e36436abf06779336ca51e0d02be94473c0474d238041285975f293734cde65c37e5db565b8f30b49f856fe519100'
7
- data.tar.gz: '09f6a2f0701acb005f356555df0d0e19befe36090d4de1b2188e7944a55836c80022eed5d4e185dc3b3b016c9c73fa3d5957e8c3cf8adf83bf4773b1090e5f0a'
6
+ metadata.gz: 8b62fd4b68b1a3b119d8da2987d263ad2fb0c4ca4d1062ef274f3dea440db62414af6188b769180a740a22898d26657569e71fefd0af9e754442061bcf481a1a
7
+ data.tar.gz: c4fbe636cd22654c867f25c954d1652540b54239a9427a7795033027a60ca7538b864e74c0134296040f57bf945750dbc80384965e7d449f9a5fb1474163ce9d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 0.7.1 (2022-05-27)
2
+
3
+ - Added support for time limit for `Solver`
4
+ - Added `enable_output` and `suppress_output` to `Solver`
5
+ - Improved `new` method for `Solver`
6
+ - Fixed error with offset with `Solver`
7
+ - Fixed segfault with `CpSolver`
8
+
1
9
  ## 0.7.0 (2022-03-23)
2
10
 
3
11
  - Updated OR-Tools to 9.3
data/README.md CHANGED
@@ -14,13 +14,73 @@ gem "or-tools"
14
14
 
15
15
  Installation can take a few minutes as OR-Tools downloads and builds. For Mac ARM, also follow [these instructions](#additional-instructions).
16
16
 
17
- ## 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
  }
@@ -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)
@@ -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
@@ -1,3 +1,3 @@
1
1
  module ORTools
2
- VERSION = "0.7.0"
2
+ VERSION = "0.7.1"
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.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-23 00:00:00.000000000 Z
11
+ date: 2022-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rice
@@ -75,7 +75,7 @@ files:
75
75
  - lib/or_tools/version.rb
76
76
  homepage: https://github.com/ankane/or-tools-ruby
77
77
  licenses:
78
- - MIT
78
+ - Apache-2.0
79
79
  metadata: {}
80
80
  post_install_message:
81
81
  rdoc_options: []