or-tools 0.6.2 → 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: c0e0f455741da72e3a215287df4ef1858893fb31d0b4be40db6ef9ab025e339c
4
- data.tar.gz: 9291933eee08f800f48f3d83ca4f2d0fc71efb0a328134ba9667ca2ac8efb185
3
+ metadata.gz: d3cfb5b8fd7f6a4563b2d50a2357e4bfc72fc5ef69e2c7c1a6ad7e7736a66235
4
+ data.tar.gz: a1e2307c4a94495f9fe28e85e68171a9a3474a45ed98344250920360adffbd0f
5
5
  SHA512:
6
- metadata.gz: 61ccc7c5f3a6c3cdf1e3dffb1ee6572d0b8cde09d4684d50353efd6f2efcb93ca4ba940343f994a3bf65992e518331bd62a02ef74ecc52f2427e12350ea7a430
7
- data.tar.gz: 589423f5b52c872b453569400ff1fd60bb1fcd3b0035e70aadbb47eedefec9b7f4187a5442893e50608529f836d2009bd77333aae3f64ce1b9d700df6fe020e7
6
+ metadata.gz: 8b62fd4b68b1a3b119d8da2987d263ad2fb0c4ca4d1062ef274f3dea440db62414af6188b769180a740a22898d26657569e71fefd0af9e754442061bcf481a1a
7
+ data.tar.gz: c4fbe636cd22654c867f25c954d1652540b54239a9427a7795033027a60ca7538b864e74c0134296040f57bf945750dbc80384965e7d449f9a5fb1474163ce9d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
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
+
9
+ ## 0.7.0 (2022-03-23)
10
+
11
+ - Updated OR-Tools to 9.3
12
+ - Removed `add_lin_min_equality` (use `add_min_equality` instead)
13
+ - Removed `add_lin_max_equality` (use `add_max_equality` instead)
14
+ - Dropped support for Debian 10
15
+
16
+ ## 0.6.3 (2022-03-13)
17
+
18
+ - Reduced gem size
19
+
1
20
  ## 0.6.2 (2022-02-09)
2
21
 
3
22
  - Fixed segfaults with `Solver`
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
@@ -57,11 +57,11 @@ namespace Rice::detail
57
57
  auto coeff = From_Ruby<int64_t>().convert(cvar[1].value());
58
58
 
59
59
  if (var.is_a(rb_cBoolVar)) {
60
- expr.AddTerm(From_Ruby<BoolVar>().convert(var.value()), coeff);
60
+ expr += From_Ruby<BoolVar>().convert(var.value()) * coeff;
61
61
  } else if (var.is_a(rb_cInteger)) {
62
- expr.AddConstant(From_Ruby<int64_t>().convert(var.value()) * coeff);
62
+ expr += From_Ruby<int64_t>().convert(var.value()) * coeff;
63
63
  } else {
64
- expr.AddTerm(From_Ruby<IntVar>().convert(var.value()), coeff);
64
+ expr += From_Ruby<IntVar>().convert(var.value()) * coeff;
65
65
  }
66
66
  }
67
67
  } else {
@@ -253,42 +253,32 @@ void init_constraint(Rice::Module& m) {
253
253
  })
