z3 0.0.20180629 → 0.0.20220203

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -4
  3. data/Rakefile +15 -8
  4. data/examples/abc_path +187 -0
  5. data/examples/abc_path-1.txt +7 -0
  6. data/examples/algebra_problems +12 -12
  7. data/examples/aquarium +133 -0
  8. data/examples/aquarium-1.txt +11 -0
  9. data/examples/bridges +2 -2
  10. data/examples/cats_organized_neatly +133 -0
  11. data/examples/cats_organized_neatly-10.txt +15 -0
  12. data/examples/cats_organized_neatly-3.txt +8 -0
  13. data/examples/cats_organized_neatly-48.txt +32 -0
  14. data/examples/circuit_problems +4 -4
  15. data/examples/clogic_puzzle +2 -2
  16. data/examples/color_nonogram +150 -0
  17. data/examples/color_nonogram-1.txt +23 -0
  18. data/examples/crossflip +2 -4
  19. data/examples/dominion +153 -0
  20. data/examples/dominion-1.txt +8 -0
  21. data/examples/dominosa +133 -0
  22. data/examples/dominosa-1.txt +8 -0
  23. data/examples/eulero +99 -0
  24. data/examples/eulero-1.txt +5 -0
  25. data/examples/four_hackers_puzzle +2 -2
  26. data/examples/futoshiki +128 -0
  27. data/examples/futoshiki-1.txt +17 -0
  28. data/examples/kakurasu +73 -0
  29. data/examples/kakurasu-1.txt +2 -0
  30. data/examples/kakuro +2 -2
  31. data/examples/killer_sudoku +88 -0
  32. data/examples/killer_sudoku-1.txt +17 -0
  33. data/examples/killer_sudoku-2.txt +53 -0
  34. data/examples/kinematics_problems +20 -20
  35. data/examples/knights_puzzle +2 -2
  36. data/examples/kropki +100 -0
  37. data/examples/kropki-1.txt +13 -0
  38. data/examples/letter_connections +2 -2
  39. data/examples/light_up +2 -2
  40. data/examples/minisudoku +2 -2
  41. data/examples/miracle_sudoku +135 -0
  42. data/examples/miracle_sudoku-1.txt +9 -0
  43. data/examples/mortal_coil_puzzle +2 -2
  44. data/examples/nanro +245 -0
  45. data/examples/nanro-1.txt +8 -0
  46. data/examples/nine_clocks +106 -0
  47. data/examples/nonogram +2 -2
  48. data/examples/pyramid_nonogram +2 -2
  49. data/examples/regexp_crossword_solver +2 -2
  50. data/examples/regexp_solver +2 -2
  51. data/examples/renzoku +124 -0
  52. data/examples/renzoku-1.txt +17 -0
  53. data/examples/sandwich_sudoku +101 -0
  54. data/examples/sandwich_sudoku-1.txt +10 -0
  55. data/examples/selfref +2 -2
  56. data/examples/simple_regexp_parser.rb +58 -56
  57. data/examples/skyscrapers +118 -0
  58. data/examples/skyscrapers-1.txt +6 -0
  59. data/examples/skyscrapers-2.txt +11 -0
  60. data/examples/star_battle +134 -0
  61. data/examples/star_battle-1.txt +10 -0
  62. data/examples/stitches +180 -0
  63. data/examples/stitches-1.txt +11 -0
  64. data/examples/sudoku +2 -2
  65. data/examples/suguru +199 -0
  66. data/examples/suguru-1.txt +17 -0
  67. data/examples/verbal_arithmetic +2 -2
  68. data/examples/yajilin +268 -0
  69. data/examples/yajilin-1.txt +10 -0
  70. data/lib/z3/ast.rb +8 -0
  71. data/lib/z3/expr/expr.rb +16 -15
  72. data/lib/z3/low_level.rb +6 -2
  73. data/lib/z3/low_level_auto.rb +180 -36
  74. data/lib/z3/optimize.rb +4 -4
  75. data/lib/z3/very_low_level.rb +8 -5
  76. data/lib/z3/very_low_level_auto.rb +45 -9
  77. data/spec/expr_spec.rb +62 -0
  78. data/spec/integration/abc_path_spec.rb +21 -0
  79. data/spec/integration/aquarium_spec.rb +27 -0
  80. data/spec/integration/cats_organized_neatly_spec.rb +14 -0
  81. data/spec/integration/color_nonogram_spec.rb +28 -0
  82. data/spec/integration/dominion_spec.rb +14 -0
  83. data/spec/integration/dominosa_spec.rb +21 -0
  84. data/spec/integration/eulero_spec.rb +11 -0
  85. data/spec/integration/futoshiki_spec.rb +23 -0
  86. data/spec/integration/kakurasu_spec.rb +18 -0
  87. data/spec/integration/killer_sudoku_spec.rb +10 -0
  88. data/spec/integration/knights_puzzle_spec.rb +11 -11
  89. data/spec/integration/kropki_spec.rb +19 -0
  90. data/spec/integration/miracle_sudoku_spec.rb +15 -0
  91. data/spec/integration/mortal_coil_puzzle_spec.rb +8 -6
  92. data/spec/integration/nanro_spec.rb +39 -0
  93. data/spec/integration/nine_clocks_spec.rb +30 -0
  94. data/spec/integration/oneofus_spec.rb +7 -15
  95. data/spec/integration/regexp_crossword_solver_spec.rb +1 -1
  96. data/spec/integration/renzoku_spec.rb +23 -0
  97. data/spec/integration/sandwich_sudoku_spec.rb +15 -0
  98. data/spec/integration/skyscraper_spec.rb +10 -0
  99. data/spec/integration/star_battle_spec.rb +27 -0
  100. data/spec/integration/stitches_spec.rb +25 -0
  101. data/spec/integration/suguru_spec.rb +23 -0
  102. data/spec/integration/yajilin_spec.rb +25 -0
  103. data/spec/optimize_spec.rb +6 -4
  104. data/spec/set_expr_spec.rb +14 -8
  105. data/spec/solver_spec.rb +4 -5
  106. data/spec/spec_helper.rb +15 -0
  107. metadata +105 -25
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "pathname"
4
+ require_relative "../lib/z3"
5
+
6
+ class Futoshiki
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 cmp_right(x, y)
26
+ v = @data[y*2][x*2 + 1]
27
+ if v == ">"
28
+ ">"
29
+ elsif v == "<"
30
+ "<"
31
+ elsif v == " "
32
+ nil
33
+ else
34
+ raise "Bad dot value"
35
+ end
36
+ end
37
+
38
+ def cmp_bottom(x, y)
39
+ v = @data[y*2 + 1][x*2]
40
+ if v == "_"
41
+ ">"
42
+ elsif v == "^"
43
+ "<"
44
+ elsif v == " " or v == nil
45
+ false
46
+ else
47
+ raise "Bad dot value"
48
+ end
49
+ end
50
+
51
+ def call
52
+ @vars = {}
53
+ @size.times do |y|
54
+ @size.times do |x|
55
+ v = Z3.Int("v#{x},#{y}")
56
+ @vars[[x,y]] = v
57
+ cv = cell_value(x, y)
58
+ if cv
59
+ @solver.assert (v == cv)
60
+ else
61
+ @solver.assert (v >= 1) & (v <= @size)
62
+ end
63
+ end
64
+ end
65
+
66
+ @size.times do |x|
67
+ line = @size.times.map{|y| @vars[[x,y]] }
68
+ @solver.assert Z3.Distinct(*line)
69
+ end
70
+
71
+ @size.times do |y|
72
+ line = @size.times.map{|x| @vars[[x,y]] }
73
+ @solver.assert Z3.Distinct(*line)
74
+ end
75
+
76
+ # Compare right
77
+ (0..@size-1).each do |y|
78
+ (0..@size-2).each do |x|
79
+ lv = @vars[[x,y]]
80
+ rv = @vars[[x+1,y]]
81
+ cmp = cmp_right(x,y)
82
+ if cmp == ">"
83
+ @solver.assert lv > rv
84
+ elsif cmp == "<"
85
+ @solver.assert lv < rv
86
+ end
87
+ end
88
+ end
89
+
90
+ # Compare bottom
91
+ (0..@size-2).each do |y|
92
+ (0..@size-1).each do |x|
93
+ tv = @vars[[x,y]]
94
+ bv = @vars[[x,y+1]]
95
+ cmp = cmp_bottom(x,y)
96
+ if cmp == ">"
97
+ @solver.assert tv > bv
98
+ elsif cmp == "<"
99
+ @solver.assert tv < bv
100
+ end
101
+ end
102
+ end
103
+
104
+ if @solver.satisfiable?
105
+ @model = @solver.model
106
+ print_answer!
107
+ else
108
+ puts "failed to solve"
109
+ end
110
+ end
111
+
112
+ private
113
+
114
+ def print_answer!
115
+ output = @data.map(&:dup)
116
+ @size.times do |y|
117
+ @size.times do |x|
118
+ v = @model[@vars[[x,y]]]
119
+ output[2*y][2*x] = "#{v}"
120
+ end
121
+ end
122
+
123
+ puts output
124
+ end
125
+ end
126
+
127
+ path = ARGV[0] || Pathname(__dir__) + "futoshiki-1.txt"
128
+ Futoshiki.new(path).call
@@ -0,0 +1,17 @@
1
+ # # # # # # #<# #
2
+
3
+ 1 # # # # 5 #<#<6
4
+ _
5
+ 7<# 2 5>#># #<# #
6
+ ^
7
+ # #<#<# # # # # #
8
+ ^
9
+ # #>#># # # 5 # 3
10
+ ^ ^ ^
11
+ # # # # # 3 # # #
12
+ _ _ ^ _ _
13
+ # # # #># # # # #
14
+ _
15
+ 5 # #>3 #># #># #
16
+ _
17
+ # # #>#># 9 #<2 5
data/examples/kakurasu ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "pathname"
4
+ require_relative "../lib/z3"
5
+
6
+ class Kakurasu
7
+ def initialize(path)
8
+ data = Pathname(path).readlines.grep(/\S/)
9
+ raise "Data needs to have two lines (cols, rows)" unless data.size == 2
10
+ @col_sums = data[0].split.map(&:to_i)
11
+ @row_sums = data[1].split.map(&:to_i)
12
+
13
+ @xsize = @col_sums.size
14
+ @ysize = @row_sums.size
15
+ @solver = Z3::Solver.new
16
+ end
17
+
18
+ def call
19
+ # setup
20
+ @bvars = {}
21
+ @cvars = {}
22
+ @rvars = {}
23
+ @ysize.times do |y|
24
+ @ysize.times do |x|
25
+ b = Z3.Bool("b#{x},#{y}")
26
+ r = Z3.Int("r#{x},#{y}")
27
+ c = Z3.Int("c#{x},#{y}")
28
+ @solver.assert (!b).implies(r == 0)
29
+ @solver.assert (!b).implies(c == 0)
30
+ @solver.assert b.implies(r == (x+1))
31
+ @solver.assert b.implies(c == (y+1))
32
+ @bvars[[x,y]] = b
33
+ @cvars[x] ||= []
34
+ @cvars[x][y] = c
35
+ @rvars[y] ||= []
36
+ @rvars[y][x] = r
37
+ end
38
+ end
39
+
40
+ @xsize.times do |x|
41
+ @solver.assert Z3.Add(*@rvars[x]) == @row_sums[x]
42
+ end
43
+
44
+ @ysize.times do |y|
45
+ @solver.assert Z3.Add(*@cvars[y]) == @col_sums[y]
46
+ end
47
+
48
+ if @solver.satisfiable?
49
+ @model = @solver.model
50
+ print_board!
51
+ else
52
+ puts "failed to solve"
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def print_board!
59
+ @ysize.times do |y|
60
+ @ysize.times do |x|
61
+ if @model[@bvars[[x,y]]].to_b
62
+ print "[X]"
63
+ else
64
+ print "[ ]"
65
+ end
66
+ end
67
+ print "\n"
68
+ end
69
+ end
70
+ end
71
+
72
+ path = ARGV[0] || Pathname(__dir__) + "kakurasu-1.txt"
73
+ Kakurasu.new(path).call
@@ -0,0 +1,2 @@
1
+ 7 14 35 5 22 22 56 10 37 2 69 21
2
+ 10 39 16 58 2 22 37 30 14 32 33 35
data/examples/kakuro CHANGED
@@ -25,7 +25,7 @@ class Kakuro
25
25
  @solver = Z3::Solver.new
