or-tools 0.1.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,132 @@
1
+ module ORTools
2
+ class Sudoku
3
+ attr_reader :solution
4
+
5
+ def initialize(initial_grid, x: false, magic_square: false, anti_knight: false, anti_king: false, non_consecutive: false)
6
+ raise ArgumentError, "Grid must be 9x9" unless initial_grid.size == 9 && initial_grid.all? { |r| r.size == 9 }
7
+ raise ArgumentError, "Grid must contain values between 0 and 9" unless initial_grid.flatten(1).all? { |v| (0..9).include?(v) }
8
+
9
+ model = ORTools::CpModel.new
10
+
11
+ cell_size = 3
12
+ line_size = cell_size**2
13
+ line = (0...line_size).to_a
14
+ cell = (0...cell_size).to_a
15
+
16
+ grid = {}
17
+ line.each do |i|
18
+ line.each do |j|
19
+ grid[[i, j]] = model.new_int_var(1, line_size, "grid %i %i" % [i, j])
20
+ end
21
+ end
22
+
23
+ line.each do |i|
24
+ model.add_all_different(line.map { |j| grid[[i, j]] })
25
+ end
26
+
27
+ line.each do |j|
28
+ model.add_all_different(line.map { |i| grid[[i, j]] })
29
+ end
30
+
31
+ cell.each do |i|
32
+ cell.each do |j|
33
+ one_cell = []
34
+ cell.each do |di|
35
+ cell.each do |dj|
36
+ one_cell << grid[[i * cell_size + di, j * cell_size + dj]]
37
+ end
38
+ end
39
+ model.add_all_different(one_cell)
40
+ end
41
+ end
42
+
43
+ line.each do |i|
44
+ line.each do |j|
45
+ if initial_grid[i][j] != 0
46
+ model.add(grid[[i, j]] == initial_grid[i][j])
47
+ end
48
+ end
49
+ end
50
+
51
+ if x
52
+ model.add_all_different(9.times.map { |i| grid[[i, i]] })
53
+ model.add_all_different(9.times.map { |i| grid[[i, 8 - i]] })
54
+ end
55
+
56
+ if magic_square
57
+ magic_sums = []
58
+ 3.times do |i|
59
+ magic_sums << model.sum(3.times.map { |j| grid[[3 + i, 3 + j]] })
60
+ magic_sums << model.sum(3.times.map { |j| grid[[3 + j, 3 + i]] })
61
+ end
62
+
63
+ magic_sums << model.sum(3.times.map { |i| grid[[3 + i, 3 + i]] })
64
+ magic_sums << model.sum(3.times.map { |i| grid[[3 + i, 5 - i]] })
65
+
66
+ first_sum = magic_sums.shift
67
+ magic_sums.each do |magic_sum|
68
+ model.add(magic_sum == first_sum)
69
+ end
70
+ end
71
+
72
+ if anti_knight
73
+ # add anti-knights rule
74
+ # for each square, add squares that cannot be feasible
75
+ moves = [[1, 2], [2, 1], [2, -1], [1, -2], [-1, -2], [-2, -1], [-2, 1], [-1, 2]]
76
+ 9.times do |i|
77
+ 9.times do |j|
78
+ moves.each do |mi, mj|
79
+ square = grid[[i + mi, j + mj]]
80
+ if square
81
+ model.add(grid[[i, j]] != square)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ if anti_king
89
+ # add anti-king rule
90
+ # for each square, add squares that cannot be feasible
91
+ moves = [[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]]
92
+ 9.times do |i|
93
+ 9.times do |j|
94
+ moves.each do |mi, mj|
95
+ square = grid[[i + mi, j + mj]]
96
+ if square
97
+ model.add(grid[[i, j]] != square)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ if non_consecutive
105
+ # add non-consecutive rule
106
+ # for each square, add squares that cannot be feasible
107
+ moves = [[1, 0], [0, 1], [-1, 0], [0, -1]]
108
+ 9.times do |i|
109
+ 9.times do |j|
110
+ moves.each do |mi, mj|
111
+ square = grid[[i + mi, j + mj]]
112
+ if square
113
+ model.add(grid[[i, j]] + 1 != square)
114
+ model.add(grid[[i, j]] - 1 != square)
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ solver = ORTools::CpSolver.new
122
+ status = solver.solve(model)
123
+ raise Error, "No solution found" unless [:feasible, :optimal].include?(status)
124
+
125
+ solution = []
126
+ line.each do |i|
127
+ solution << line.map { |j| solver.value(grid[[i, j]]) }
128
+ end
129
+ @solution = solution
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,60 @@
1
+ module ORTools
2
+ class TSP
3
+ attr_reader :route, :route_indexes, :distances, :total_distance
4
+
5
+ DISTANCE_SCALE = 1000
6
+ DEGREES_TO_RADIANS = Math::PI / 180
7
+
8
+ def initialize(locations)
9
+ raise ArgumentError, "Locations must have latitude and longitude" unless locations.all? { |l| l[:latitude] && l[:longitude] }
10
+ raise ArgumentError, "Latitude must be between -90 and 90" unless locations.all? { |l| l[:latitude] >= -90 && l[:latitude] <= 90 }
11
+ raise ArgumentError, "Longitude must be between -180 and 180" unless locations.all? { |l| l[:longitude] >= -180 && l[:longitude] <= 180 }
12
+ raise ArgumentError, "Must be at least two locations" unless locations.size >= 2
13
+
14
+ distance_matrix =
15
+ locations.map do |from|
16
+ locations.map do |to|
17
+ # must be integers
18
+ (distance(from, to) * DISTANCE_SCALE).to_i
19
+ end
20
+ end
21
+
22
+ manager = ORTools::RoutingIndexManager.new(locations.size, 1, 0)
23
+ routing = ORTools::RoutingModel.new(manager)
24
+
25
+ distance_callback = lambda do |from_index, to_index|
26
+ from_node = manager.index_to_node(from_index)
27
+ to_node = manager.index_to_node(to_index)
28
+ distance_matrix[from_node][to_node]
29
+ end
30
+
31
+ transit_callback_index = routing.register_transit_callback(distance_callback)
32
+ routing.set_arc_cost_evaluator_of_all_vehicles(transit_callback_index)
33
+ assignment = routing.solve(first_solution_strategy: :path_cheapest_arc)
34
+
35
+ @route_indexes = []
36
+ @distances = []
37
+
38
+ index = routing.start(0)
39
+ while !routing.end?(index)
40
+ @route_indexes << manager.index_to_node(index)
41
+ previous_index = index
42
+ index = assignment.value(routing.next_var(index))
43
+ @distances << routing.arc_cost_for_vehicle(previous_index, index, 0) / DISTANCE_SCALE.to_f
44
+ end
45
+ @route_indexes << manager.index_to_node(index)
46
+ @route = locations.values_at(*@route_indexes)
47
+ @total_distance = @distances.sum
48
+ end
49
+
50
+ private
51
+
52
+ def distance(from, to)
53
+ from_lat = from[:latitude] * DEGREES_TO_RADIANS
54
+ from_lng = from[:longitude] * DEGREES_TO_RADIANS
55
+ to_lat = to[:latitude] * DEGREES_TO_RADIANS
56
+ to_lng = to[:longitude] * DEGREES_TO_RADIANS
57
+ 2 * 6371 * Math.asin(Math.sqrt(Math.sin((to_lat - from_lat) / 2.0)**2 + Math.cos(from_lat) * Math.cos(to_lat) * Math.sin((from_lng - to_lng) / 2.0)**2))
58
+ end
59
+ end
60
+ end
@@ -1,3 +1,3 @@
1
1
  module ORTools
