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 +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
|