z3 0.0.20181229 → 0.0.20220320
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/.rspec +0 -0
- data/README.md +0 -2
- data/Rakefile +8 -1
- 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/bridges-1.txt +0 -0
- 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/kakuro-1.txt +0 -0
- 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/letter_connections-1.txt +0 -0
- data/examples/light_up +2 -2
- data/examples/light_up-1.txt +0 -0
- data/examples/minisudoku +2 -2
- data/examples/minisudoku-1.txt +0 -0
- data/examples/miracle_sudoku +135 -0
- data/examples/miracle_sudoku-1.txt +9 -0
- data/examples/mortal_coil_puzzle +2 -2
- data/examples/mortal_coil_puzzle-9.txt +0 -0
- 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/beginner-1.txt +0 -0
- data/examples/regexp_crossword/beginner-2.txt +0 -0
- data/examples/regexp_crossword/beginner-3.txt +0 -0
- data/examples/regexp_crossword/beginner-4.txt +0 -0
- data/examples/regexp_crossword/beginner-5.txt +0 -0
- data/examples/regexp_crossword/experienced-1.txt +0 -0
- data/examples/regexp_crossword/experienced-2.txt +0 -0
- data/examples/regexp_crossword/experienced-3.txt +0 -0
- data/examples/regexp_crossword/experienced-4.txt +0 -0
- data/examples/regexp_crossword/experienced-5.txt +0 -0
- data/examples/regexp_crossword/tutorial-1.txt +0 -0
- data/examples/regexp_crossword/tutorial-2.txt +0 -0
- data/examples/regexp_crossword/tutorial-3.txt +0 -0
- data/examples/regexp_crossword/tutorial-4.txt +0 -0
- data/examples/regexp_crossword/tutorial-5.txt +0 -0
- data/examples/regexp_crossword/tutorial-6.txt +0 -0
- data/examples/regexp_crossword/tutorial-7.txt +0 -0
- data/examples/regexp_crossword/tutorial-8.txt +0 -0
- data/examples/regexp_crossword/tutorial-9.txt +0 -0
- data/examples/regexp_crossword_solver +2 -2
- data/examples/regexp_solver +2 -2
- data/examples/regexp_string_matcher.rb +0 -0
- 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 +0 -0
- 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/sudoku-1.txt +0 -0
- 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 +0 -0
- data/lib/z3/context.rb +0 -0
- data/lib/z3/exception.rb +0 -0
- data/lib/z3/expr/arith_expr.rb +0 -0
- data/lib/z3/expr/array_expr.rb +0 -0
- data/lib/z3/expr/bitvec_expr.rb +47 -2
- data/lib/z3/expr/bool_expr.rb +0 -0
- data/lib/z3/expr/expr.rb +3 -3
- data/lib/z3/expr/float_expr.rb +0 -0
- data/lib/z3/expr/int_expr.rb +0 -0
- data/lib/z3/expr/real_expr.rb +0 -0
- data/lib/z3/expr/rounding_mode_expr.rb +0 -0
- data/lib/z3/expr/set_expr.rb +0 -0
- data/lib/z3/func_decl.rb +0 -0
- data/lib/z3/goal.rb +0 -0
- data/lib/z3/hacks.rb +0 -0
- data/lib/z3/interface.rb +0 -0
- data/lib/z3/low_level.rb +0 -0
- data/lib/z3/low_level_auto.rb +138 -10
- data/lib/z3/model.rb +0 -0
- data/lib/z3/optimize.rb +0 -0
- data/lib/z3/printer.rb +0 -0
- data/lib/z3/probe.rb +0 -0
- data/lib/z3/solver.rb +0 -0
- data/lib/z3/sort/array_sort.rb +0 -0
- data/lib/z3/sort/bitvec_sort.rb +0 -0
- data/lib/z3/sort/bool_sort.rb +0 -0
- data/lib/z3/sort/float_sort.rb +0 -0
- data/lib/z3/sort/int_sort.rb +0 -0
- data/lib/z3/sort/real_sort.rb +0 -0
- data/lib/z3/sort/rounding_mode_sort.rb +0 -0
- data/lib/z3/sort/set_sort.rb +0 -0
- data/lib/z3/sort/sort.rb +0 -0
- data/lib/z3/tactic.rb +0 -0
- data/lib/z3/very_low_level.rb +5 -1
- data/lib/z3/very_low_level_auto.rb +35 -3
- data/lib/z3.rb +0 -0
- data/spec/array_expr_spec.rb +0 -0
- data/spec/array_sort_spec.rb +0 -0
- data/spec/bitvec_expr_spec.rb +13 -0
- data/spec/bitvec_sort_spec.rb +0 -0
- data/spec/bool_expr_spec.rb +0 -0
- data/spec/bool_sort_spec.rb +0 -0
- data/spec/coverage_helper.rb +0 -0
- data/spec/expr_spec.rb +0 -0
- data/spec/float_expr_spec.rb +0 -0
- data/spec/float_sort_spec.rb +0 -0
- data/spec/goal_spec.rb +0 -0
- data/spec/int_expr_spec.rb +0 -0
- data/spec/int_sort_spec.rb +0 -0
- data/spec/integration/abc_path_spec.rb +21 -0
- data/spec/integration/algebra_problems_spec.rb +0 -0
- data/spec/integration/aquarium_spec.rb +27 -0
- data/spec/integration/basic_int_math_spec.rb +0 -0
- data/spec/integration/basic_logic_spec.rb +0 -0
- data/spec/integration/bit_tricks_spec.rb +0 -0
- data/spec/integration/bridges_spec.rb +0 -0
- data/spec/integration/cats_organized_neatly_spec.rb +14 -0
- data/spec/integration/cicruit_problem_spec.rb +0 -0
- data/spec/integration/color_nonogram_spec.rb +28 -0
- data/spec/integration/crossflip_spec.rb +0 -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/four_hackers_puzzle_spec.rb +0 -0
- data/spec/integration/futoshiki_spec.rb +23 -0
- data/spec/integration/geometry_problem_spec.rb +0 -0
- data/spec/integration/kakurasu_spec.rb +18 -0
- data/spec/integration/kakuro_spec.rb +0 -0
- data/spec/integration/killer_sudoku_spec.rb +10 -0
- data/spec/integration/kinematics_problems_spec.rb +0 -0
- data/spec/integration/knights_puzzle_spec.rb +11 -11
- data/spec/integration/kropki_spec.rb +19 -0
- data/spec/integration/letter_connections_spec.rb +0 -0
- data/spec/integration/light_up_spec.rb +0 -0
- data/spec/integration/minisudoku_spec.rb +0 -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/nonogram_spec.rb +0 -0
- data/spec/integration/oneofus_spec.rb +0 -0
- data/spec/integration/pyramid_nonogram_spec.rb +0 -0
- data/spec/integration/regexp_crossword_solver_spec.rb +1 -1
- data/spec/integration/regexp_solver_spec.rb +0 -0
- data/spec/integration/renzoku_spec.rb +23 -0
- data/spec/integration/sandwich_sudoku_spec.rb +15 -0
- data/spec/integration/selfref_spec.rb +0 -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/sudoku_spec.rb +0 -0
- data/spec/integration/suguru_spec.rb +23 -0
- data/spec/integration/verbal_arithmetic_spec.rb +0 -0
- data/spec/integration/yajilin_spec.rb +25 -0
- data/spec/integration/zebra_puzzle_spec.rb +0 -0
- data/spec/interface_spec.rb +0 -0
- data/spec/model_spec.rb +0 -0
- data/spec/optimize_spec.rb +3 -1
- data/spec/printer_spec.rb +0 -0
- data/spec/probe_spec.rb +0 -0
- data/spec/real_expr_spec.rb +0 -0
- data/spec/real_sort_spec.rb +0 -0
- data/spec/rounding_mode_expr_spec.rb +0 -0
- data/spec/rounding_mode_sort_spec.rb +0 -0
- data/spec/set_expr_spec.rb +15 -9
- data/spec/set_sort_spec.rb +0 -0
- data/spec/solver_spec.rb +1 -2
- data/spec/sort_spec.rb +0 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/tactic_spec.rb +0 -0
- data/spec/z3_spec.rb +0 -0
- metadata +85 -5
data/examples/stitches
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require_relative "../lib/z3"
|
5
|
+
require "paint"
|
6
|
+
|
7
|
+
class StitchesSolver
|
8
|
+
def initialize(path)
|
9
|
+
data = Pathname(path).read
|
10
|
+
data = data.strip.split("\n").map do |line|
|
11
|
+
line.split
|
12
|
+
end
|
13
|
+
@col_counts = data.shift.map(&:to_i)
|
14
|
+
@row_counts = data.map(&:shift).map(&:to_i)
|
15
|
+
@xsize = @col_counts.size
|
16
|
+
@ysize = @row_counts.size
|
17
|
+
@data = data
|
18
|
+
raise unless data.size == @ysize
|
19
|
+
raise unless data.all?{|r| r.size == @ysize}
|
20
|
+
raise unless @col_counts.sum == @row_counts.sum
|
21
|
+
@solver = Z3::Solver.new
|
22
|
+
@groups = @data.flatten.uniq
|
23
|
+
end
|
24
|
+
|
25
|
+
def call
|
26
|
+
assign_colors!
|
27
|
+
setup_cell_vars!
|
28
|
+
setup_stitch_vars!
|
29
|
+
|
30
|
+
setup_cell_assertions!
|
31
|
+
setup_stitch_assertions!
|
32
|
+
setup_cell_stitch_assertions!
|
33
|
+
|
34
|
+
if @solver.satisfiable?
|
35
|
+
@model = @solver.model
|
36
|
+
print_answer!
|
37
|
+
else
|
38
|
+
puts "failed to solve"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def assign_colors!
|
43
|
+
@fg = @groups.to_h{|g| [g, [rand(256),rand(256),rand(256)]] }
|
44
|
+
@bg = @groups.to_h{|g| [g, [rand(256),rand(256),rand(256)]] }
|
45
|
+
end
|
46
|
+
|
47
|
+
def setup_cell_vars!
|
48
|
+
@cell_vars = {}
|
49
|
+
@xsize.times do |x|
|
50
|
+
@ysize.times do |y|
|
51
|
+
v = Z3.Bool("c#{x},#{y}")
|
52
|
+
@cell_vars[[x,y]] = v
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def row_of_cell_vars(y)
|
58
|
+
@xsize.times.map{|x| @cell_vars[[x,y]].ite(1,0) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def col_of_cell_vars(x)
|
62
|
+
@ysize.times.map{|y| @cell_vars[[x,y]].ite(1,0) }
|
63
|
+
end
|
64
|
+
|
65
|
+
def setup_cell_assertions!
|
66
|
+
@xsize.times do |x|
|
67
|
+
@solver.assert Z3.Add(*col_of_cell_vars(x)) == @col_counts[x]
|
68
|
+
end
|
69
|
+
|
70
|
+
@ysize.times do |y|
|
71
|
+
@solver.assert Z3.Add(*row_of_cell_vars(y)) == @row_counts[y]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def setup_stitch_vars!
|
76
|
+
@hstitch = {}
|
77
|
+
@vstitch = {}
|
78
|
+
@stitch_groups = {}
|
79
|
+
|
80
|
+
(@xsize-1).times do |x|
|
81
|
+
@ysize.times do |y|
|
82
|
+
v = Z3.Bool("h#{x},#{y}")
|
83
|
+
g1 = @data[y][x]
|
84
|
+
g2 = @data[y][x+1]
|
85
|
+
pair = [g1,g2].sort.uniq
|
86
|
+
next if pair.size == 1
|
87
|
+
@stitch_groups[pair] ||= []
|
88
|
+
@stitch_groups[pair] << v
|
89
|
+
@hstitch[[x,y]] = v
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
@xsize.times do |x|
|
94
|
+
(@ysize-1).times do |y|
|
95
|
+
v = Z3.Bool("v#{x},#{y}")
|
96
|
+
g1 = @data[y][x]
|
97
|
+
g2 = @data[y+1][x]
|
98
|
+
pair = [g1,g2].sort.uniq
|
99
|
+
next if pair.size == 1
|
100
|
+
@stitch_groups[pair] ||= []
|
101
|
+
@stitch_groups[pair] << v
|
102
|
+
@vstitch[[x,y]] = v
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
group_count = (2 * @stitch_groups.keys.size)
|
107
|
+
raise unless @col_counts.sum % group_count == 0
|
108
|
+
@stitch_count = @col_counts.sum / group_count
|
109
|
+
end
|
110
|
+
|
111
|
+
def setup_stitch_assertions!
|
112
|
+
@stitch_groups.each do |g, vars|
|
113
|
+
@solver.assert Z3.Add(*vars.map{|v| v.ite(1,0)}) == @stitch_count
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def setup_cell_stitch_assertions!
|
118
|
+
@xsize.times do |x|
|
119
|
+
@ysize.times do |y|
|
120
|
+
vars = [
|
121
|
+
@hstitch[[x,y]],
|
122
|
+
@hstitch[[x-1,y]],
|
123
|
+
@vstitch[[x,y]],
|
124
|
+
@vstitch[[x,y-1]],
|
125
|
+
].compact.map{|v| v.ite(1,0)}
|
126
|
+
if vars.empty?
|
127
|
+
@solver.assert !@cell_vars[[x,y]]
|
128
|
+
else
|
129
|
+
@solver.assert @cell_vars[[x,y]].ite(1,0) == Z3.Add(*vars)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def group_at(x,y)
|
136
|
+
return nil if x < 0 or y < 0
|
137
|
+
return nil if x >= @xsize or y >= @ysize
|
138
|
+
@data[y][x]
|
139
|
+
end
|
140
|
+
|
141
|
+
def paint_for(s,x1,y1,x2=x1,y2=y1)
|
142
|
+
g1 = group_at(x1,y1)
|
143
|
+
g2 = group_at(x2,y2)
|
144
|
+
if g1 == g2
|
145
|
+
Paint[s, @fg[g1], @bg[g1]]
|
146
|
+
else
|
147
|
+
s
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def print_answer!
|
152
|
+
@ysize.times do |y|
|
153
|
+
@xsize.times do |x|
|
154
|
+
s = @model[@cell_vars[[x,y]]].to_b ? "*" : " "
|
155
|
+
print paint_for(s, x, y)
|
156
|
+
if @hstitch[[x,y]]
|
157
|
+
hs = @model[@hstitch[[x,y]]].to_b ? "-" : " "
|
158
|
+
else
|
159
|
+
hs = " "
|
160
|
+
end
|
161
|
+
print paint_for(hs, x, y, x+1, y)
|
162
|
+
end
|
163
|
+
print "\n"
|
164
|
+
next if y == @ysize - 1
|
165
|
+
@xsize.times do |x|
|
166
|
+
if @vstitch[[x,y]]
|
167
|
+
vs = @model[@vstitch[[x,y]]].to_b ? "|" : " "
|
168
|
+
else
|
169
|
+
vs = " "
|
170
|
+
end
|
171
|
+
print paint_for(vs, x, y, x, y+1)
|
172
|
+
print paint_for(" ", x, y, x+1, y+1)
|
173
|
+
end
|
174
|
+
print "\n"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
path = ARGV[0] || Pathname(__dir__) + "stitches-1.txt"
|
180
|
+
StitchesSolver.new(path).call
|
@@ -0,0 +1,11 @@
|
|
1
|
+
6 8 6 9 2 5 4 8 2 2
|
2
|
+
3 a a a a a a a b b b
|
3
|
+
6 a a c c c a a b c b
|
4
|
+
4 a f f c c b b b c b
|
5
|
+
6 f f f f c c c c c b
|
6
|
+
8 h h h f c h c b b b
|
7
|
+
4 g h h h h h c c b b
|
8
|
+
6 g g e e e h h h e b
|
9
|
+
8 g g g g e h h e e e
|
10
|
+
5 d d e e e e e e e e
|
11
|
+
2 d d d d e e e e e e
|
data/examples/sudoku
CHANGED
@@ -13,7 +13,7 @@ class SudokuSolver
|
|
13
13
|
@solver = Z3::Solver.new
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
16
|
+
def call
|
17
17
|
@cells = (0..8).map do |j|
|
18
18
|
(0..8).map do |i|
|
19
19
|
cell_var(@data[j][i], i, j)
|
@@ -58,4 +58,4 @@ class SudokuSolver
|
|
58
58
|
end
|
59
59
|
|
60
60
|
path = ARGV[0] || Pathname(__dir__) + "sudoku-1.txt"
|
61
|
-
SudokuSolver.new(path).
|
61
|
+
SudokuSolver.new(path).call
|
data/examples/sudoku-1.txt
CHANGED
File without changes
|
data/examples/suguru
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require_relative "../lib/z3"
|
5
|
+
|
6
|
+
class Suguru
|
7
|
+
def initialize(path)
|
8
|
+
@data = Pathname(path).readlines.map(&:chomp)
|
9
|
+
@ysize = (@data.size - 1) / 2
|
10
|
+
@xsize = (@data[0].size - 1) / 2
|
11
|
+
raise unless @data.size == 2 * @ysize + 1
|
12
|
+
raise unless @data.all?{ |row| row.size == 2 * @xsize + 1 }
|
13
|
+
@solver = Z3::Solver.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
@connections = calculate_connections
|
18
|
+
@containers = calculate_containers
|
19
|
+
|
20
|
+
@ysize.times do |y|
|
21
|
+
@ysize.times do |x|
|
22
|
+
v = cell_data(x, y)
|
23
|
+
if v
|
24
|
+
@solver.assert cell(x, y) == v
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
@containers.each do |name, cells|
|
30
|
+
@solver.assert Z3.Distinct(*cells)
|
31
|
+
cells.each do |c|
|
32
|
+
@solver.assert c >= 1
|
33
|
+
@solver.assert c <= cells.size
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
@ysize.times do |y|
|
38
|
+
@xsize.times do |x|
|
39
|
+
@solver.assert cell(x, y) != cell(x+1, y)
|
40
|
+
@solver.assert cell(x, y) != cell(x-1, y+1)
|
41
|
+
@solver.assert cell(x, y) != cell(x, y+1)
|
42
|
+
@solver.assert cell(x, y) != cell(x+1, y+1)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
if @solver.satisfiable?
|
47
|
+
@model = @solver.model
|
48
|
+
print_answer
|
49
|
+
else
|
50
|
+
puts "failed to solve"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def calculate_containers
|
57
|
+
cells = @xsize.times.flat_map{|x| @ysize.times.map{|y| [x,y]}}
|
58
|
+
|
59
|
+
containers = {}
|
60
|
+
cell_assignments = {}
|
61
|
+
cells.each do |c|
|
62
|
+
ct = c.join(",")
|
63
|
+
containers[ct] = [c]
|
64
|
+
cell_assignments[c] = ct
|
65
|
+
end
|
66
|
+
|
67
|
+
@connections.each do |c1, c2|
|
68
|
+
# Already in same container
|
69
|
+
ct1 = cell_assignments[c1]
|
70
|
+
ct2 = cell_assignments[c2]
|
71
|
+
next if ct1 == ct2
|
72
|
+
containers.delete(ct2).each do |c3|
|
73
|
+
cell_assignments[c3] = ct1
|
74
|
+
containers[ct1] << c3
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
containers.transform_values{|cells| cells.map{|x,y| cell(x,y)}}
|
79
|
+
end
|
80
|
+
|
81
|
+
def calculate_connections
|
82
|
+
result = []
|
83
|
+
|
84
|
+
@ysize.times do |y|
|
85
|
+
(0..@xsize-2).each do |x|
|
86
|
+
if cell_open_right?(x, y)
|
87
|
+
result << [[x,y], [x+1,y]]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
@xsize.times do |x|
|
93
|
+
(0..@ysize-2).each do |y|
|
94
|
+
if cell_open_down?(x, y)
|
95
|
+
result << [[x,y], [x,y+1]]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
result
|
101
|
+
end
|
102
|
+
|
103
|
+
def cell_open_right?(x, y)
|
104
|
+
@data[2*y+1][2*x+2] == " "
|
105
|
+
end
|
106
|
+
|
107
|
+
def cell_open_down?(x, y)
|
108
|
+
@data[2*y+2][2*x+1] == " "
|
109
|
+
end
|
110
|
+
|
111
|
+
def cell_data(x, y)
|
112
|
+
v = @data[2*y+1][2*x+1]
|
113
|
+
return nil if v == " "
|
114
|
+
v.to_i
|
115
|
+
end
|
116
|
+
|
117
|
+
def cell(x, y)
|
118
|
+
Z3.Int("c#{x},#{y}")
|
119
|
+
end
|
120
|
+
|
121
|
+
def print_answer
|
122
|
+
output = @data.map(&:dup)
|
123
|
+
@ysize.times do |y|
|
124
|
+
@xsize.times do |x|
|
125
|
+
# This only works for 1-9, could use hex or something for more
|
126
|
+
value = @model[cell(x,y)].to_s
|
127
|
+
output[2*y+1][2*x+1, 1] = value
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
puts output
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
path = ARGV[0] || Pathname(__dir__) + "suguru-1.txt"
|
136
|
+
Suguru.new(path).call
|
137
|
+
|
138
|
+
|
139
|
+
__END__
|
140
|
+
|
141
|
+
# Based on https://play.google.com/store/apps/details?id=com.alexuvarov.android.kropki.puzzle&hl=en_GB
|
142
|
+
#
|
143
|
+
# NxN grid
|
144
|
+
# Each cell contains numbers 1 to N
|
145
|
+
# Each row and each column contains distinct numbers
|
146
|
+
# If there's black dot (o) between cells, one is twice the other
|
147
|
+
# If there's white dot (*) between cells, one is the other plus one
|
148
|
+
# If there's no dot, neither of these is true
|
149
|
+
# (for 1/2, dot can be of either color)
|
150
|
+
|
151
|
+
class Kropki
|
152
|
+
def call
|
153
|
+
# Cells contain numbers 1 - N
|
154
|
+
@size.times do |y|
|
155
|
+
@size.times do |x|
|
156
|
+
@solver.assert cell(x, y) >= 1
|
157
|
+
@solver.assert cell(x, y) <= @size
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Each row and column distinct
|
162
|
+
@size.times do |y|
|
163
|
+
@solver.assert Z3.Distinct(*@size.times.map{ |x| cell(x,y) })
|
164
|
+
end
|
165
|
+
@size.times do |x|
|
166
|
+
@solver.assert Z3.Distinct(*@size.times.map{ |y| cell(x,y) })
|
167
|
+
end
|
168
|
+
|
169
|
+
# Horizontal dots
|
170
|
+
@size.times do |y|
|
171
|
+
(0...@size).each do |x|
|
172
|
+
dot_constraints cell(x, y), cell(x+1, y), @data[2*y+1][2*x+2]
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Vertical dots
|
177
|
+
@size.times do |x|
|
178
|
+
(0...@size).each do |y|
|
179
|
+
dot_constraints cell(x, y), cell(x, y+1), @data[2*y+2][2*x+1]
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def dot_constraints(c1, c2, dot)
|
187
|
+
if dot == "o"
|
188
|
+
@solver.assert Z3.Or(c1 == c2 + 1, c2 == c1 + 1)
|
189
|
+
elsif dot == "*"
|
190
|
+
@solver.assert Z3.Or(c1 == c2 * 2, c2 == c1 * 2)
|
191
|
+
else
|
192
|
+
@solver.assert c1 != c2 + 1
|
193
|
+
@solver.assert c2 != c1 + 1
|
194
|
+
@solver.assert c1 != c2 * 2
|
195
|
+
@solver.assert c2 != c1 * 2
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
data/examples/verbal_arithmetic
CHANGED
@@ -16,7 +16,7 @@ class VerbalArithmetic
|
|
16
16
|
@c = c.chars.map{|v| @vars[v]}
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
19
|
+
def call
|
20
20
|
@solver.assert @a[0] != 0
|
21
21
|
@solver.assert @b[0] != 0
|
22
22
|
@solver.assert @c[0] != 0
|
@@ -44,4 +44,4 @@ class VerbalArithmetic
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
VerbalArithmetic.new("SEND", "MORE", "MONEY").
|
47
|
+
VerbalArithmetic.new("SEND", "MORE", "MONEY").call
|
data/examples/yajilin
ADDED
@@ -0,0 +1,268 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require_relative "../lib/z3"
|
5
|
+
|
6
|
+
class Yajilin
|
7
|
+
def initialize(path)
|
8
|
+
@data = Pathname(path)
|
9
|
+
.readlines
|
10
|
+
.map{|line|
|
11
|
+
line
|
12
|
+
.chomp
|
13
|
+
.split
|
14
|
+
.map{|x| x == "." ? nil : x}
|
15
|
+
}
|
16
|
+
@xsize = @data.size
|
17
|
+
@ysize = @data[0].size
|
18
|
+
raise unless @data.all?{|row| row.size == @xsize}
|
19
|
+
@solver = Z3::Solver.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_board?(x,y)
|
23
|
+
x >= 0 and y >= 0 and x < @xsize and y < @ysize
|
24
|
+
end
|
25
|
+
|
26
|
+
# true = white
|
27
|
+
# false = black
|
28
|
+
def cvar(x,y)
|
29
|
+
return unless on_board?(x,y)
|
30
|
+
Z3.Bool("c[#{x},#{y}]")
|
31
|
+
end
|
32
|
+
|
33
|
+
# true - connected
|
34
|
+
# right from (x,y)
|
35
|
+
def hvar(x,y)
|
36
|
+
return unless on_board?(x,y) and x != @xsize - 1
|
37
|
+
Z3.Bool("h[#{x},#{y}]")
|
38
|
+
end
|
39
|
+
|
40
|
+
# true - connected
|
41
|
+
# down from (x,y)
|
42
|
+
def vvar(x,y)
|
43
|
+
return unless on_board?(x,y) and y != @ysize - 1
|
44
|
+
Z3.Bool("v[#{x},#{y}]")
|
45
|
+
end
|
46
|
+
|
47
|
+
# Every cell has a number that shows its position within the loop
|
48
|
+
def nvar(x,y)
|
49
|
+
Z3.Int("n[#{x},#{y}]")
|
50
|
+
end
|
51
|
+
|
52
|
+
def hint_cell?(x,y)
|
53
|
+
!!@data[y][x]
|
54
|
+
end
|
55
|
+
|
56
|
+
def neighbours(x,y)
|
57
|
+
[
|
58
|
+
[x+1, y ],
|
59
|
+
[x-1, y ],
|
60
|
+
[x, y+1],
|
61
|
+
[x, y-1],
|
62
|
+
].select{|nx,ny|
|
63
|
+
on_board?(nx, ny) and !hint_cell?(nx,ny)
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def neighbour_cvars(x,y)
|
68
|
+
neighbours(x,y)
|
69
|
+
.map{|nx,ny| cvar(nx, ny)}
|
70
|
+
end
|
71
|
+
|
72
|
+
def connections_at(x,y)
|
73
|
+
[
|
74
|
+
hvar(x,y),
|
75
|
+
hvar(x-1,y),
|
76
|
+
vvar(x,y),
|
77
|
+
vvar(x,y-1),
|
78
|
+
].compact
|
79
|
+
end
|
80
|
+
|
81
|
+
def count_loops_at(x,y)
|
82
|
+
Z3.Add(*connections_at(x,y).map{|n| n.ite(1,0)})
|
83
|
+
end
|
84
|
+
|
85
|
+
def assert_connections
|
86
|
+
each_xy do |x,y|
|
87
|
+
if hint_cell?(x, y)
|
88
|
+
@solver.assert count_loops_at(x,y) == 0
|
89
|
+
else
|
90
|
+
@solver.assert cvar(x,y).implies(count_loops_at(x,y) == 2)
|
91
|
+
@solver.assert (~cvar(x,y)).implies(count_loops_at(x,y) == 0)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def assert_no_black_multiples
|
97
|
+
each_xy do |x,y|
|
98
|
+
next if hint_cell?(x, y)
|
99
|
+
@solver.assert (~cvar(x,y)).implies(Z3.And(*neighbour_cvars(x,y)))
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def stripe_left(x0,y)
|
104
|
+
(0...x0).map{|x| cvar(x,y) unless hint_cell?(x,y) }.compact
|
105
|
+
end
|
106
|
+
|
107
|
+
def stripe_right(x0,y)
|
108
|
+
(x0+1...@xsize).map{|x| cvar(x,y) unless hint_cell?(x,y) }.compact
|
109
|
+
end
|
110
|
+
|
111
|
+
def stripe_up(x,y0)
|
112
|
+
(0...y0).map{|y| cvar(x,y) unless hint_cell?(x,y) }.compact
|
113
|
+
end
|
114
|
+
|
115
|
+
def stripe_down(x,y0)
|
116
|
+
(y0+1...@ysize).map{|y| cvar(x,y) unless hint_cell?(x,y) }.compact
|
117
|
+
end
|
118
|
+
|
119
|
+
def assert_arrows
|
120
|
+
each_xy do |x,y|
|
121
|
+
if hint_cell?(x, y)
|
122
|
+
count = @data[y][x][0].to_i
|
123
|
+
dir = @data[y][x][1]
|
124
|
+
case dir
|
125
|
+
when "<", "←"
|
126
|
+
stripe = stripe_left(x,y)
|
127
|
+
when ">", "→"
|
128
|
+
stripe = stripe_right(x,y)
|
129
|
+
when "^", "↑"
|
130
|
+
stripe = stripe_up(x,y)
|
131
|
+
when "_", "↓"
|
132
|
+
stripe = stripe_down(x,y)
|
133
|
+
else
|
134
|
+
raise
|
135
|
+
end
|
136
|
+
|
137
|
+
@solver.assert Z3.Add(*stripe.map{|c| c.ite(0,1)}) == count
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def each_xy
|
143
|
+
@ysize.times do |y|
|
144
|
+
@xsize.times do |x|
|
145
|
+
yield(x,y)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def connections_and_nvars(x,y)
|
151
|
+
[
|
152
|
+
[x+1, y, hvar(x, y)],
|
153
|
+
[x-1, y, hvar(x-1, y)],
|
154
|
+
[x, y+1, vvar(x, y)],
|
155
|
+
[x, y-1, vvar(x, y-1)],
|
156
|
+
].select{|nx,ny,convar|
|
157
|
+
on_board?(nx, ny) and !hint_cell?(nx,ny)
|
158
|
+
}.map{|nx,ny,convar| [nvar(nx,ny), convar] }
|
159
|
+
end
|
160
|
+
|
161
|
+
# Loop will have Ns of 0+, all distinct and consecutive
|
162
|
+
# Non-loop will have Ns of MAX + cell_number
|
163
|
+
def assert_single_loop
|
164
|
+
# max_value = @xsize * @ysize + 10
|
165
|
+
max_value = 1000
|
166
|
+
|
167
|
+
each_xy do |x,y|
|
168
|
+
cell_idx = x + y * @xsize
|
169
|
+
@solver.assert nvar(x,y) >= 0
|
170
|
+
if hint_cell?(x,y)
|
171
|
+
@solver.assert nvar(x,y) == max_value + cell_idx
|
172
|
+
else
|
173
|
+
@solver.assert (~cvar(x,y)).implies( nvar(x,y) == max_value + cell_idx )
|
174
|
+
|
175
|
+
nval = connections_and_nvars(x,y)
|
176
|
+
.map{|n,c| c.ite(n+1, max_value) }
|
177
|
+
.reduce{|a,b| (a <= b).ite(a, b) }
|
178
|
+
|
179
|
+
@solver.assert cvar(x,y).implies((nvar(x,y) == 0) | (nvar(x,y) == nval) )
|
180
|
+
@solver.assert cvar(x,y).implies(nvar(x,y) < max_value)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# only one loop start
|
185
|
+
@solver.assert Z3.Add(
|
186
|
+
*enum_for(:each_xy).map{|x,y|
|
187
|
+
(nvar(x,y) == 0).ite(1,0)
|
188
|
+
}) == 1
|
189
|
+
end
|
190
|
+
|
191
|
+
def call
|
192
|
+
assert_connections
|
193
|
+
assert_no_black_multiples
|
194
|
+
assert_arrows
|
195
|
+
assert_single_loop
|
196
|
+
|
197
|
+
if @solver.satisfiable?
|
198
|
+
@model = @solver.model
|
199
|
+
print_answer
|
200
|
+
else
|
201
|
+
puts "failed to solve"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def hint_data(x,y)
|
206
|
+
u = @data[y][x]
|
207
|
+
n = u[0]
|
208
|
+
a = {
|
209
|
+
"<" => "←",
|
210
|
+
">" => "→",
|
211
|
+
"_" => "↓",
|
212
|
+
"^" => "↑",
|
213
|
+
}[u[1]] || u[1]
|
214
|
+
n + a
|
215
|
+
end
|
216
|
+
|
217
|
+
# TODO: make it nicer
|
218
|
+
def print_answer
|
219
|
+
@ysize.times do |y|
|
220
|
+
@xsize.times do |x|
|
221
|
+
if hint_cell?(x,y)
|
222
|
+
print hint_data(x,y)
|
223
|
+
# print "O "
|
224
|
+
else
|
225
|
+
c = @model[cvar(x,y)].to_b
|
226
|
+
if c
|
227
|
+
print "* "
|
228
|
+
else
|
229
|
+
print "# "
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
next if x == @xsize - 1
|
234
|
+
if @model[hvar(x,y)].to_b
|
235
|
+
print "-"
|
236
|
+
else
|
237
|
+
print " "
|
238
|
+
end
|
239
|
+
end
|
240
|
+
print "\n"
|
241
|
+
|
242
|
+
next if y == @ysize-1
|
243
|
+
@xsize.times do |x|
|
244
|
+
if @model[vvar(x,y)].to_b
|
245
|
+
print "| "
|
246
|
+
else
|
247
|
+
print " "
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
print "\n"
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def print_debug_loop_info
|
256
|
+
print "\n"
|
257
|
+
@ysize.times do |y|
|
258
|
+
@xsize.times do |x|
|
259
|
+
n = @model[nvar(x,y)].to_i
|
260
|
+
print "%8d" % n
|
261
|
+
end
|
262
|
+
print "\n"
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
path = ARGV[0] || Pathname(__dir__) + "yajilin-1.txt"
|
268
|
+
Yajilin.new(path).call
|
data/lib/z3/ast.rb
CHANGED
File without changes
|