2
- VERSION = "0.1.2"
2
+ VERSION = "0.3.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: or-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-18 00:00:00.000000000 Z
11
+ date: 2020-07-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rice
@@ -93,19 +93,26 @@ files:
93
93
  - README.md
94
94
  - ext/or-tools/ext.cpp
95
95
  - ext/or-tools/extconf.rb
96
+ - ext/or-tools/vendor.rb
96
97
  - lib/or-tools.rb
98
+ - lib/or_tools/basic_scheduler.rb
99
+ - lib/or_tools/bool_var.rb
97
100
  - lib/or_tools/comparison.rb
98
101
  - lib/or_tools/comparison_operators.rb
99
102
  - lib/or_tools/cp_model.rb
100
103
  - lib/or_tools/cp_solver.rb
101
104
  - lib/or_tools/cp_solver_solution_callback.rb
102
105
  - lib/or_tools/ext.bundle
106
+ - lib/or_tools/int_var.rb
103
107
  - lib/or_tools/knapsack_solver.rb
104
108
  - lib/or_tools/linear_expr.rb
105
109
  - lib/or_tools/routing_model.rb
106
110
  - lib/or_tools/sat_int_var.rb
107
111
  - lib/or_tools/sat_linear_expr.rb
112
+ - lib/or_tools/seating.rb
108
113
  - lib/or_tools/solver.rb
114
+ - lib/or_tools/sudoku.rb
115
+ - lib/or_tools/tsp.rb
109
116
  - lib/or_tools/version.rb
110
117
  homepage: https://github.com/ankane/or-tools
111
118
  licenses: