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 +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +789 -0
- data/ext/or-tools/ext.cpp +166 -7
- data/lib/or-tools.rb +1 -0
- data/lib/or_tools/cp_solver_solution_callback.rb +8 -1
- data/lib/or_tools/ext.bundle +0 -0
- data/lib/or_tools/int_var.rb +5 -0
- data/lib/or_tools/sat_int_var.rb +13 -0
- data/lib/or_tools/sat_linear_expr.rb +18 -2
- data/lib/or_tools/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9681fd5e91b33fa5eca9ad0611011a93e6cd7f1edf12fed4e9956d914725738c
|
4
|
+
data.tar.gz: a44de2a7f7c86cd6b341abc72e2cf11dab73b3ecd9599b8b00664333af837739
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68bd5337214d4ef54a6d5fd6750beb493372c9735ab6a1dc40d22cac2e08804d1fcb241f0dfb50f02980d202837405edd9c3ffcaa723a34e291a0a6d194c38a0
|
7
|
+
data.tar.gz: 0e43f33feaa383093c2b46e620bc2976e27a21441e86be52fd1310c2f2bc13aa0385dd96f9e46b88215384f843c1e94c85c6471fd501ab8ab26a7ad15d4fa252
|
data/CHANGELOG.md
CHANGED
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)
|
data/ext/or-tools/ext.cpp
CHANGED
@@ -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
|
-
|
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
|
-
|
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) {
|
data/lib/or-tools.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/or_tools/ext.bundle
CHANGED
Binary file
|
data/lib/or_tools/sat_int_var.rb
CHANGED
@@ -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
|
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"
|
data/lib/or_tools/version.rb
CHANGED
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.
|
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-
|
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
|