or-tools 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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