z3 0.0.20180629 → 0.0.20220203
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/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
|