26
26
  end
27
27
 
28
- def solve!
28
+ def call
29
29
  @cells = map_coordinates{|x,y| cell_var(x,y) if @data[[x,y]] == nil}.compact
30
30
 
31
31
  (0...@ysize).each do |y|
@@ -111,4 +111,4 @@ class Kakuro
111
111
  end
112
112
 
113
113
  path = ARGV[0] || Pathname(__dir__) + "kakuro-1.txt"
114
- Kakuro.new(path).solve!
114
+ Kakuro.new(path).call
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "pathname"
4
+ require_relative "../lib/z3"
5
+
6
+ class KillerSudokuSolver
7
+ def initialize(path)
8
+ data = Pathname(path).read
9
+ knowns, cages, cage_counts = data.split(/\n{2,}/)
10
+ @knowns = knowns.strip.split("\n").map do |line|
11
+ line.split.map{|c| c == "." ? nil : c.to_i}
12
+ end
13
+ @cages = cages.strip.split("\n").map do |line|
14
+ line.split
15
+ end
16
+ @size = @knowns.size
17
+ raise "Bad size" unless @knowns.all?{|line| line.size == @size}
18
+ raise "Bad size" unless @cages.size == @size
19
+ raise "Bad size" unless @cages.all?{|line| line.size == @size}
20
+ @cage_counts = cage_counts.split("\n").to_h(&:split).transform_values(&:to_i)
21
+ @solver = Z3::Solver.new
22
+ if @size == 9
23
+ @boxsize = 3
24
+ elsif @size == 4
25
+ @boxsize = 2
26
+ else
27
+ raise "Bad size"
28
+ end
29
+ end
30
+
31
+ def call
32
+ @cells = (0...@size).map do |j|
33
+ (0...@size).map do |i|
34
+ cell_var(@knowns[j][i], i, j)
35
+ end
36
+ end
37
+
38
+ @cells.each do |row|
39
+ @solver.assert Z3.Distinct(*row)
40
+ end
41
+ @cells.transpose.each do |column|
42
+ @solver.assert Z3.Distinct(*column)
43
+ end
44
+ @cells.each_slice(@boxsize) do |rows|
45
+ rows.transpose.each_slice(@boxsize) do |square|
46
+ @solver.assert Z3.Distinct(*square.flatten)
47
+ end
48
+ end
49
+
50
+ by_cage = {}
51
+ (0...@size).each do |j|
52
+ (0...@size).each do |i|
53
+ c = @cages[j][i]
54
+ by_cage[c] ||= []
55
+ by_cage[c] << @cells[j][i]
56
+ end
57
+ end
58
+
59
+ by_cage.each do |cage_name, cells|
60
+ @solver.assert Z3.Add(*cells) == @cage_counts.fetch(cage_name)
61
+ @solver.assert Z3.Distinct(*cells)
62
+ end
63
+
64
+ if @solver.satisfiable?
65
+ @model = @solver.model
66
+ print_answer!
67
+ else
68
+ puts "failed to solve"
69
+ end
70
+ end
71
+
72
+ def cell_var(cell, i, j)
73
+ v = Z3.Int("cell[#{i+1},#{j+1}]")
74
+ @solver.assert v >= 1
75
+ @solver.assert v <= @size
76
+ @solver.assert v == cell if cell != nil
77
+ v
78
+ end
79
+
80
+ def print_answer!
81
+ @cells.each do |row|
82
+ puts row.map{|v| @model[v]}.join(" ")
83
+ end
84
+ end
85
+ end
86
+
87
+ path = ARGV[0] || Pathname(__dir__) + "killer_sudoku-1.txt"
88
+ KillerSudokuSolver.new(path).call
@@ -0,0 +1,17 @@
1
+ . . . .
2
+ . . . .
3
+ . . . .
4
+ . . . .
5
+
6
+ a b c c
7
+ a b d e
8
+ a d d e
9
+ f f g g
10
+
11
+ a 9
12
+ b 3
13
+ c 4
14
+ d 7
15
+ e 7
16
+ f 4
17
+ g 6
@@ -0,0 +1,53 @@
1
+ . . . . . . . . .
2
+ . . . . . . . . .
3
+ . . . . . . . . .
4
+ . . . . . . . . .
5
+ . . . . . . . . .
6
+ . . . . . . . . .
7
+ . . . . . . . . .
8
+ . . . . . . . . .
9
+ . . . . . . . . .
10
+
11
+ a a b c d d d e e
12
+ f g b c v v x y z
13
+ f g g u u w x y z
14
+ f s s t t w A A B
15
+ h h C C C D D A B
16
+ i i G G F E E r q
17
+ j j m G F n o r q
18
+ k m m F F n o p q
19
+ k l l n n n p p p
20
+
21
+ a 9
22
+ b 8
23
+ c 14
24
+ d 13
25
+ e 15
26
+ f 15
27
+ g 18
28
+ h 8
29
+ i 9
30
+ j 12
31
+ k 10
32
+ l 8
33
+ m 15
34
+ n 26
35
+ o 13
36
+ p 19
37
+ q 12
38
+ r 12
39
+ s 10
40
+ t 10
41
+ u 11
42
+ v 13
43
+ w 11
44
+ x 10
45
+ y 5
46
+ z 7
47
+ A 12
48
+ B 11
49
+ C 13
50
+ D 12
51
+ E 9
52
+ F 17
53
+ G 18
@@ -31,7 +31,7 @@ class KinematicsProblem01 < KinematicsProblem
31
31
  An airplane accelerates down a runway at 3.20 m/s2 for 32.8 s until is finally lifts off the ground.
