or-tools 0.1.2 → 0.1.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: 3d0010ac9fa141621f67cee4eb00ba077a5bafcec58c65202d477c1576ba797e
4
- data.tar.gz: '0328a9b9f0fa296df875e0756b40dc16fdbf756725d5ecc4e3bc23c600245974'
3
+ metadata.gz: 9681fd5e91b33fa5eca9ad0611011a93e6cd7f1edf12fed4e9956d914725738c
4
+ data.tar.gz: a44de2a7f7c86cd6b341abc72e2cf11dab73b3ecd9599b8b00664333af837739
5
5
  SHA512:
6
- metadata.gz: dd66a479521af3f036ed14f639f957cd6b608efed729a4e7ed1eb5fde749fd779ec471750b93ab5d48e0162116e9865c0763355fd72b610a0b74cf6747dad220
7
- data.tar.gz: 41af8a12b7aa110baae1f62029c4bde6e291ea1b870f9435ba3463a1a9e52d41dc069ef049671a95802d3942e29402f7096709c6a8fd659382a372923e03002a
6
+ metadata.gz: 68bd5337214d4ef54a6d5fd6750beb493372c9735ab6a1dc40d22cac2e08804d1fcb241f0dfb50f02980d202837405edd9c3ffcaa723a34e291a0a6d194c38a0
7
+ data.tar.gz: 0e43f33feaa383093c2b46e620bc2976e27a21441e86be52fd1310c2f2bc13aa0385dd96f9e46b88215384f843c1e94c85c6471fd501ab8ab26a7ad15d4fa252
@@ -1,3 +1,8 @@
1
+ ## 0.1.3 (2020-03-24)
2
+
3
+ - Added support for more routing problems
4
+ - Added `add_all_different` to `CpModel`
5
+
1
6
  ## 0.1.2 (2020-02-18)
2
7
 
3
8
  - Added support for scheduling
data/README.md CHANGED
@@ -28,6 +28,8 @@ Constraint Optimization
28
28
 