254
254
  .define_method(
255
255
  "add_min_equality",
256
- [](CpModelBuilder& self, IntVar target, std::vector<IntVar> vars) {
256
+ [](CpModelBuilder& self, LinearExpr target, std::vector<LinearExpr> vars) {
257
257
  return self.AddMinEquality(target, vars);
258
258
  })
259
- .define_method(
260
- "add_lin_min_equality",
261
- [](CpModelBuilder& self, LinearExpr target, std::vector<LinearExpr> exprs) {
262
- return self.AddLinMinEquality(target, exprs);
263
- })
264
259
  .define_method(
265
260
  "add_max_equality",
266
- [](CpModelBuilder& self, IntVar target, std::vector<IntVar> vars) {
261
+ [](CpModelBuilder& self, LinearExpr target, std::vector<LinearExpr> vars) {
267
262
  return self.AddMaxEquality(target, vars);
268
263
  })
269
- .define_method(
270
- "add_lin_max_equality",
271
- [](CpModelBuilder& self, LinearExpr target, std::vector<LinearExpr> exprs) {
272
- return self.AddLinMaxEquality(target, exprs);
273
- })
274
264
  .define_method(
275
265
  "add_division_equality",
276
- [](CpModelBuilder& self, IntVar target, IntVar numerator, IntVar denominator) {
266
+ [](CpModelBuilder& self, LinearExpr target, LinearExpr numerator, LinearExpr denominator) {
277
267
  return self.AddDivisionEquality(target, numerator, denominator);
278
268
  })
279
269
  .define_method(
280
270
  "add_abs_equality",
281
- [](CpModelBuilder& self, IntVar target, IntVar var) {
271
+ [](CpModelBuilder& self, LinearExpr target, LinearExpr var) {
282
272
  return self.AddAbsEquality(target, var);
283
273
  })
284
274
  .define_method(
285
275
  "add_modulo_equality",
286
- [](CpModelBuilder& self, IntVar target, IntVar var, IntVar mod) {
276
+ [](CpModelBuilder& self, LinearExpr target, LinearExpr var, LinearExpr mod) {
287
277
  return self.AddModuloEquality(target, var, mod);
288
278
  })
289
279
  .define_method(
290
280
  "add_multiplication_equality",
291
- [](CpModelBuilder& self, IntVar target, std::vector<IntVar> vars) {
281
+ [](CpModelBuilder& self, LinearExpr target, std::vector<LinearExpr> vars) {
292
282
  return self.AddMultiplicationEquality(target, vars);
293
283
  })
294
284
  .define_method(
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
  }
@@ -25,12 +25,7 @@ end
25
25
 
26
26
  $INCFLAGS << " -I#{inc}"
27
27
 
28
- $LDFLAGS << " -Wl,-rpath,#{rpath}"
29
- $LDFLAGS << " -L#{lib}"
28
+ $LDFLAGS.prepend("-Wl,-rpath,#{rpath} -L#{lib} ")
30
29
  raise "OR-Tools not found" unless have_library("ortools")
31
30
 
32
- Dir["#{lib}/libabsl_*.a"].each do |lib|
33
- $LDFLAGS << " #{lib}"
34
- end
35
-
36
31
  create_makefile("or_tools/ext")
@@ -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)
@@ -4,7 +4,7 @@ require "fileutils"
4
4
  require "net/http"
5
5
  require "tmpdir"
6
6
 
7
- version = "9.2.9972"
7
+ version = "9.3.10497"
8
8
 
9
9
  if RbConfig::CONFIG["host_os"] =~ /darwin/i
10
10
  if RbConfig::CONFIG["host_cpu"] =~ /arm|aarch64/i
@@ -17,8 +17,8 @@ if RbConfig::CONFIG["host_os"] =~ /darwin/i
17
17
 
18
18
  MSG
19
19
  else
20
- filename = "or-tools_MacOsX-12.0.1_v#{version}.tar.gz"
21
- checksum = "796791a8ef84507d62e193e647cccb1c7725dae4f1474476e1777fe4a44ee3e0"
20
+ filename = "or-tools_MacOsX-12.2.1_v#{version}.tar.gz"
21
+ checksum = "33941702c59983897935eef06d91aca6c89ed9a8f5f4de3a9dfe489e97d7ca8c"
22
22
  end
23
23
  else
24
24
  # try /etc/os-release with fallback to /usr/lib/os-release
@@ -32,23 +32,21 @@ else
32
32
  os_version = os_info["VERSION_ID"]
33
33
 
34
34
  if os == "ubuntu" && os_version == "20.04"
35
- filename = "or-tools_amd64_ubuntu-20.04_v#{version}.tar.gz"
36
- checksum = "985e3036eaecacfc8a0258ec2ebef429240491577d4e0896d68fc076e65451ec"
35
+ # TODO remove sub in v9.4
36
+ filename = "or-tools_amd64_ubuntu-20.04_v#{version.sub(".10497", ".10502")}.tar.gz"
37
+ checksum = "3d1979967a2c9358b5bc956f2e6b608b00e89e13c71d48d075475ce4138b6d1c"
37
38
  elsif os == "ubuntu" && os_version == "18.04"
38
39
  filename = "or-tools_amd64_ubuntu-18.04_v#{version}.tar.gz"
39
- checksum = "e36406c4fe8c111e1ace0ede9d0787ff0e98f11afd7db9cc074adfd0f55628a6"
40
+ checksum = "6ba5cc153417267e8f8e15f8b6390b17f22de07bacc61f3740a4172ccd56c274"
40
41
  elsif os == "debian" && os_version == "11"
41
42
  filename = "or-tools_amd64_debian-11_v#{version}.tar.gz"
42
- checksum = "bd49ee916213b2140ab255414d35a28f19dff7caf87632309753d3fc553f85dd"
43
- elsif os == "debian" && os_version == "10"
44
- filename = "or-tools_amd64_debian-10_v#{version}.tar.gz"
45
- checksum = "b152fee584f0c8228fe2ff21b74c789870ff9b7064e42ca26305c6b5653f0064"
43
+ checksum = "db0636bab909eabf06a7004f7572dca6fa152f3823c1365b0b7428405bf250e6"
46
44
  elsif os == "centos" && os_version == "8"
47
45
  filename = "or-tools_amd64_centos-8_v#{version}.tar.gz"
48
- checksum = "66ed4bb800acf92c672f7e68acdf4ea27bbfdb17bbddc02f8326cd55a97305f6"
46
+ checksum = "e5649069fd7a3e8228cc18b91e265a90562c5d03a0c962b0346911aada0aedc9"
49
47
  elsif os == "centos" && os_version == "7"
50
48
  filename = "or-tools_amd64_centos-7_v#{version}.tar.gz"
51
- checksum = "4a5c1b1639a2828cd7e1ba82a574ef37876557b59e8aab8b81811bb750d53035"
49
+ checksum = "3bffdec8c09fc1345dcbd6a553437e2894014093fafb53e50adc7d4d776bb08b"
52
50
  else
53
51
  platform =
54
52
  if Gem.win_platform?
@@ -121,12 +119,37 @@ end
121
119
  download_checksum = Digest::SHA256.file(download_path).hexdigest
122
120
  raise "Bad checksum: #{download_checksum}" if download_checksum != checksum
123
121
 
124
- # extract - can't use Gem::Package#extract_tar_gz from RubyGems
125
- # since it limits filenames to 100 characters (doesn't support UStar format)
126
122
  path = File.expand_path("../../tmp/or-tools", __dir__)
127
123
  FileUtils.mkdir_p(path)
128
- tar_args = Gem.win_platform? ? ["--force-local"] : []
129
- system "tar", "zxf", download_path, "-C", path, "--strip-components=1", *tar_args
124
+
125
+ # extract - can't use Gem::Package#extract_tar_gz from RubyGems
126
+ # since it limits filenames to 100 characters (doesn't support UStar format)
127
+ # for space, only keep licenses, include, and shared library
128
+ Dir.mktmpdir do |extract_path|
129
+ tar_args = Gem.win_platform? ? ["--force-local"] : []
130
+ system "tar", "zxf", download_path, "-C", extract_path, "--strip-components=1", *tar_args
131
+
132
+ # licenses
133
+ license_files = Dir.glob("**/*{LICENSE,LICENCE,NOTICE,COPYING,license,licence,notice,copying}*", base: extract_path)
134
+ raise "License not found" unless license_files.any?
135
+ license_files.each do |file|
136
+ FileUtils.mkdir_p(File.join(path, File.dirname(file)))
137
+ FileUtils.cp(File.join(extract_path, file), File.join(path, file))
138
+ end
139
+
140
+ # include
141
+ FileUtils.cp_r(File.join(extract_path, "include"), File.join(path, "include"))
142
+
143
+ # shared library
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"))
151
+ end
152
+ end
130
153
 
131
154
  # export
132
155
  $vendor_path = path
@@ -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.6.2"
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.6.2
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-02-09 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: []
@@ -92,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
92
92
  - !ruby/object:Gem::Version
93
93
  version: '0'
94
94
  requirements: []
95
- rubygems_version: 3.3.3
95
+ rubygems_version: 3.3.7
96
96
  signing_key:
97
97
  specification_version: 4
98
98
  summary: Operations research tools for Ruby