32
32
  Determine the distance traveled before takeoff.
33
33
  """
34
- def solve!
34
+ def call
35
35
  a = Z3.Real("a")
36
36
  t = Z3.Real("t")
37
37
  d = Z3.Real("d")
@@ -46,7 +46,7 @@ class KinematicsProblem02 < KinematicsProblem
46
46
  """
47
47
  A car starts from rest and accelerates uniformly over a time of 5.21 seconds for a distance of 110 m. Determine the acceleration of the car.
48
48
  """
49
- def solve!
49
+ def call
50
50
  a = Z3.Real("a")
51
51
  t = Z3.Real("t")
52
52
  d = Z3.Real("d")
@@ -62,7 +62,7 @@ class KinematicsProblem03 < KinematicsProblem
62
62
  Upton Chuck is riding the Giant Drop at Great America.
63
63
  If Upton free falls for 2.60 seconds, what will be his final velocity and how far will he fall?
64
64
  """
65
- def solve!
65
+ def call
66
66
  a = Z3.Real("a")
67
67
  t = Z3.Real("t")
68
68
  v = Z3.Real("v")
@@ -79,7 +79,7 @@ class KinematicsProblem04 < KinematicsProblem
79
79
  """
80
80
  A race car accelerates uniformly from 18.5 m/s to 46.1 m/s in 2.47 seconds. Determine the acceleration of the car and the distance traveled.
81
81
  """