29
29
  - [CP-SAT Solver](#cp-sat-solver)
30
30
  - [Solving an Optimization Problem](#solving-an-optimization-problem)
31
+ - [Cryptarithmetic](#cryptarithmetic)
32
+ - [The N-queens Problem](#the-n-queens-problem)
31
33
 
32
34
  Integer Optimization
33
35
 
@@ -37,6 +39,11 @@ Routing
37
39
 
38
40
  - [Traveling Salesperson Problem (TSP)](#traveling-salesperson-problem-tsp)
39
41
  - [Vehicle Routing Problem (VRP)](#vehicle-routing-problem-vrp)
42
+ - [Capacity Constraints](#capacity-constraints)
43
+ - [Pickups and Deliveries](#pickups-and-deliveries)
44
+ - [Time Window Constraints](#time-window-constraints)
45
+ - [Resource Constraints](#resource-constraints)
46
+ - [Penalties and Dropping Visits](#penalties-and-dropping-visits)
40
47
  - [Routing Options](#routing-options)
41
48
 
42
49
  Bin Packing
@@ -60,6 +67,11 @@ Scheduling
60
67
 
61
68
  - [Employee Scheduling](#employee-scheduling)
62
69
 
70
+ Other Examples
71
+
72
+ - [Sudoku](#sudoku)
73
+ - [Set Partitioning](#set-partitioning)
74
+
63
75
  ### The Glop Linear Solver
64
76
 
65
77
  [Guide](https://developers.google.com/optimization/lp/glop)
@@ -210,6 +222,146 @@ if status == :optimal
210
222
  end
211
223
  ```
212
224
 
225
+ ### Cryptarithmetic
226
+
227
+ [Guide](https://developers.google.com/optimization/cp/cryptarithmetic)
228
+
229
+ Define the variables
230
+
231
+ ```ruby
232
+ model = ORTools::CpModel.new
233
+
234
+ base = 10
235
+
236
+ c = model.new_int_var(1, base - 1, "C")
237
+ p = model.new_int_var(0, base - 1, "P")
238
+ i = model.new_int_var(1, base - 1, "I")
239
+ s = model.new_int_var(0, base - 1, "S")
240
+ f = model.new_int_var(1, base - 1, "F")
241
+ u = model.new_int_var(0, base - 1, "U")
242
+ n = model.new_int_var(0, base - 1, "N")
243
+ t = model.new_int_var(1, base - 1, "T")
244
+ r = model.new_int_var(0, base - 1, "R")
245
+ e = model.new_int_var(0, base - 1, "E")
246
+
247
+ letters = [c, p, i, s, f, u, n, t, r, e]
248
+ ```
249
+
250
+ Define the constraints
251
+
252
+ ```ruby
253
+ model.add_all_different(letters)
254
+
255
+ model.add(c * base + p + i * base + s + f * base * base + u * base +
256
+ n == t * base * base * base + r * base * base + u * base + e)
257
+ ```
258
+
259
+ Define the solution printer
260
+
261
+ ```ruby
262
+ class VarArraySolutionPrinter < ORTools::CpSolverSolutionCallback
263
+ attr_reader :solution_count
264
+
265
+ def initialize(variables)
266
+ super()
267
+ @variables = variables
268
+ @solution_count = 0
269
+ end
270
+
271
+ def on_solution_callback
272
+ @solution_count += 1
273
+ @variables.each do |v|
274
+ print "%s=%i " % [v.name, value(v)]
275
+ end
276
+ puts
277
+ end
278
+ end
279
+ ```
280
+
281
+ Invoke the solver
282
+
283
+ ```ruby
284
+ solver = ORTools::CpSolver.new
285
+ solution_printer = VarArraySolutionPrinter.new(letters)
286
+ status = solver.search_for_all_solutions(model, solution_printer)
287
+
288
+ puts
289
+ puts "Statistics"
290
+ puts " - status : %s" % status
291
+ puts " - conflicts : %i" % solver.num_conflicts
292
+ puts " - branches : %i" % solver.num_branches
293
+ puts " - wall time : %f s" % solver.wall_time
294
+ puts " - solutions found : %i" % solution_printer.solution_count
295
+ ```
296
+
297
+ ### The N-queens Problem
298
+
299
+ [Guide](https://developers.google.com/optimization/cp/queens)
300
+
301
+ Declare the model
302
+
303
+ ```ruby
304
+ board_size = 8
305
+ model = ORTools::CpModel.new
306
+ ```
307
+
308
+ Create the variables
309
+
310
+ ```ruby
311
+ queens = board_size.times.map { |i| model.new_int_var(0, board_size - 1, "x%i" % i) }
312
+ ```
313
+
314
+ Create the constraints
315
+
316
+ ```ruby
317
+ board_size.times do |i|
318
+ diag1 = []
319
+ diag2 = []
320
+ board_size.times do |j|
321
+ q1 = model.new_int_var(0, 2 * board_size, "diag1_%i" % i)
322
+ diag1 << q1
323
+ model.add(q1 == queens[j] + j)
324
+ q2 = model.new_int_var(-board_size, board_size, "diag2_%i" % i)
325
+ diag2 << q2
326
+ model.add(q2 == queens[j] - j)
327
+ end
328
+ model.add_all_different(diag1)
329
+ model.add_all_different(diag2)
330
+ end
331
+ ```
332
+
333
+ Create a solution printer
334
+
335
+ ```ruby
336
+ class SolutionPrinter < ORTools::CpSolverSolutionCallback
337
+ attr_reader :solution_count
338
+
339
+ def initialize(variables)
340
+ super()
341
+ @variables = variables
342
+ @solution_count = 0
343
+ end
344
+
345
+ def on_solution_callback
346
+ @solution_count += 1
347
+ @variables.each do |v|
348
+ print "%s = %i " % [v.name, value(v)]
349
+ end
350
+ puts
351
+ end
352
+ end
353
+ ```
354
+
355
+ Call the solver and display the results
356
+
357
+ ```ruby
358
+ solver = ORTools::CpSolver.new
359
+ solution_printer = SolutionPrinter.new(queens)
360
+ status = solver.search_for_all_solutions(model, solution_printer)
361
+ puts
362
+ puts "Solutions found : %i" % solution_printer.solution_count
363
+ ```
364
+
213
365
  ### Mixed-Integer Programming
214
366
 
215
367
  [Guide](https://developers.google.com/optimization/mip/integer_opt)
@@ -422,6 +574,516 @@ end
422
574
  puts "Maximum of the route distances: #{max_route_distance}m"
423
575
  ```
424
576
 
577
+ ### Capacity Constraints
578
+
579
+ [Guide](https://developers.google.com/optimization/routing/cvrp)
580
+
581
+ ```ruby
582
+ data = {}
583
+ data[:distance_matrix] = [
584
+ [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],
585
+ [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],
586
+ [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],
587
+ [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],
588
+ [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],
589
+ [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],
590
+ [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],
591
+ [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],
592
+ [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],
593
+ [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],
594
+ [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],
595
+ [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],
596
+ [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],
597
+ [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],
598
+ [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],
599
+ [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],
600
+ [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0]
601
+ ]
602
+ data[:demands] = [0, 1, 1, 2, 4, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8]
603
+ data[:vehicle_capacities] = [15, 15, 15, 15]
604
+ data[:num_vehicles] = 4
605
+ data[:depot] = 0
606
+
607
+ manager = ORTools::RoutingIndexManager.new(data[:distance_matrix].size, data[:num_vehicles], data[:depot])
608
+ routing = ORTools::RoutingModel.new(manager)
609
+
610
+ distance_callback = lambda do |from_index, to_index|
611
+ from_node = manager.index_to_node(from_index)
612
+ to_node = manager.index_to_node(to_index)
613
+ data[:distance_matrix][from_node][to_node]
614
+ end
615
+
616
+ transit_callback_index = routing.register_transit_callback(distance_callback)
617
+
618
+ routing.set_arc_cost_evaluator_of_all_vehicles(transit_callback_index)
619
+
620
+ demand_callback = lambda do |from_index|
621
+ from_node = manager.index_to_node(from_index)
622
+ data[:demands][from_node]
623
+ end
624
+
625
+ demand_callback_index = routing.register_unary_transit_callback(demand_callback)
626
+ routing.add_dimension_with_vehicle_capacity(
627
+ demand_callback_index,
628
+ 0, # null capacity slack
629
+ data[:vehicle_capacities], # vehicle maximum capacities
630
+ true, # start cumul to zero
631
+ "Capacity"
632
+ )
633
+
634
+ solution = routing.solve(first_solution_strategy: :path_cheapest_arc)
635
+
636
+ total_distance = 0
637
+ total_load = 0
638
+ data[:num_vehicles].times do |vehicle_id|
639
+ index = routing.start(vehicle_id)
640
+ plan_output = String.new("Route for vehicle #{vehicle_id}:\n")
641
+ route_distance = 0
642
+ route_load = 0
643
+ while !routing.end?(index)
644
+ node_index = manager.index_to_node(index)
645
+ route_load += data[:demands][node_index]
646
+ plan_output += " #{node_index} Load(#{route_load}) -> "
647
+ previous_index = index
648
+ index = solution.value(routing.next_var(index))
649
+ route_distance += routing.arc_cost_for_vehicle(previous_index, index, vehicle_id)
650
+ end
651
+ plan_output += " #{manager.index_to_node(index)} Load(#{route_load})\n"
652
+ plan_output += "Distance of the route: #{route_distance}m\n"
653
+ plan_output += "Load of the route: #{route_load}\n\n"
654
+ puts plan_output
655
+ total_distance += route_distance
656
+ total_load += route_load
657
+ end
658
+ puts "Total distance of all routes: #{total_distance}m"
659
+ puts "Total load of all routes: #{total_load}"
660
+ ```
661
+
662
+ ### Pickups and Deliveries
663
+
664
+ [Guide](https://developers.google.com/optimization/routing/pickup_delivery)
665
+
666
+ ```ruby
667
+ data = {}
668
+ data[:distance_matrix] = [
669
+ [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],
670
+ [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],
671
+ [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],
672
+ [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],
673
+ [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],
674
+ [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],
675
+ [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],
676
+ [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],
677
+ [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],
678
+ [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],
679
+ [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],
680
+ [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],
681
+ [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],
682
+ [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],
683
+ [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],
684
+ [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],
685
+ [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0]
686
+ ]
687
+ data[:pickups_deliveries] = [
688
+ [1, 6],
689
+ [2, 10],
690
+ [4, 3],
691
+ [5, 9],
692
+ [7, 8],
693
+ [15, 11],
694
+ [13, 12],
695
+ [16, 14],
696
+ ]
697
+ data[:num_vehicles] = 4
698
+ data[:depot] = 0
699
+
700
+ manager = ORTools::RoutingIndexManager.new(data[:distance_matrix].size, data[:num_vehicles], data[:depot])
701
+
702
+ routing = ORTools::RoutingModel.new(manager)
703
+
704
+ distance_callback = lambda do |from_index, to_index|
705
+ from_node = manager.index_to_node(from_index)
706
+ to_node = manager.index_to_node(to_index)
707
+ data[:distance_matrix][from_node][to_node]
708
+ end
709
+
710
+ transit_callback_index = routing.register_transit_callback(distance_callback)
711
+ routing.set_arc_cost_evaluator_of_all_vehicles(transit_callback_index)
712
+
713
+ dimension_name = "Distance"
714
+ routing.add_dimension(
715
+ transit_callback_index,
716
+ 0, # no slack
717
+ 3000, # vehicle maximum travel distance
718
+ true, # start cumul to zero
719
+ dimension_name
720
+ )
721
+ distance_dimension = routing.mutable_dimension(dimension_name)
722
+ distance_dimension.global_span_cost_coefficient = 100
723
+
724
+ data[:pickups_deliveries].each do |request|
725
+ pickup_index = manager.node_to_index(request[0])
726
+ delivery_index = manager.node_to_index(request[1])
727
+ routing.add_pickup_and_delivery(pickup_index, delivery_index)
728
+ routing.solver.add(routing.vehicle_var(pickup_index) == routing.vehicle_var(delivery_index))
729
+ routing.solver.add(distance_dimension.cumul_var(pickup_index) <= distance_dimension.cumul_var(delivery_index))
730
+ end
731
+
732
+ solution = routing.solve(first_solution_strategy: :parallel_cheapest_insertion)
733
+
734
+ total_distance = 0
735
+ data[:num_vehicles].times do |vehicle_id|
736
+ index = routing.start(vehicle_id)
737
+ plan_output = String.new("Route for vehicle #{vehicle_id}:\n")
738
+ route_distance = 0
739
+ while !routing.end?(index)
740
+ plan_output += " #{manager.index_to_node(index)} -> "
741
+ previous_index = index
742
+ index = solution.value(routing.next_var(index))
743
+ route_distance += routing.arc_cost_for_vehicle(previous_index, index, vehicle_id)
744
+ end
745
+ plan_output += "#{manager.index_to_node(index)}\n"
746
+ plan_output += "Distance of the route: #{route_distance}m\n\n"
747
+ puts plan_output
748
+ total_distance += route_distance
749
+ end
750
+ puts "Total Distance of all routes: #{total_distance}m"
751
+ ```
752
+
753
+ ### Time Window Constraints
754
+
755
+ [Guide](https://developers.google.com/optimization/routing/vrptw)
756
+
757
+ ```ruby
758
+ data = {}
759
+ data[:time_matrix] = [
760
+ [0, 6, 9, 8, 7, 3, 6, 2, 3, 2, 6, 6, 4, 4, 5, 9, 7],
761
+ [6, 0, 8, 3, 2, 6, 8, 4, 8, 8, 13, 7, 5, 8, 12, 10, 14],
762
+ [9, 8, 0, 11, 10, 6, 3, 9, 5, 8, 4, 15, 14, 13, 9, 18, 9],
763
+ [8, 3, 11, 0, 1, 7, 10, 6, 10, 10, 14, 6, 7, 9, 14, 6, 16],
764
+ [7, 2, 10, 1, 0, 6, 9, 4, 8, 9, 13, 4, 6, 8, 12, 8, 14],
765
+ [3, 6, 6, 7, 6, 0, 2, 3, 2, 2, 7, 9, 7, 7, 6, 12, 8],
766
+ [6, 8, 3, 10, 9, 2, 0, 6, 2, 5, 4, 12, 10, 10, 6, 15, 5],
767
+ [2, 4, 9, 6, 4, 3, 6, 0, 4, 4, 8, 5, 4, 3, 7, 8, 10],
768
+ [3, 8, 5, 10, 8, 2, 2, 4, 0, 3, 4, 9, 8, 7, 3, 13, 6],
769
+ [2, 8, 8, 10, 9, 2, 5, 4, 3, 0, 4, 6, 5, 4, 3, 9, 5],
770
+ [6, 13, 4, 14, 13, 7, 4, 8, 4, 4, 0, 10, 9, 8, 4, 13, 4],
771
+ [6, 7, 15, 6, 4, 9, 12, 5, 9, 6, 10, 0, 1, 3, 7, 3, 10],
772
+ [4, 5, 14, 7, 6, 7, 10, 4, 8, 5, 9, 1, 0, 2, 6, 4, 8],
773
+ [4, 8, 13, 9, 8, 7, 10, 3, 7, 4, 8, 3, 2, 0, 4, 5, 6],
774
+ [5, 12, 9, 14, 12, 6, 6, 7, 3, 3, 4, 7, 6, 4, 0, 9, 2],
775
+ [9, 10, 18, 6, 8, 12, 15, 8, 13, 9, 13, 3, 4, 5, 9, 0, 9],
776
+ [7, 14, 9, 16, 14, 8, 5, 10, 6, 5, 4, 10, 8, 6, 2, 9, 0],
777
+ ]
778
+ data[:time_windows] = [
779
+ [0, 5], # depot
780
+ [7, 12], # 1
781
+ [10, 15], # 2
782
+ [16, 18], # 3
783
+ [10, 13], # 4
784
+ [0, 5], # 5
785
+ [5, 10], # 6
786
+ [0, 4], # 7
787
+ [5, 10], # 8
788
+ [0, 3], # 9
789
+ [10, 16], # 10
790
+ [10, 15], # 11
791
+ [0, 5], # 12
792
+ [5, 10], # 13
793
+ [7, 8], # 14
794
+ [10, 15], # 15
795
+ [11, 15], # 16
796
+ ]
797
+ data[:num_vehicles] = 4
798
+ data[:depot] = 0
799
+
800
+ manager = ORTools::RoutingIndexManager.new(data[:time_matrix].size, data[:num_vehicles], data[:depot])
801
+ routing = ORTools::RoutingModel.new(manager)
802
+
803
+ time_callback = lambda do |from_index, to_index|
804
+ from_node = manager.index_to_node(from_index)
805
+ to_node = manager.index_to_node(to_index)
806
+ data[:time_matrix][from_node][to_node]
807
+ end
808
+
809
+ transit_callback_index = routing.register_transit_callback(time_callback)
810
+ routing.set_arc_cost_evaluator_of_all_vehicles(transit_callback_index)
811
+ time = "Time"
812
+ routing.add_dimension(
813
+ transit_callback_index,
814
+ 30, # allow waiting time
815
+ 30, # maximum time per vehicle
816
+ false, # don't force start cumul to zero
817
+ time
818
+ )
819
+ time_dimension = routing.mutable_dimension(time)
820
+
821
+ data[:time_windows].each_with_index do |time_window, location_idx|
822
+ next if location_idx == 0
823
+ index = manager.node_to_index(location_idx)
824
+ time_dimension.cumul_var(index).set_range(time_window[0], time_window[1])
825
+ end
826
+
827
+ data[:num_vehicles].times do |vehicle_id|
828
+ index = routing.start(vehicle_id)
829
+ time_dimension.cumul_var(index).set_range(data[:time_windows][0][0], data[:time_windows][0][1])
830
+ end
831
+
832
+ data[:num_vehicles].times do |i|
833
+ routing.add_variable_minimized_by_finalizer(time_dimension.cumul_var(routing.start(i)))
834
+ routing.add_variable_minimized_by_finalizer(time_dimension.cumul_var(routing.end(i)))
835
+ end
836
+
837
+ solution = routing.solve(first_solution_strategy: :path_cheapest_arc)
838
+
839
+ time_dimension = routing.mutable_dimension("Time")
840
+ total_time = 0
841
+ data[:num_vehicles].times do |vehicle_id|
842
+ index = routing.start(vehicle_id)
843
+ plan_output = String.new("Route for vehicle #{vehicle_id}:\n")
844
+ while !routing.end?(index)
845
+ time_var = time_dimension.cumul_var(index)
846
+ plan_output += "#{manager.index_to_node(index)} Time(#{solution.min(time_var)},#{solution.max(time_var)}) -> "
847
+ index = solution.value(routing.next_var(index))
848
+ end
849
+ time_var = time_dimension.cumul_var(index)
850
+ plan_output += "#{manager.index_to_node(index)} Time(#{solution.min(time_var)},#{solution.max(time_var)})\n"
851
+ plan_output += "Time of the route: #{solution.min(time_var)}min\n\n"
852
+ puts plan_output
853
+ total_time += solution.min(time_var)
854
+ end
855
+ puts "Total time of all routes: #{total_time}min"
856
+ ```
857
+
858
+ ### Resource Constraints
859
+
860
+ [Guide](https://developers.google.com/optimization/routing/cvrptw_resources)
861
+
862
+ ```ruby
863
+ data = {}
864
+ data[:time_matrix] = [
865
+ [0, 6, 9, 8, 7, 3, 6, 2, 3, 2, 6, 6, 4, 4, 5, 9, 7],
866
+ [6, 0, 8, 3, 2, 6, 8, 4, 8, 8, 13, 7, 5, 8, 12, 10, 14],
867
+ [9, 8, 0, 11, 10, 6, 3, 9, 5, 8, 4, 15, 14, 13, 9, 18, 9],
868
+ [8, 3, 11, 0, 1, 7, 10, 6, 10, 10, 14, 6, 7, 9, 14, 6, 16],
869
+ [7, 2, 10, 1, 0, 6, 9, 4, 8, 9, 13, 4, 6, 8, 12, 8, 14],
870
+ [3, 6, 6, 7, 6, 0, 2, 3, 2, 2, 7, 9, 7, 7, 6, 12, 8],
871
+ [6, 8, 3, 10, 9, 2, 0, 6, 2, 5, 4, 12, 10, 10, 6, 15, 5],
872
+ [2, 4, 9, 6, 4, 3, 6, 0, 4, 4, 8, 5, 4, 3, 7, 8, 10],
873
+ [3, 8, 5, 10, 8, 2, 2, 4, 0, 3, 4, 9, 8, 7, 3, 13, 6],
874
+ [2, 8, 8, 10, 9, 2, 5, 4, 3, 0, 4, 6, 5, 4, 3, 9, 5],
875
+ [6, 13, 4, 14, 13, 7, 4, 8, 4, 4, 0, 10, 9, 8, 4, 13, 4],
876
+ [6, 7, 15, 6, 4, 9, 12, 5, 9, 6, 10, 0, 1, 3, 7, 3, 10],
877
+ [4, 5, 14, 7, 6, 7, 10, 4, 8, 5, 9, 1, 0, 2, 6, 4, 8],
878
+ [4, 8, 13, 9, 8, 7, 10, 3, 7, 4, 8, 3, 2, 0, 4, 5, 6],
879
+ [5, 12, 9, 14, 12, 6, 6, 7, 3, 3, 4, 7, 6, 4, 0, 9, 2],
880
+ [9, 10, 18, 6, 8, 12, 15, 8, 13, 9, 13, 3, 4, 5, 9, 0, 9],
881
+ [7, 14, 9, 16, 14, 8, 5, 10, 6, 5, 4, 10, 8, 6, 2, 9, 0]
882
+ ]
883
+ data[:time_windows] = [
884
+ [0, 5], # depot
885
+ [7, 12], # 1
886
+ [10, 15], # 2
887
+ [5, 14], # 3
888
+ [5, 13], # 4
889
+ [0, 5], # 5
890
+ [5, 10], # 6
891
+ [0, 10], # 7
892
+ [5, 10], # 8
893
+ [0, 5], # 9
894
+ [10, 16], # 10
895
+ [10, 15], # 11
896
+ [0, 5], # 12
897
+ [5, 10], # 13
898
+ [7, 12], # 14
899
+ [10, 15], # 15
900
+ [5, 15], # 16
901
+ ]
902
+ data[:num_vehicles] = 4
903
+ data[:vehicle_load_time] = 5
904
+ data[:vehicle_unload_time] = 5
905
+ data[:depot_capacity] = 2
906
+ data[:depot] = 0
907
+
908
+ manager = ORTools::RoutingIndexManager.new(data[:time_matrix].size, data[:num_vehicles], data[:depot])
909
+
910
+ routing = ORTools::RoutingModel.new(manager)
911
+
912
+ time_callback = lambda do |from_index, to_index|
913
+ from_node = manager.index_to_node(from_index)
914
+ to_node = manager.index_to_node(to_index)
915
+ data[:time_matrix][from_node][to_node]
916
+ end
917
+
918
+ transit_callback_index = routing.register_transit_callback(time_callback)
919
+
920
+ routing.set_arc_cost_evaluator_of_all_vehicles(transit_callback_index)
921
+
922
+ time = "Time"
923
+ routing.add_dimension(
924
+ transit_callback_index,
925
+ 60, # allow waiting time
926
+ 60, # maximum time per vehicle
927
+ false, # don't force start cumul to zero
928
+ time
929
+ )
930
+ time_dimension = routing.mutable_dimension(time)
931
+ data[:time_windows].each_with_index do |time_window, location_idx|
932
+ next if location_idx == 0
933
+ index = manager.node_to_index(location_idx)
934
+ time_dimension.cumul_var(index).set_range(time_window[0], time_window[1])
935
+ end
936
+
937
+ data[:num_vehicles].times do |vehicle_id|
938
+ index = routing.start(vehicle_id)
939
+ time_dimension.cumul_var(index).set_range(data[:time_windows][0][0], data[:time_windows][0][1])
940
+ end
941
+
942
+ solver = routing.solver
943
+ intervals = []
944
+ data[:num_vehicles].times do |i|
945
+ intervals << solver.fixed_duration_interval_var(
946
+ time_dimension.cumul_var(routing.start(i)),
947
+ data[:vehicle_load_time],
948
+ "depot_interval"
949
+ )
950
+ intervals << solver.fixed_duration_interval_var(
951
+ time_dimension.cumul_var(routing.end(i)),
952
+ data[:vehicle_unload_time],
953
+ "depot_interval"
954
+ )
955
+ end
956
+
957
+ depot_usage = [1] * intervals.size
958
+ solver.add(solver.cumulative(intervals, depot_usage, data[:depot_capacity], "depot"))
959
+
960
+ data[:num_vehicles].times do |i|
961
+ routing.add_variable_minimized_by_finalizer(time_dimension.cumul_var(routing.start(i)))
962
+ routing.add_variable_minimized_by_finalizer(time_dimension.cumul_var(routing.end(i)))
963
+ end
964
+
965
+ solution = routing.solve(first_solution_strategy: :path_cheapest_arc)
966
+
967
+ time_dimension = routing.mutable_dimension("Time")
968
+ total_time = 0
969
+ data[:num_vehicles].times do |vehicle_id|
970
+ index = routing.start(vehicle_id)
971
+ plan_output = String.new("Route for vehicle #{vehicle_id}:\n")
972
+ while !routing.end?(index)
973
+ time_var = time_dimension.cumul_var(index)
974
+ plan_output += "#{manager.index_to_node(index)} Time(#{solution.min(time_var)},#{solution.max(time_var)}) -> "
975
+ index = solution.value(routing.next_var(index))
976
+ end
977
+ time_var = time_dimension.cumul_var(index)
978
+ plan_output += "#{manager.index_to_node(index)} Time(#{solution.min(time_var)},#{solution.max(time_var)})\n"
979
+ plan_output += "Time of the route: #{solution.min(time_var)}min\n\n"
980
+ puts plan_output
981
+ total_time += solution.min(time_var)
982
+ end
983
+ puts "Total time of all routes: #{total_time}min"
984
+ ```
985
+
986
+ ### Penalties and Dropping Visits
987
+
988
+ [Guide](https://developers.google.com/optimization/routing/penalties)
989
+
990
+ ```ruby
991
+ data = {}
992
+ data[:distance_matrix] = [
993
+ [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],
994
+ [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],
995
+ [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],
996
+ [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],
997
+ [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],
998
+ [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],
999
+ [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],
1000
+ [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],
1001
+ [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],
1002
+ [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],
1003
+ [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],
1004
+ [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],
1005
+ [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],
1006
+ [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],
1007
+ [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],
1008
+ [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],
1009
+ [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0]
1010
+ ]
1011
+ data[:demands] = [0, 1, 1, 3, 6, 3, 6, 8, 8, 1, 2, 1, 2, 6, 6, 8, 8]
1012
+ data[:vehicle_capacities] = [15, 15, 15, 15]
1013
+ data[:num_vehicles] = 4
1014
+ data[:depot] = 0
1015
+
1016
+ manager = ORTools::RoutingIndexManager.new(data[:distance_matrix].size, data[:num_vehicles], data[:depot])
1017
+
1018
+ routing = ORTools::RoutingModel.new(manager)
1019
+
1020
+ distance_callback = lambda do |from_index, to_index|
1021
+ from_node = manager.index_to_node(from_index)
1022
+ to_node = manager.index_to_node(to_index)
1023
+ data[:distance_matrix][from_node][to_node]
1024
+ end
1025
+
1026
+ transit_callback_index = routing.register_transit_callback(distance_callback)
1027
+
1028
+ routing.set_arc_cost_evaluator_of_all_vehicles(transit_callback_index)
1029
+
1030
+ demand_callback = lambda do |from_index|
1031
+ from_node = manager.index_to_node(from_index)
1032
+ data[:demands][from_node]
1033
+ end
1034
+
1035
+ demand_callback_index = routing.register_unary_transit_callback(demand_callback)
1036
+ routing.add_dimension_with_vehicle_capacity(
1037
+ demand_callback_index,
1038
+ 0, # null capacity slack
1039
+ data[:vehicle_capacities], # vehicle maximum capacities
1040
+ true, # start cumul to zero
1041
+ "Capacity"
1042
+ )
1043
+
1044
+ penalty = 1000
1045
+ 1.upto(data[:distance_matrix].size - 1) do |node|
1046
+ routing.add_disjunction([manager.node_to_index(node)], penalty)
1047
+ end
1048
+
1049
+ assignment = routing.solve(first_solution_strategy: :path_cheapest_arc)
1050
+
1051
+ dropped_nodes = String.new("Dropped nodes:")
1052
+ routing.size.times do |node|
1053
+ next if routing.start?(node) || routing.end?(node)
1054
+
1055
+ if assignment.value(routing.next_var(node)) == node
1056
+ dropped_nodes += " #{manager.index_to_node(node)}"
1057
+ end
1058
+ end
1059
+ puts dropped_nodes
1060
+
1061
+ total_distance = 0
1062
+ total_load = 0
1063
+ data[:num_vehicles].times do |vehicle_id|
1064
+ index = routing.start(vehicle_id)
1065
+ plan_output = "Route for vehicle #{vehicle_id}:\n"
1066
+ route_distance = 0
1067
+ route_load = 0
1068
+ while !routing.end?(index)
1069
+ node_index = manager.index_to_node(index)
1070
+ route_load += data[:demands][node_index]
1071
+ plan_output += " #{node_index} Load(#{route_load}) -> "
1072
+ previous_index = index
1073
+ index = assignment.value(routing.next_var(index))
1074
+ route_distance += routing.arc_cost_for_vehicle(previous_index, index, vehicle_id)
1075
+ end
1076
+ plan_output += " #{manager.index_to_node(index)} Load(#{route_load})\n"
1077
+ plan_output += "Distance of the route: #{route_distance}m\n"
1078
+ plan_output += "Load of the route: #{route_load}\n\n"
1079
+ puts plan_output
1080
+ total_distance += route_distance
1081
+ total_load += route_load
1082
+ end
1083
+ puts "Total Distance of all routes: #{total_distance}m"
1084
+ puts "Total Load of all routes: #{total_load}"
1085
+ ```
1086
+
425
1087
  ### Routing Options
426
1088
 
427
1089
  [Guide](https://developers.google.com/optimization/routing/routing_options)
@@ -1085,6 +1747,133 @@ puts " - wall time : %f s" % solver.wall_time
1085
1747
  puts " - solutions found : %i" % solution_printer.solution_count
1086
1748
  ```
1087
1749
 
1750
+ ## Sudoku
1751
+
1752
+ [Example](https://github.com/google/or-tools/blob/stable/examples/python/sudoku_sat.py)
1753
+
1754
+ ```ruby
1755
+ # create the model
1756
+ model = ORTools::CpModel.new
1757
+
1758
+ cell_size = 3
1759
+ line_size = cell_size**2
1760
+ line = (0...line_size).to_a
1761
+ cell = (0...cell_size).to_a
1762
+
1763
+ initial_grid = [
1764
+ [0, 6, 0, 0, 5, 0, 0, 2, 0],
1765
+ [0, 0, 0, 3, 0, 0, 0, 9, 0],
1766
+ [7, 0, 0, 6, 0, 0, 0, 1, 0],
1767
+ [0, 0, 6, 0, 3, 0, 4, 0, 0],
1768
+ [0, 0, 4, 0, 7, 0, 1, 0, 0],
1769
+ [0, 0, 5, 0, 9, 0, 8, 0, 0],
1770
+ [0, 4, 0, 0, 0, 1, 0, 0, 6],
1771
+ [0, 3, 0, 0, 0, 8, 0, 0, 0],
1772
+ [0, 2, 0, 0, 4, 0, 0, 5, 0]
1773
+ ]
1774
+
1775
+ grid = {}
1776
+ line.each do |i|
1777
+ line.each do |j|
1778
+ grid[[i, j]] = model.new_int_var(1, line_size, "grid %i %i" % [i, j])
1779
+ end
1780
+ end
1781
+
1782
+ # all different on rows
1783
+ line.each do |i|
1784
+ model.add_all_different(line.map { |j| grid[[i, j]] })
1785
+ end
1786
+
1787
+ # all different on columns
1788
+ line.each do |j|
1789
+ model.add_all_different(line.map { |i| grid[[i, j]] })
1790
+ end
1791
+
1792
+ # all different on cells
1793
+ cell.each do |i|
1794
+ cell.each do |j|
1795
+ one_cell = []
1796
+ cell.each do |di|
1797
+ cell.each do |dj|
1798
+ one_cell << grid[[i * cell_size + di, j * cell_size + dj]]
1799
+ end
1800
+ end
1801
+ model.add_all_different(one_cell)
1802
+ end
1803
+ end
1804
+
1805
+ # initial values
1806
+ line.each do |i|
1807
+ line.each do |j|
1808
+ if initial_grid[i][j] != 0
1809
+ model.add(grid[[i, j]] == initial_grid[i][j])
1810
+ end
1811
+ end
1812
+ end
1813
+
1814
+ # solve and print solution
1815
+ solver = ORTools::CpSolver.new
1816
+ status = solver.solve(model)
1817
+ if status == :feasible
1818
+ line.each do |i|
1819
+ p line.map { |j| solver.value(grid[[i, j]]) }
1820
+ end
1821
+ end
1822
+ ```
1823
+
1824
+ ## Set Partitioning
1825
+
1826
+ [Example](https://pythonhosted.org/PuLP/CaseStudies/a_set_partitioning_problem.html)
1827
+
1828
+ ```ruby
1829
+ # A set partitioning model of a wedding seating problem
1830
+ # Authors: Stuart Mitchell 2009
1831
+
1832
+ max_tables = 5
1833
+ max_table_size = 4
1834
+ guests = %w(A B C D E F G I J K L M N O P Q R)
1835
+
1836
+ # Find the happiness of the table
1837
+ # by calculating the maximum distance between the letters
1838
+ def happiness(table)
1839
+ (table[0].ord - table[-1].ord).abs
1840
+ end
1841
+
1842
+ # create list of all possible tables
1843
+ possible_tables = []
1844
+ (1..max_table_size).each do |i|
1845
+ possible_tables += guests.combination(i).to_a
1846
+ end
1847
+
1848
+ solver = ORTools::Solver.new("Wedding Seating Model", :cbc)
1849
+
1850
+ # create a binary variable to state that a table setting is used
1851
+ x = {}
1852
+ possible_tables.each do |table|
1853
+ x[table] = solver.int_var(0, 1, "table #{table.join(", ")}")
1854
+ end
1855
+
1856
+ solver.minimize(solver.sum(possible_tables.map { |table| x[table] * happiness(table) }))
1857
+
1858
+ # specify the maximum number of tables
1859
+ solver.add(solver.sum(x.values) <= max_tables)
1860
+
1861
+ # a guest must seated at one and only one table
1862
+ guests.each do |guest|
1863
+ tables_with_guest = possible_tables.select { |table| table.include?(guest) }
1864
+ solver.add(solver.sum(tables_with_guest.map { |table| x[table] }) == 1)
1865
+ end
1866
+
1867
+ status = solver.solve
1868
+
1869
+ puts "The chosen tables are out of a total of %s:" % possible_tables.size
1870
+ possible_tables.each do |table|
1871
+ if x[table].solution_value == 1
1872
+ p table
1873
+ end
1874
+ end
1875
+ ```
1876
+
1088
1877
  ## History
1089
1878
 
1090
1879
  View the [changelog](https://github.com/ankane/or-tools/blob/master/CHANGELOG.md)
@@ -115,8 +115,11 @@ operations_research::sat::LinearExpr from_ruby<operations_research::sat::LinearE
115
115
  auto cvar = (Array) var;
116
116
  // TODO clean up
117
117
  Object o = cvar[0];
118
- if (((Rice::String) o.call("class").call("name")).str() == "ORTools::BoolVar") {
118
+ std::string type = ((String) o.call("class").call("name")).str();
119
+ if (type == "ORTools::BoolVar") {
119
120
  expr.AddVar(from_ruby<operations_research::sat::BoolVar>(cvar[0]));
121
+ } else if (type == "Integer") {
122
+ expr.AddConstant(from_ruby<int64>(cvar[0]));
120
123
  } else {
121
124
  expr.AddTerm(from_ruby<operations_research::sat::IntVar>(cvar[0]), from_ruby<int64>(cvar[1]));
122
125
  }
@@ -141,13 +144,22 @@ class Assignment {
141
144
  int64 Value(const operations_research::IntVar* const var) const {
142
145
  return self->Value(var);
143
146
  }
147
+ int64 Min(const operations_research::IntVar* const var) const {
148
+ return self->Min(var);
149
+ }
150
+ int64 Max(const operations_research::IntVar* const var) const {
151
+ return self->Max(var);
152
+ }
144
153
  };
145
154
 
146
155
  Class rb_cMPVariable;
147
156
  Class rb_cMPConstraint;
148
157
  Class rb_cMPObjective;
149
158
  Class rb_cIntVar;
159
+ Class rb_cIntervalVar;
150
160
  Class rb_cRoutingDimension;
161
+ Class rb_cConstraint;
162
+ Class rb_cSolver2;
151
163
 
152
164
  template<>
153
165
  inline
@@ -177,6 +189,13 @@ Object to_ruby<operations_research::IntVar*>(operations_research::IntVar* const
177
189
  return Rice::Data_Object<operations_research::IntVar>(x, rb_cIntVar, nullptr, nullptr);
178
190
  }
179
191
 
192
+ template<>
193
+ inline
194
+ Object to_ruby<operations_research::IntervalVar*>(operations_research::IntervalVar* const &x)
195
+ {
196
+ return Rice::Data_Object<operations_research::IntervalVar>(x, rb_cIntervalVar, nullptr, nullptr);
197
+ }
198
+
180
199
  template<>
181
200
  inline
182
201
  Object to_ruby<RoutingDimension*>(RoutingDimension* const &x)
@@ -184,6 +203,42 @@ Object to_ruby<RoutingDimension*>(RoutingDimension* const &x)
184
203
  return Rice::Data_Object<RoutingDimension>(x, rb_cRoutingDimension, nullptr, nullptr);
185
204
  }
186
205
 
206
+ template<>
207
+ inline
208
+ Object to_ruby<operations_research::Constraint*>(operations_research::Constraint* const &x)
209
+ {
210
+ return Rice::Data_Object<operations_research::Constraint>(x, rb_cConstraint, nullptr, nullptr);
211
+ }
212
+
213
+ template<>
214
+ inline
215
+ Object to_ruby<operations_research::Solver*>(operations_research::Solver* const &x)
216
+ {
217
+ return Rice::Data_Object<operations_research::Solver>(x, rb_cSolver2, nullptr, nullptr);
218
+ }
219
+
220
+ // need a wrapper class since absl::Span doesn't own
221
+ class IntVarSpan {
222
+ std::vector<operations_research::sat::IntVar> vec;
223
+ public:
224
+ IntVarSpan(Object x) {
225
+ Array a = Array(x);
226
+ for (std::size_t i = 0; i < a.size(); ++i) {
227
+ vec.push_back(from_ruby<operations_research::sat::IntVar>(a[i]));
228
+ }
229
+ }
230
+ operator absl::Span<const operations_research::sat::IntVar>() {
231
+ return absl::Span<const operations_research::sat::IntVar>(vec);
232
+ }
233
+ };
234
+
235
+ template<>
236
+ inline
237
+ IntVarSpan from_ruby<IntVarSpan>(Object x)
238
+ {
239
+ return IntVarSpan(x);
240
+ }
241
+
187
242
  extern "C"
188
243
  void Init_ext()
189
244
  {
@@ -293,6 +348,11 @@ void Init_ext()
293
348
  *[](MPVariable& self, double other) {
294
349
  LinearExpr s(&self);
295
350
  return s * other;
351
+ })
352
+ .define_method(
353
+ "inspect",
354
+ *[](MPVariable& self) {
355
+ return "#<ORTools::MPVariable @name=\"" + self.name() + "\">";
296
356
  });
297
357
 
298
358
  define_class_under<LinearExpr>(rb_mORTools, "LinearExpr")
@@ -402,7 +462,9 @@ void Init_ext()
402
462
  });
403
463
 
404
464
  // not to be confused with operations_research::IntVar
405
- define_class_under<operations_research::sat::IntVar>(rb_mORTools, "SatIntVar");
465
+ define_class_under<operations_research::sat::IntVar>(rb_mORTools, "SatIntVar")
466
+ .define_method("name", &operations_research::sat::IntVar::Name);
467
+
406
468
  define_class_under<BoolVar>(rb_mORTools, "BoolVar")
407
469
  .define_method("name", &BoolVar::Name)
408
470
  .define_method("index", &BoolVar::index)
@@ -456,6 +518,11 @@ void Init_ext()
456
518
  *[](CpModelBuilder& self, operations_research::sat::LinearExpr x, operations_research::sat::LinearExpr y) {
457
519
  self.AddLessOrEqual(x, y);
458
520
  })
521
+ .define_method(
522
+ "add_all_different",
523
+ *[](CpModelBuilder& self, IntVarSpan vars) {
524
+ self.AddAllDifferent(vars);
525
+ })
459
526
  .define_method(
460
527
  "maximize",
461
528
  *[](CpModelBuilder& self, operations_research::sat::LinearExpr expr) {
@@ -503,7 +570,12 @@ void Init_ext()
503
570
  .define_method("num_conflicts", &CpSolverResponse::num_conflicts)
504
571
  .define_method("num_branches", &CpSolverResponse::num_branches)
505
572
  .define_method("wall_time", &CpSolverResponse::wall_time)
506
- // .define_method("solution_integer_value", &SolutionIntegerValue)
573
+ .define_method(
574
+ "solution_integer_value",
575
+ *[](CpSolverResponse& self, operations_research::sat::IntVar& x) {
576
+ operations_research::sat::LinearExpr expr(x);
577
+ return SolutionIntegerValue(self, expr);
578
+ })
507
579
  .define_method("solution_boolean_value", &operations_research::sat::SolutionBooleanValue)
508
580
  .define_method(
509
581
  "status",
@@ -525,17 +597,72 @@ void Init_ext()
525
597
 
526
598
  define_class_under<RoutingIndexManager>(rb_mORTools, "RoutingIndexManager")
527
599
  .define_constructor(Constructor<RoutingIndexManager, int, int, RoutingNodeIndex>())
528
- .define_method("index_to_node", &RoutingIndexManager::IndexToNode);
600
+ .define_method("index_to_node", &RoutingIndexManager::IndexToNode)
601
+ .define_method("node_to_index", &RoutingIndexManager::NodeToIndex);
529
602
 
530
603
  define_class_under<Assignment>(rb_mORTools, "Assignment")
531
604
  .define_method("objective_value", &Assignment::ObjectiveValue)
532
- .define_method("value", &Assignment::Value);
605
+ .define_method("value", &Assignment::Value)
606
+ .define_method("min", &Assignment::Min)
607
+ .define_method("max", &Assignment::Max);
533
608
 
534
609
  // not to be confused with operations_research::sat::IntVar
535
- rb_cIntVar = define_class_under<operations_research::IntVar>(rb_mORTools, "IntVar");
610
+ rb_cIntVar = define_class_under<operations_research::IntVar>(rb_mORTools, "IntVar")
611
+ .define_method(
612
+ "set_range",
613
+ *[](operations_research::IntVar& self, int64 new_min, int64 new_max) {
614
+ self.SetRange(new_min, new_max);
615
+ });
616
+
617
+ rb_cIntervalVar = define_class_under<operations_research::IntervalVar>(rb_mORTools, "IntervalVar");
536
618
 
537
619
  rb_cRoutingDimension = define_class_under<RoutingDimension>(rb_mORTools, "RoutingDimension")
538
- .define_method("global_span_cost_coefficient=", &RoutingDimension::SetGlobalSpanCostCoefficient);
620
+ .define_method("global_span_cost_coefficient=", &RoutingDimension::SetGlobalSpanCostCoefficient)
621
+ .define_method("cumul_var", &RoutingDimension::CumulVar);
622
+
623
+ rb_cConstraint = define_class_under<operations_research::Constraint>(rb_mORTools, "Constraint");
624
+
625
+ rb_cSolver2 = define_class_under<operations_research::Solver>(rb_mORTools, "Solver2")
626
+ .define_method(
627
+ "add",
628
+ *[](operations_research::Solver& self, Object o) {
629
+ operations_research::Constraint* constraint;
630
+ if (o.respond_to("left")) {
631
+ operations_research::IntExpr* left(from_ruby<operations_research::IntVar*>(o.call("left")));
632
+ operations_research::IntExpr* right(from_ruby<operations_research::IntVar*>(o.call("right")));
633
+ auto op = o.call("operator").to_s().str();
634
+ if (op == "==") {
635
+ constraint = self.MakeEquality(left, right);
636
+ } else if (op == "<=") {
637
+ constraint = self.MakeLessOrEqual(left, right);
638
+ } else {
639
+ throw std::runtime_error("Unknown operator");
640
+ }
641
+ } else {
642
+ constraint = from_ruby<operations_research::Constraint*>(o);
643
+ }
644
+ self.AddConstraint(constraint);
645
+ })
646
+ .define_method(
647
+ "fixed_duration_interval_var",
648
+ *[](operations_research::Solver& self, operations_research::IntVar* const start_variable, int64 duration, const std::string& name) {
649
+ return self.MakeFixedDurationIntervalVar(start_variable, duration, name);
650
+ })
651
+ .define_method(
652
+ "cumulative",
653
+ *[](operations_research::Solver& self, Array rb_intervals, Array rb_demands, int64 capacity, const std::string& name) {
654
+ std::vector<operations_research::IntervalVar*> intervals;
655
+ for (std::size_t i = 0; i < rb_intervals.size(); ++i) {
656
+ intervals.push_back(from_ruby<operations_research::IntervalVar*>(rb_intervals[i]));
657
+ }
658
+
659
+ std::vector<int64> demands;
660
+ for (std::size_t i = 0; i < rb_demands.size(); ++i) {
661
+ demands.push_back(from_ruby<int64>(rb_demands[i]));
662
+ }
663
+
664
+ return self.MakeCumulative(intervals, demands, capacity, name);
665
+ });
539
666
 
540
667
  define_class_under<RoutingModel>(rb_mORTools, "RoutingModel")
541
668
  .define_constructor(Constructor<RoutingModel, RoutingIndexManager>())
@@ -548,13 +675,44 @@ void Init_ext()
548
675
  }
549
676
  );
550
677
  })
678
+ .define_method(
679
+ "register_unary_transit_callback",
680
+ *[](RoutingModel& self, Object callback) {
681
+ return self.RegisterUnaryTransitCallback(
682
+ [callback](int64 from_index) -> int64 {
683
+ return from_ruby<int64>(callback.call("call", from_index));
684
+ }
685
+ );
686
+ })
551
687
  .define_method("depot", &RoutingModel::GetDepot)
688
+ .define_method("size", &RoutingModel::Size)
689
+ .define_method("vehicle_var", &RoutingModel::VehicleVar)
552
690
  .define_method("set_arc_cost_evaluator_of_all_vehicles", &RoutingModel::SetArcCostEvaluatorOfAllVehicles)
553
691
  .define_method("set_arc_cost_evaluator_of_vehicle", &RoutingModel::SetArcCostEvaluatorOfVehicle)
554
692
  .define_method("set_fixed_cost_of_all_vehicles", &RoutingModel::SetFixedCostOfAllVehicles)
555
693
  .define_method("set_fixed_cost_of_vehicle", &RoutingModel::SetFixedCostOfVehicle)
556
694
  .define_method("fixed_cost_of_vehicle", &RoutingModel::GetFixedCostOfVehicle)
557
695
  .define_method("add_dimension", &RoutingModel::AddDimension)
696
+ .define_method(
697
+ "add_dimension_with_vehicle_capacity",
698
+ *[](RoutingModel& self, int evaluator_index, int64 slack_max, Array vc, bool fix_start_cumul_to_zero, const std::string& name) {
699
+ std::vector<int64> vehicle_capacities;
700
+ for (std::size_t i = 0; i < vc.size(); ++i) {
701
+ vehicle_capacities.push_back(from_ruby<int64>(vc[i]));
702
+ }
703
+ self.AddDimensionWithVehicleCapacity(evaluator_index, slack_max, vehicle_capacities, fix_start_cumul_to_zero, name);
704
+ })
705
+ .define_method(
706
+ "add_disjunction",
707
+ *[](RoutingModel& self, Array rb_indices, int64 penalty) {
708
+ std::vector<int64> indices;
709
+ for (std::size_t i = 0; i < rb_indices.size(); ++i) {
710
+ indices.push_back(from_ruby<int64>(rb_indices[i]));
711
+ }
712
+ self.AddDisjunction(indices, penalty);
713
+ })
714
+ .define_method("add_pickup_and_delivery", &RoutingModel::AddPickupAndDelivery)
715
+ .define_method("solver", &RoutingModel::solver)
558
716
  .define_method("start", &RoutingModel::Start)
559
717
  .define_method("end", &RoutingModel::End)
560
718
  .define_method("start?", &RoutingModel::IsStart)
@@ -565,6 +723,7 @@ void Init_ext()
565
723
  .define_method("next_var", &RoutingModel::NextVar)
566
724
  .define_method("arc_cost_for_vehicle", &RoutingModel::GetArcCostForVehicle)
567
725
  .define_method("mutable_dimension", &RoutingModel::GetMutableDimension)
726
+ .define_method("add_variable_minimized_by_finalizer", &RoutingModel::AddVariableMinimizedByFinalizer)
568
727
  .define_method(
569
728
  "solve_with_parameters",
570
729
  *[](RoutingModel& self, const RoutingSearchParameters& search_parameters) {
@@ -7,6 +7,7 @@ require "or_tools/comparison_operators"
7
7
  require "or_tools/cp_model"
8
8
  require "or_tools/cp_solver"
9
9
  require "or_tools/cp_solver_solution_callback"
10
+ require "or_tools/int_var"
10
11
  require "or_tools/knapsack_solver"
11
12
  require "or_tools/linear_expr"
12
13
  require "or_tools/routing_model"
@@ -3,7 +3,14 @@ module ORTools
3
3
  attr_writer :response
4
4
 
5
5
  def value(expr)
6
- @response.solution_boolean_value(expr)
6
+ case expr
7
+ when SatIntVar
8
+ @response.solution_integer_value(expr)
9
+ when BoolVar
10
+ @response.solution_boolean_value(expr)
11
+ else
12
+ raise "Unsupported type"
13
+ end
7
14
  end
8
15
  end
9
16
  end
Binary file
@@ -0,0 +1,5 @@
1
+ module ORTools
2
+ class IntVar
3
+ include ComparisonOperators
4
+ end
5
+ end
@@ -5,5 +5,18 @@ module ORTools
5
5
  def *(other)
6
6
  SatLinearExpr.new([[self, other]])
7
7
  end
8
+
9
+ def +(other)
10
+ SatLinearExpr.new([[self, 1], [other, 1]])
11
+ end
12
+
13
+ def -(other)
14
+ SatLinearExpr.new([[self, 1], [-other, 1]])
15
+ end
16
+
17
+ # for now
18
+ def inspect
19
+ name
20
+ end
8
21
  end
9
22
  end
@@ -16,8 +16,24 @@ module ORTools
16
16
  add(other, -1)
17
17
  end
18
18
 
19
+ def *(other)
20
+ if vars.size == 1
21
+ self.class.new([[vars[0][0], vars[0][1] * other]])
22
+ else
23
+ raise ArgumentError, "Multiplication not allowed here"
24
+ end
25
+ end
26
+
19
27
  def inspect
20
- vars.map { |v| v[0].is_a?(BoolVar) ? v[0].name : v[0].name + " * " + v[1] }.join(" + ")
28
+ vars.map do |v|
29
+ k = v[0]
30
+ k = k.respond_to?(:name) ? k.name : k.inspect
31
+ if v[1] == 1
32
+ k
33
+ else
34
+ "#{k} * #{v[1]}"
35
+ end
36
+ end.join(" + ").sub(" + -", " - ")
21
37
  end
22
38
 
23
39
  private
@@ -27,7 +43,7 @@ module ORTools
27
43
  case other
28
44
  when SatLinearExpr
29
45
  other.vars
30
- when BoolVar
46
+ when BoolVar, SatIntVar
31
47
  [[other, 1]]
32
48
  else
33
49
  raise ArgumentError, "Unsupported type"
@@ -1,3 +1,3 @@
1
1
  module ORTools
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.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.1.2
4
+ version: 0.1.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: 2020-02-18 00:00:00.000000000 Z
11
+ date: 2020-03-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rice
@@ -100,6 +100,7 @@ files:
100
100
  - lib/or_tools/cp_solver.rb
101
101
  - lib/or_tools/cp_solver_solution_callback.rb
102
102
  - lib/or_tools/ext.bundle
103
+ - lib/or_tools/int_var.rb
103
104
  - lib/or_tools/knapsack_solver.rb
104
105
  - lib/or_tools/linear_expr.rb
105
106
  - lib/or_tools/routing_model.rb