z3 0.0.20180629 → 0.0.20220203
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -4
- data/Rakefile +15 -8
- data/examples/abc_path +187 -0
- data/examples/abc_path-1.txt +7 -0
- data/examples/algebra_problems +12 -12
- data/examples/aquarium +133 -0
- data/examples/aquarium-1.txt +11 -0
- data/examples/bridges +2 -2
- data/examples/cats_organized_neatly +133 -0
- data/examples/cats_organized_neatly-10.txt +15 -0
- data/examples/cats_organized_neatly-3.txt +8 -0
- data/examples/cats_organized_neatly-48.txt +32 -0
- data/examples/circuit_problems +4 -4
- data/examples/clogic_puzzle +2 -2
- data/examples/color_nonogram +150 -0
- data/examples/color_nonogram-1.txt +23 -0
- data/examples/crossflip +2 -4
- data/examples/dominion +153 -0
- data/examples/dominion-1.txt +8 -0
- data/examples/dominosa +133 -0
- data/examples/dominosa-1.txt +8 -0
- data/examples/eulero +99 -0
- data/examples/eulero-1.txt +5 -0
- data/examples/four_hackers_puzzle +2 -2
- data/examples/futoshiki +128 -0
- data/examples/futoshiki-1.txt +17 -0
- data/examples/kakurasu +73 -0
- data/examples/kakurasu-1.txt +2 -0
- data/examples/kakuro +2 -2
- data/examples/killer_sudoku +88 -0
- data/examples/killer_sudoku-1.txt +17 -0
- data/examples/killer_sudoku-2.txt +53 -0
- data/examples/kinematics_problems +20 -20
- data/examples/knights_puzzle +2 -2
- data/examples/kropki +100 -0
- data/examples/kropki-1.txt +13 -0
- data/examples/letter_connections +2 -2
- data/examples/light_up +2 -2
- data/examples/minisudoku +2 -2
- data/examples/miracle_sudoku +135 -0
- data/examples/miracle_sudoku-1.txt +9 -0
- data/examples/mortal_coil_puzzle +2 -2
- data/examples/nanro +245 -0
- data/examples/nanro-1.txt +8 -0
- data/examples/nine_clocks +106 -0
- data/examples/nonogram +2 -2
- data/examples/pyramid_nonogram +2 -2
- data/examples/regexp_crossword_solver +2 -2
- data/examples/regexp_solver +2 -2
- data/examples/renzoku +124 -0
- data/examples/renzoku-1.txt +17 -0
- data/examples/sandwich_sudoku +101 -0
- data/examples/sandwich_sudoku-1.txt +10 -0
- data/examples/selfref +2 -2
- data/examples/simple_regexp_parser.rb +58 -56
- data/examples/skyscrapers +118 -0
- data/examples/skyscrapers-1.txt +6 -0
- data/examples/skyscrapers-2.txt +11 -0
- data/examples/star_battle +134 -0
- data/examples/star_battle-1.txt +10 -0
- data/examples/stitches +180 -0
- data/examples/stitches-1.txt +11 -0
- data/examples/sudoku +2 -2
- data/examples/suguru +199 -0
- data/examples/suguru-1.txt +17 -0
- data/examples/verbal_arithmetic +2 -2
- data/examples/yajilin +268 -0
- data/examples/yajilin-1.txt +10 -0
- data/lib/z3/ast.rb +8 -0
- data/lib/z3/expr/expr.rb +16 -15
- data/lib/z3/low_level.rb +6 -2
- data/lib/z3/low_level_auto.rb +180 -36
- data/lib/z3/optimize.rb +4 -4
- data/lib/z3/very_low_level.rb +8 -5
- data/lib/z3/very_low_level_auto.rb +45 -9
- data/spec/expr_spec.rb +62 -0
- data/spec/integration/abc_path_spec.rb +21 -0
- data/spec/integration/aquarium_spec.rb +27 -0
- data/spec/integration/cats_organized_neatly_spec.rb +14 -0
- data/spec/integration/color_nonogram_spec.rb +28 -0
- data/spec/integration/dominion_spec.rb +14 -0
- data/spec/integration/dominosa_spec.rb +21 -0
- data/spec/integration/eulero_spec.rb +11 -0
- data/spec/integration/futoshiki_spec.rb +23 -0
- data/spec/integration/kakurasu_spec.rb +18 -0
- data/spec/integration/killer_sudoku_spec.rb +10 -0
- data/spec/integration/knights_puzzle_spec.rb +11 -11
- data/spec/integration/kropki_spec.rb +19 -0
- data/spec/integration/miracle_sudoku_spec.rb +15 -0
- data/spec/integration/mortal_coil_puzzle_spec.rb +8 -6
- data/spec/integration/nanro_spec.rb +39 -0
- data/spec/integration/nine_clocks_spec.rb +30 -0
- data/spec/integration/oneofus_spec.rb +7 -15
- data/spec/integration/regexp_crossword_solver_spec.rb +1 -1
- data/spec/integration/renzoku_spec.rb +23 -0
- data/spec/integration/sandwich_sudoku_spec.rb +15 -0
- data/spec/integration/skyscraper_spec.rb +10 -0
- data/spec/integration/star_battle_spec.rb +27 -0
- data/spec/integration/stitches_spec.rb +25 -0
- data/spec/integration/suguru_spec.rb +23 -0
- data/spec/integration/yajilin_spec.rb +25 -0
- data/spec/optimize_spec.rb +6 -4
- data/spec/set_expr_spec.rb +14 -8
- data/spec/solver_spec.rb +4 -5
- data/spec/spec_helper.rb +15 -0
- metadata +105 -25
@@ -0,0 +1,135 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require_relative "../lib/z3"
|
5
|
+
|
6
|
+
class MiracleSudokuSolver
|
7
|
+
def initialize(path)
|
8
|
+
data = Pathname(path).read
|
9
|
+
data = data.strip.split("\n").map do |line|
|
10
|
+
line.split.map{|c| c == "." ? nil : c.to_i}
|
11
|
+
end
|
12
|
+
@data = data
|
13
|
+
raise "Bad size" unless @data.size == 9
|
14
|
+
raise "Bad size" unless @data.all?{|row| row.size == 9}
|
15
|
+
@solver = Z3::Solver.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def king_moves
|
19
|
+
[
|
20
|
+
[-1,-1],
|
21
|
+
[-1, 0],
|
22
|
+
[-1,+1],
|
23
|
+
[ 0,-1],
|
24
|
+
[ 0,+1],
|
25
|
+
[+1,-1],
|
26
|
+
[+1, 0],
|
27
|
+
[+1,+1],
|
28
|
+
]
|
29
|
+
end
|
30
|
+
|
31
|
+
def knight_moves
|
32
|
+
[
|
33
|
+
[-2, -1],
|
34
|
+
[-2, +1],
|
35
|
+
[-1, -2],
|
36
|
+
[-1, +2],
|
37
|
+
[+1, -2],
|
38
|
+
[+1, +2],
|
39
|
+
[+2, -1],
|
40
|
+
[+2, +1],
|
41
|
+
]
|
42
|
+
end
|
43
|
+
|
44
|
+
def orthogonal_moves
|
45
|
+
[
|
46
|
+
[-1, 0],
|
47
|
+
[+1, 0],
|
48
|
+
[ 0, -1],
|
49
|
+
[ 0, +1],
|
50
|
+
]
|
51
|
+
end
|
52
|
+
|
53
|
+
def call
|
54
|
+
@cells = (0..8).map do |y|
|
55
|
+
(0..8).map do |x|
|
56
|
+
cell_var(data_at(x,y), x, y)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
@cells.each do |row|
|
61
|
+
@solver.assert Z3.Distinct(*row)
|
62
|
+
end
|
63
|
+
@cells.transpose.each do |column|
|
64
|
+
@solver.assert Z3.Distinct(*column)
|
65
|
+
end
|
66
|
+
@cells.each_slice(3) do |rows|
|
67
|
+
rows.transpose.each_slice(3) do |square|
|
68
|
+
@solver.assert Z3.Distinct(*square.flatten)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
each_coord do |x,y|
|
73
|
+
[*king_moves, *knight_moves].each do |dx, dy|
|
74
|
+
x2 = x + dx
|
75
|
+
y2 = y + dy
|
76
|
+
next unless in_bounds?(x2, y2)
|
77
|
+
@solver.assert var_at(x,y) != var_at(x2,y2)
|
78
|
+
end
|
79
|
+
|
80
|
+
orthogonal_moves.each do |dx, dy|
|
81
|
+
x2 = x + dx
|
82
|
+
y2 = y + dy
|
83
|
+
next unless in_bounds?(x2, y2)
|
84
|
+
@solver.assert var_at(x,y) != var_at(x2,y2) + 1
|
85
|
+
@solver.assert var_at(x,y) != var_at(x2,y2) - 1
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
if @solver.satisfiable?
|
90
|
+
@model = @solver.model
|
91
|
+
print_answer!
|
92
|
+
else
|
93
|
+
puts "failed to solve"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def each_coord
|
98
|
+
9.times do |x|
|
99
|
+
9.times do |y|
|
100
|
+
yield(x, y)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def in_bounds?(x,y)
|
106
|
+
(0..8).include?(x) and (0..8).include?(y)
|
107
|
+
end
|
108
|
+
|
109
|
+
def var_at(x, y)
|
110
|
+
return nil unless in_bounds?(x,y)
|
111
|
+
@cells[y][x]
|
112
|
+
end
|
113
|
+
|
114
|
+
def data_at(x, y)
|
115
|
+
return nil unless in_bounds?(x,y)
|
116
|
+
@data[y][x]
|
117
|
+
end
|
118
|
+
|
119
|
+
def cell_var(cell, x, y)
|
120
|
+
v = Z3.Int("cell[#{x+1},#{y+1}]")
|
121
|
+
@solver.assert v >= 1
|
122
|
+
@solver.assert v <= 9
|
123
|
+
@solver.assert v == cell if cell != nil
|
124
|
+
v
|
125
|
+
end
|
126
|
+
|
127
|
+
def print_answer!
|
128
|
+
@cells.each do |row|
|
129
|
+
puts row.map{|v| @model[v]}.join(" ")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
path = ARGV[0] || Pathname(__dir__) + "miracle_sudoku-1.txt"
|
135
|
+
MiracleSudokuSolver.new(path).call
|
data/examples/mortal_coil_puzzle
CHANGED
@@ -78,7 +78,7 @@ class MortalCoilSolver
|
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
|
-
def
|
81
|
+
def call
|
82
82
|
setup_vars!
|
83
83
|
line_continuity!
|
84
84
|
line_goes_until_it_hits_something!
|
@@ -102,4 +102,4 @@ class MortalCoilSolver
|
|
102
102
|
end
|
103
103
|
|
104
104
|
path = ARGV[0] || Pathname(__dir__) + "mortal_coil_puzzle-9.txt"
|
105
|
-
MortalCoilSolver.new(path).
|
105
|
+
MortalCoilSolver.new(path).call
|
data/examples/nanro
ADDED
@@ -0,0 +1,245 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative "../lib/z3"
|
4
|
+
require "pathname"
|
5
|
+
|
6
|
+
# https://www.gmpuzzles.com/blog/nanro-rules-and-info/
|
7
|
+
|
8
|
+
class Nanro
|
9
|
+
def initialize(path)
|
10
|
+
data = Pathname(path)
|
11
|
+
.readlines
|
12
|
+
.map{|row| row.chomp.split}
|
13
|
+
@ysize = data.size
|
14
|
+
@xsize = data[0].size
|
15
|
+
raise unless data.all?{|row| row.size == @ysize}
|
16
|
+
@regions = data
|
17
|
+
.map{|row|
|
18
|
+
row.map{|cell|
|
19
|
+
cell.gsub(/\d|\./, "")
|
20
|
+
}
|
21
|
+
}
|
22
|
+
@hints = data
|
23
|
+
.map{|row|
|
24
|
+
row.map{|cell|
|
25
|
+
cell[1] =~ /(\d+)/ ? $1.to_i : nil
|
26
|
+
}
|
27
|
+
}
|
28
|
+
@solver = Z3::Solver.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def assert_each_cell_is_0_or_rvar
|
32
|
+
each_xy do |x,y|
|
33
|
+
@solver.assert Z3.Or(
|
34
|
+
cvar(x,y) == 0,
|
35
|
+
cvar(x,y) == rvar(region_at(x,y))
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def assert_rvar_equals_number_of_numbered_cells_in_region
|
41
|
+
cells_by_region.each do |r, xys|
|
42
|
+
nonzeroes = xys.map{|x,y| (cvar(x,y) == 0).ite(0,1)}
|
43
|
+
@solver.assert rvar(r) == Z3.Add(*nonzeroes)
|
44
|
+
@solver.assert rvar(r) >= 1
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def assert_hints_obeyed
|
49
|
+
each_xy do |x,y|
|
50
|
+
if hint_at(x,y)
|
51
|
+
@solver.assert cvar(x,y) == hint_at(x,y)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def assert_no_numbered_squares
|
57
|
+
each_xy do |x,y|
|
58
|
+
next unless on_board?(x+1,y+1)
|
59
|
+
@solver.assert Z3.Or(
|
60
|
+
cvar(x,y) == 0,
|
61
|
+
cvar(x+1,y) == 0,
|
62
|
+
cvar(x,y+1) == 0,
|
63
|
+
cvar(x+1,y+1) == 0,
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def assert_no_same_number_between_regions
|
69
|
+
each_xy do |x,y|
|
70
|
+
neighbours(x,y).each do |nx,ny|
|
71
|
+
next if region_at(x,y) == region_at(nx,ny)
|
72
|
+
@solver.assert (cvar(x,y) != 0).implies(cvar(x,y) != cvar(nx,ny))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def assert_numbered_cells_all_connected
|
78
|
+
max_value = @xsize * @ysize + 10
|
79
|
+
|
80
|
+
each_xy do |x,y|
|
81
|
+
@solver.assert nvar(x,y) >= 0
|
82
|
+
@solver.assert (cvar(x,y) == 0).implies(nvar(x,y) == max_value)
|
83
|
+
@solver.assert (cvar(x,y) != 0).implies(nvar(x,y) < max_value)
|
84
|
+
|
85
|
+
nval = neighbours(x,y)
|
86
|
+
.map{|nx,ny|
|
87
|
+
(cvar(nx,ny) == 0).ite(max_value, nvar(nx,ny) + 1)
|
88
|
+
}
|
89
|
+
.reduce{|a,b| (a <= b).ite(a, b) }
|
90
|
+
|
91
|
+
@solver.assert (cvar(x,y) != 0).implies(Z3.Or(nvar(x,y) == 0, nvar(x,y) == nval))
|
92
|
+
end
|
93
|
+
|
94
|
+
@solver.assert Z3.Add(
|
95
|
+
*enum_for(:each_xy).map{|x,y|
|
96
|
+
(nvar(x,y) == 0).ite(1,0)
|
97
|
+
}) == 1
|
98
|
+
end
|
99
|
+
|
100
|
+
def call
|
101
|
+
assert_hints_obeyed
|
102
|
+
assert_no_numbered_squares
|
103
|
+
assert_each_cell_is_0_or_rvar
|
104
|
+
assert_rvar_equals_number_of_numbered_cells_in_region
|
105
|
+
assert_no_same_number_between_regions
|
106
|
+
assert_numbered_cells_all_connected
|
107
|
+
|
108
|
+
if @solver.satisfiable?
|
109
|
+
@model = @solver.model
|
110
|
+
print_answer!
|
111
|
+
else
|
112
|
+
puts "failed to solve"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def neighbours(x,y)
|
119
|
+
[
|
120
|
+
[x+1,y],
|
121
|
+
[x-1,y],
|
122
|
+
[x,y+1],
|
123
|
+
[x,y-1],
|
124
|
+
].select{|nx,ny| on_board?(nx,ny)}
|
125
|
+
end
|
126
|
+
|
127
|
+
def cells_by_region
|
128
|
+
@cells_by_region ||= enum_for(:each_xy).group_by{|x,y| region_at(x,y)}
|
129
|
+
end
|
130
|
+
|
131
|
+
def rvar(r)
|
132
|
+
Z3.Int("r[#{r}]")
|
133
|
+
end
|
134
|
+
|
135
|
+
def cvar(x,y)
|
136
|
+
Z3.Int("c[#{x},#{y}]")
|
137
|
+
end
|
138
|
+
|
139
|
+
def nvar(x,y)
|
140
|
+
Z3.Int("n[#{x},#{y}]")
|
141
|
+
end
|
142
|
+
|
143
|
+
def each_xy
|
144
|
+
@ysize.times do |y|
|
145
|
+
@xsize.times do |x|
|
146
|
+
yield(x,y)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def on_board?(x,y)
|
152
|
+
x >= 0 and y >= 0 and x < @xsize and y < @ysize
|
153
|
+
end
|
154
|
+
|
155
|
+
def region_at(x,y)
|
156
|
+
return nil unless on_board?(x,y)
|
157
|
+
@regions[y][x]
|
158
|
+
end
|
159
|
+
|
160
|
+
def hint_at(x,y)
|
161
|
+
return nil unless on_board?(x,y)
|
162
|
+
@hints[y][x]
|
163
|
+
end
|
164
|
+
|
165
|
+
def print_corner?(x, y)
|
166
|
+
[
|
167
|
+
region_at(x,y),
|
168
|
+
region_at(x-1,y),
|
169
|
+
region_at(x,y-1),
|
170
|
+
region_at(x-1,y-1),
|
171
|
+
].uniq.size > 1
|
172
|
+
end
|
173
|
+
|
174
|
+
def print_edge?(x1, y1, x2, y2)
|
175
|
+
region_at(x1,y1) != region_at(x2,y2)
|
176
|
+
end
|
177
|
+
|
178
|
+
def corner_output(x,y)
|
179
|
+
if print_corner?(x, y)
|
180
|
+
"*"
|
181
|
+
else
|
182
|
+
" "
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def vedge_output(x,y)
|
187
|
+
if print_edge?(x, y, x, y-1)
|
188
|
+
"---"
|
189
|
+
else
|
190
|
+
" "
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def hedge_output(x,y)
|
195
|
+
if print_edge?(x, y, x-1, y)
|
196
|
+
"|"
|
197
|
+
else
|
198
|
+
" "
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def print_answer!
|
203
|
+
(0..@ysize).each do |y|
|
204
|
+
(0..@xsize).each do |x|
|
205
|
+
print corner_output(x,y)
|
206
|
+
next if x == @xsize
|
207
|
+
print vedge_output(x,y)
|
208
|
+
end
|
209
|
+
print "\n"
|
210
|
+
|
211
|
+
next if y == @ysize
|
212
|
+
|
213
|
+
(0..@xsize).each do |x|
|
214
|
+
print hedge_output(x,y)
|
215
|
+
next if x == @xsize
|
216
|
+
print " "
|
217
|
+
end
|
218
|
+
print "\n"
|
219
|
+
|
220
|
+
(0..@xsize).each do |x|
|
221
|
+
print hedge_output(x,y)
|
222
|
+
next if x == @xsize
|
223
|
+
print " "
|
224
|
+
c = @model[cvar(x,y)].to_i
|
225
|
+
if c == 0
|
226
|
+
print " "
|
227
|
+
else
|
228
|
+
print c
|
229
|
+
end
|
230
|
+
print " "
|
231
|
+
end
|
232
|
+
print "\n"
|
233
|
+
|
234
|
+
(0..@xsize).each do |x|
|
235
|
+
print hedge_output(x,y)
|
236
|
+
next if x == @xsize
|
237
|
+
print " "
|
238
|
+
end
|
239
|
+
print "\n"
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
path = ARGV[0] || Pathname(__dir__) + "nanro-1.txt"
|
245
|
+
Nanro.new(path).call
|
@@ -0,0 +1,106 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# From Tested on Humans Escape Room game
|
3
|
+
|
4
|
+
require_relative "../lib/z3"
|
5
|
+
|
6
|
+
class NineClocks
|
7
|
+
def initialize
|
8
|
+
@solver = Z3::Solver.new
|
9
|
+
|
10
|
+
@inputs = {}
|
11
|
+
@outputs = {}
|
12
|
+
|
13
|
+
# Setup
|
14
|
+
each_xy do |x,y|
|
15
|
+
@inputs[[x,y]] = Z3.Bitvec("i#{x},#{y}", 2)
|
16
|
+
@outputs[[x,y]] = Z3.Bitvec("o#{x},#{y}", 2)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Rules
|
20
|
+
neighbours = {}
|
21
|
+
each_xy do |x,y|
|
22
|
+
out = @outputs[[x,y]]
|
23
|
+
neighbours[out] = []
|
24
|
+
each_xy do |xx, yy|
|
25
|
+
if (xx-x).abs + (yy-y).abs <= 1
|
26
|
+
neighbours[out] << @inputs[[xx,yy]]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
neighbours.each do |ovar, ivars|
|
31
|
+
@solver.assert ovar == Z3.Add(*ivars)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def each_xy
|
37
|
+
3.times do |x|
|
38
|
+
3.times do |y|
|
39
|
+
yield(x,y)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def target_corner
|
45
|
+
each_xy do |x,y|
|
46
|
+
if x == 0 and y == 0
|
47
|
+
@solver.assert @outputs[[x,y]] == 1
|
48
|
+
else
|
49
|
+
@solver.assert @outputs[[x,y]] == 0
|
50
|
+
end
|
51
|
+
end
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def target_edge
|
56
|
+
each_xy do |x,y|
|
57
|
+
if x == 0 and y == 1
|
58
|
+
@solver.assert @outputs[[x,y]] == 1
|
59
|
+
else
|
60
|
+
@solver.assert @outputs[[x,y]] == 0
|
61
|
+
end
|
62
|
+
end
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
def target_center
|
67
|
+
each_xy do |x,y|
|
68
|
+
if x == 1 and y == 1
|
69
|
+
@solver.assert @outputs[[x,y]] == 1
|
70
|
+
else
|
71
|
+
@solver.assert @outputs[[x,y]] == 0
|
72
|
+
end
|
73
|
+
end
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
def solve
|
78
|
+
if @solver.satisfiable?
|
79
|
+
model = @solver.model
|
80
|
+
|
81
|
+
puts "IN:"
|
82
|
+
3.times do |x|
|
83
|
+
3.times do |y|
|
84
|
+
ivar = model[@inputs[[x,y]]]
|
85
|
+
print ivar, " "
|
86
|
+
end
|
87
|
+
print "\n"
|
88
|
+
end
|
89
|
+
|
90
|
+
puts "OUT:"
|
91
|
+
3.times do |x|
|
92
|
+
3.times do |y|
|
93
|
+
ivar = model[@outputs[[x,y]]]
|
94
|
+
print ivar, " "
|
95
|
+
end
|
96
|
+
print "\n"
|
97
|
+
end
|
98
|
+
else
|
99
|
+
puts "Not satisfiable"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
NineClocks.new.target_corner.solve
|
105
|
+
NineClocks.new.target_edge.solve
|
106
|
+
NineClocks.new.target_center.solve
|
data/examples/nonogram
CHANGED
@@ -76,7 +76,7 @@ class Nonogram
|
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
79
|
-
def
|
79
|
+
def call
|
80
80
|
@cells = (0...@ysize).map{|y|
|
81
81
|
(0...@xsize).map{|x|
|
82
82
|
Z3.Bool("cell[#{x+1},#{y+1}]")
|
@@ -149,4 +149,4 @@ nonogram = Nonogram.new(
|
|
149
149
|
]
|
150
150
|
)
|
151
151
|
|
152
|
-
nonogram.
|
152
|
+
nonogram.call
|
data/examples/pyramid_nonogram
CHANGED
@@ -27,7 +27,7 @@ class PyramidNonogram
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
def
|
30
|
+
def call
|
31
31
|
if @solver.satisfiable?
|
32
32
|
@model = @solver.model
|
33
33
|
print_answer!
|
@@ -163,4 +163,4 @@ pyramid_nonogram.left_to_bottom(
|
|
163
163
|
[_],
|
164
164
|
[_],
|
165
165
|
)
|
166
|
-
pyramid_nonogram.
|
166
|
+
pyramid_nonogram.call
|
@@ -43,7 +43,7 @@ class RegexpCrosswordSolver
|
|
43
43
|
SimpleRegexpParser.new(@rows[y], "row-#{y}").parse
|
44
44
|
end
|
45
45
|
|
46
|
-
def
|
46
|
+
def call
|
47
47
|
@crossword = {}
|
48
48
|
@xsize.times do |x|
|
49
49
|
@ysize.times do |y|
|
@@ -77,4 +77,4 @@ class RegexpCrosswordSolver
|
|
77
77
|
end
|
78
78
|
|
79
79
|
path = ARGV[0] || Pathname(__dir__) + "regexp_crossword/tutorial-1.txt"
|
80
|
-
RegexpCrosswordSolver.new(path).
|
80
|
+
RegexpCrosswordSolver.new(path).call
|
data/examples/regexp_solver
CHANGED
@@ -11,7 +11,7 @@ class RegexpSolver
|
|
11
11
|
@solver = Z3::Solver.new
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
14
|
+
def call
|
15
15
|
@str = (0...@length).map do |i|
|
16
16
|
v = Z3.Int("char[#{i}]")
|
17
17
|
@solver.assert v >= 0
|
@@ -40,4 +40,4 @@ end
|
|
40
40
|
|
41
41
|
length = ARGV[0].to_i
|
42
42
|
regexp = ARGV[1]
|
43
|
-
RegexpSolver.new(length, regexp).
|
43
|
+
RegexpSolver.new(length, regexp).call
|
data/examples/renzoku
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require_relative "../lib/z3"
|
5
|
+
|
6
|
+
class Renzoku
|
7
|
+
# This needs to be very exactly formatted
|
8
|
+
def initialize(path)
|
9
|
+
@data = Pathname(path).readlines.map(&:chomp)
|
10
|
+
@size = (@data.size+1)/2
|
11
|
+
@solver = Z3::Solver.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def cell_value(x, y)
|
15
|
+
v = @data[y*2][x*2]
|
16
|
+
if v == "#"
|
17
|
+
nil
|
18
|
+
elsif v =~ /\d/
|
19
|
+
v.to_i
|
20
|
+
else
|
21
|
+
raise "Bad cell value"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def dot_right?(x, y)
|
26
|
+
v = @data[y*2][x*2 + 1]
|
27
|
+
if v == "."
|
28
|
+
true
|
29
|
+
elsif v == " "
|
30
|
+
false
|
31
|
+
else
|
32
|
+
raise "Bad dot value"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def dot_bottom?(x, y)
|
37
|
+
v = @data[y*2 + 1][x*2]
|
38
|
+
if v == "."
|
39
|
+
true
|
40
|
+
elsif v == " " or v == nil
|
41
|
+
false
|
42
|
+
else
|
43
|
+
raise "Bad dot value"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def call
|
48
|
+
@vars = {}
|
49
|
+
@size.times do |y|
|
50
|
+
@size.times do |x|
|
51
|
+
v = Z3.Int("v#{x},#{y}")
|
52
|
+
@vars[[x,y]] = v
|
53
|
+
cv = cell_value(x, y)
|
54
|
+
if cv
|
55
|
+
@solver.assert (v == cv)
|
56
|
+
else
|
57
|
+
@solver.assert (v >= 1) & (v <= @size)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
@size.times do |x|
|
63
|
+
line = @size.times.map{|y| @vars[[x,y]] }
|
64
|
+
@solver.assert Z3.Distinct(*line)
|
65
|
+
end
|
66
|
+
|
67
|
+
@size.times do |y|
|
68
|
+
line = @size.times.map{|x| @vars[[x,y]] }
|
69
|
+
@solver.assert Z3.Distinct(*line)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Dots right
|
73
|
+
(0..@size-1).each do |y|
|
74
|
+
(0..@size-2).each do |x|
|
75
|
+
lv = @vars[[x,y]]
|
76
|
+
rv = @vars[[x+1,y]]
|
77
|
+
if dot_right?(x,y)
|
78
|
+
@solver.assert (lv == (rv + 1)) | (lv == (rv - 1))
|
79
|
+
else
|
80
|
+
@solver.assert lv != (rv + 1)
|
81
|
+
@solver.assert lv != (rv - 1)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Dots bottom
|
87
|
+
(0..@size-2).each do |y|
|
88
|
+
(0..@size-1).each do |x|
|
89
|
+
tv = @vars[[x,y]]
|
90
|
+
bv = @vars[[x,y+1]]
|
91
|
+
if dot_bottom?(x,y)
|
92
|
+
@solver.assert (tv == (bv + 1)) | (tv == (bv - 1))
|
93
|
+
else
|
94
|
+
@solver.assert tv != (bv + 1)
|
95
|
+
@solver.assert tv != (bv - 1)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
if @solver.satisfiable?
|
101
|
+
@model = @solver.model
|
102
|
+
print_answer!
|
103
|
+
else
|
104
|
+
puts "failed to solve"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def print_answer!
|
111
|
+
output = @data.map(&:dup)
|
112
|
+
@size.times do |y|
|
113
|
+
@size.times do |x|
|
114
|
+
v = @model[@vars[[x,y]]]
|
115
|
+
output[2*y][2*x] = "#{v}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
puts output
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
path = ARGV[0] || Pathname(__dir__) + "renzoku-1.txt"
|
124
|
+
Renzoku.new(path).call
|