82
- def solve!
82
+ def call
83
83
  a = Z3.Real("a")
84
84
  t = Z3.Real("t")
85
85
  vs= Z3.Real("vs")
@@ -100,7 +100,7 @@ class KinematicsProblem05 < KinematicsProblem
100
100
  The acceleration of gravity on the moon is 1.67 m/s2.
101
101
  Determine the time for the feather to fall to the surface of the moon.
102
102
  """
103
- def solve!
103
+ def call
104
104
  a = Z3.Real("a")
105
105
  t = Z3.Real("t")
106
106
  d = Z3.Real("d")
@@ -118,7 +118,7 @@ class KinematicsProblem06 < KinematicsProblem
118
118
  If a rocket-powered sled is accelerated to a speed of 444 m/s in 1.83 seconds,
119
119
  then what is the acceleration and what is the distance that the sled travels?
120
120
  """
121
- def solve!
121
+ def call
122
122
  a = Z3.Real("a")
123
123
  t = Z3.Real("t")
124
124
  d = Z3.Real("d")
@@ -136,7 +136,7 @@ class KinematicsProblem07 < KinematicsProblem
136
136
  A bike accelerates uniformly from rest to a speed of 7.10 m/s over a distance of 35.4 m.
137
137
  Determine the acceleration of the bike.
138
138
  """
139
- def solve!
139
+ def call
140
140
  a = Z3.Real("a")
141
141
  t = Z3.Real("t")
142
142
  d = Z3.Real("d")
@@ -156,7 +156,7 @@ class KinematicsProblem08 < KinematicsProblem
156
156
  The takeoff speed for this plane will be 65 m/s.
157
157
  Assuming this minimum acceleration, what is the minimum allowed length for the runway?
158
158
  """
159
- def solve!
159
+ def call
160
160
  a = Z3.Real("a")
161
161
  t = Z3.Real("t")
162
162
  d = Z3.Real("d")
@@ -174,7 +174,7 @@ class KinematicsProblem09 < KinematicsProblem
174
174
  A car traveling at 22.4 m/s skids to a stop in 2.55 s.
175
175
  Determine the skidding distance of the car (assume uniform acceleration).
176
176
  """
177
- def solve!
177
+ def call
178
178
  t = Z3.Real("t")
179
179
  d = Z3.Real("d")
180
180
  v = Z3.Real("v")
@@ -190,7 +190,7 @@ class KinematicsProblem10 < KinematicsProblem
190
190
  A kangaroo is capable of jumping to a height of 2.62 m.
191
191
  Determine the takeoff speed of the kangaroo.
192
192
  """
193
- def solve!
193
+ def call
194
194
  a = Z3.Real("a")
195
195
  t = Z3.Real("t")
196
196
  d = Z3.Real("d")
@@ -207,13 +207,13 @@ end
207
207
  # This is actually param for Python Z3 printer, can't work until we get that
208
208
  # Z3.set_param("rational_to_decimal", true)
209
209
 
210
- KinematicsProblem01.new.solve!
211
- KinematicsProblem02.new.solve!
212
- KinematicsProblem03.new.solve!
213
- KinematicsProblem04.new.solve!
214
- KinematicsProblem05.new.solve!
215
- KinematicsProblem06.new.solve!
216
- KinematicsProblem07.new.solve!
217
- KinematicsProblem08.new.solve!
218
- KinematicsProblem09.new.solve!
219
- KinematicsProblem10.new.solve!
210
+ KinematicsProblem01.new.call
211
+ KinematicsProblem02.new.call
212
+ KinematicsProblem03.new.call
213
+ KinematicsProblem04.new.call
214
+ KinematicsProblem05.new.call
215
+ KinematicsProblem06.new.call
216
+ KinematicsProblem07.new.call
217
+ KinematicsProblem08.new.call
218
+ KinematicsProblem09.new.call
219
+ KinematicsProblem10.new.call
@@ -39,7 +39,7 @@ class KnightsPuzzle
39
39
  puts ""
40
40
  end
41
41
 
42
- def solve!
42
+ def call
43
43
  if @solver.satisfiable?
44
44
  @model = @solver.model
45
45
  puts "Solved"
@@ -119,4 +119,4 @@ class KnightsPuzzle
119
119
  end
120
120
  end
121
121
 
122
- KnightsPuzzle.new.solve!
122
+ KnightsPuzzle.new.call
data/examples/kropki ADDED
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env ruby
2
+
3
+
4
+ require "pathname"
5
+ require_relative "../lib/z3"
6
+
7
+ # Based on https://play.google.com/store/apps/details?id=com.alexuvarov.android.kropki.puzzle&hl=en_GB
8
+ #
9
+ # NxN grid
10
+ # Each cell contains numbers 1 to N
11
+ # Each row and each column contains distinct numbers
12
+ # If there's black dot (o) between cells, one is twice the other
13
+ # If there's white dot (*) between cells, one is the other plus one
14
+ # If there's no dot, neither of these is true
15
+ # (for 1/2, dot can be of either color)
16
+
17
+ class Kropki
18
+ def initialize(path)
19
+ @data = Pathname(path).readlines.map(&:chomp)
20
+ @size = (@data.size - 1) / 2
21
+ raise unless @data.size == 2 * @size + 1
22
+ raise unless @data.all?{ |row| row.size == 2 * @size + 1 }
23
+ @solver = Z3::Solver.new
24
+ end
25
+
26
+ def call
27
+ # Cells contain numbers 1 - N
28
+ @size.times do |y|
29
+ @size.times do |x|
30
+ @solver.assert cell(x, y) >= 1
31
+ @solver.assert cell(x, y) <= @size
32
+ end
33
+ end
34
+
35
+ # Each row and column distinct
36
+ @size.times do |y|
37
+ @solver.assert Z3.Distinct(*@size.times.map{ |x| cell(x,y) })
38
+ end
39
+ @size.times do |x|
40
+ @solver.assert Z3.Distinct(*@size.times.map{ |y| cell(x,y) })
41
+ end
42
+
43
+ # Horizontal dots
44
+ @size.times do |y|
45
+ (0...@size).each do |x|
46
+ dot_constraints cell(x, y), cell(x+1, y), @data[2*y+1][2*x+2]
47
+ end
48
+ end
49
+
50
+ # Vertical dots
51
+ @size.times do |x|
52
+ (0...@size).each do |y|
53
+ dot_constraints cell(x, y), cell(x, y+1), @data[2*y+2][2*x+1]
54
+ end
55
+ end
56
+
57
+ if @solver.satisfiable?
58
+ @model = @solver.model
59
+ print_answer
60
+ else
61
+ puts "failed to solve"
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def dot_constraints(c1, c2, dot)
68
+ if dot == "o"
69
+ @solver.assert Z3.Or(c1 == c2 + 1, c2 == c1 + 1)
70
+ elsif dot == "*"
71
+ @solver.assert Z3.Or(c1 == c2 * 2, c2 == c1 * 2)
72
+ else
73
+ @solver.assert c1 != c2 + 1
74
+ @solver.assert c2 != c1 + 1
75
+ @solver.assert c1 != c2 * 2
76
+ @solver.assert c2 != c1 * 2
77
+ end
78
+ end
79
+
80
+ def cell(x, y)
81
+ Z3.Int("c#{x},#{y}")
82
+ end
83
+
84
+ def print_answer
85
+ output = @data.map(&:dup)
86
+ @size.times do |y|
87
+ @size.times do |x|
88
+ # This only works for 1-9, could use hex or something for more
89
+ value = @model[cell(x,y)].to_s
90
+ output[2*y+1][2*x+1, 1] = value
91
+ end
92
+ end
93
+
94
+ puts output
95
+ end
96
+ end
97
+
98
+
99
+ path = ARGV[0] || Pathname(__dir__) + "kropki-1.txt"
100
+ Kropki.new(path).call
@@ -0,0 +1,13 @@
1
+ +-+-+-+-+-+-+
2
+ | | * | * o |
3
+ +-+-+-+*+o+*+
4
+ | | o | o * |
5
+ +-+-+o+o+-+-+
6
+ | o | | o | |
7
+ +*+o+-+-+o+-+
8
+ | * o o | * |
9
+ +-+-+-+o+-+-+
10
+ | | | | | * |
11
+ +*+-+-+-+o+*+
12
+ | * o | | o |
13
+ +-+-+-+-+-+-+
@@ -61,7 +61,7 @@ class LetterConnections
61
61
  @solver = Z3::Solver.new
62
62
  end
63
63
 
64
- def solve!
64
+ def call
65
65
  @line = map_coordinates{|x,y| line_var(x,y) }
66
66
  @dir = map_coordinates{|x,y| dir_var(x,y) }
67
67
 
@@ -187,4 +187,4 @@ class LetterConnections
187
187
  end
188
188
 
189
189
  path = ARGV[0] || Pathname(__dir__) + "letter_connections-1.txt"
190
- LetterConnections.new(path).solve!
190
+ LetterConnections.new(path).call
data/examples/light_up CHANGED
@@ -13,7 +13,7 @@ class LightUp
13
13
  @solver = Z3::Solver.new
14
14
  end
15
15
 
16
- def solve!
16
+ def call
17
17
  @lamps = map_coordinates{|x,y| int01(x,y)}
18
18
 
19
19
  # No lamps on walls
@@ -95,4 +95,4 @@ class LightUp
95
95
  end
96
96
 
97
97
  path = ARGV[0] || Pathname(__dir__) + "light_up-1.txt"
98
- LightUp.new(path).solve!
98
+ LightUp.new(path).call
data/examples/minisudoku CHANGED
@@ -13,7 +13,7 @@ class MiniSudokuSolver
13
13
  @solver = Z3::Solver.new
14
14
  end
15
15
 
16
- def solve!
16
+ def call
17
17
  @cells = (0..5).map do |j|
18
18
  (0..5).map do |i|
19
19
  cell_var(@data[j][i], i, j)
@@ -58,4 +58,4 @@ class MiniSudokuSolver
58
58
  end
59
59
 
60
60
  path = ARGV[0] || Pathname(__dir__) + "minisudoku-1.txt"
61
- MiniSudokuSolver.new(path).solve!
61
+ MiniSudokuSolver.new(path).call