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.
Files changed (210) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -0
  3. data/README.md +0 -2
  4. data/Rakefile +8 -1
  5. data/examples/abc_path +187 -0
  6. data/examples/abc_path-1.txt +7 -0
  7. data/examples/algebra_problems +12 -12
  8. data/examples/aquarium +133 -0
  9. data/examples/aquarium-1.txt +11 -0
  10. data/examples/bridges +2 -2
  11. data/examples/bridges-1.txt +0 -0
  12. data/examples/cats_organized_neatly +133 -0
  13. data/examples/cats_organized_neatly-10.txt +15 -0
  14. data/examples/cats_organized_neatly-3.txt +8 -0
  15. data/examples/cats_organized_neatly-48.txt +32 -0
  16. data/examples/circuit_problems +4 -4
  17. data/examples/clogic_puzzle +2 -2
  18. data/examples/color_nonogram +150 -0
  19. data/examples/color_nonogram-1.txt +23 -0
  20. data/examples/crossflip +2 -4
  21. data/examples/dominion +153 -0
  22. data/examples/dominion-1.txt +8 -0
  23. data/examples/dominosa +133 -0
  24. data/examples/dominosa-1.txt +8 -0
  25. data/examples/eulero +99 -0
  26. data/examples/eulero-1.txt +5 -0
  27. data/examples/four_hackers_puzzle +2 -2
  28. data/examples/futoshiki +128 -0
  29. data/examples/futoshiki-1.txt +17 -0
  30. data/examples/kakurasu +73 -0
  31. data/examples/kakurasu-1.txt +2 -0
  32. data/examples/kakuro +2 -2
  33. data/examples/kakuro-1.txt +0 -0
  34. data/examples/killer_sudoku +88 -0
  35. data/examples/killer_sudoku-1.txt +17 -0
  36. data/examples/killer_sudoku-2.txt +53 -0
  37. data/examples/kinematics_problems +20 -20
  38. data/examples/knights_puzzle +2 -2
  39. data/examples/kropki +100 -0
  40. data/examples/kropki-1.txt +13 -0
  41. data/examples/letter_connections +2 -2
  42. data/examples/letter_connections-1.txt +0 -0
  43. data/examples/light_up +2 -2
  44. data/examples/light_up-1.txt +0 -0
  45. data/examples/minisudoku +2 -2
  46. data/examples/minisudoku-1.txt +0 -0
  47. data/examples/miracle_sudoku +135 -0
  48. data/examples/miracle_sudoku-1.txt +9 -0
  49. data/examples/mortal_coil_puzzle +2 -2
  50. data/examples/mortal_coil_puzzle-9.txt +0 -0
  51. data/examples/nanro +245 -0
  52. data/examples/nanro-1.txt +8 -0
  53. data/examples/nine_clocks +106 -0
  54. data/examples/nonogram +2 -2
  55. data/examples/pyramid_nonogram +2 -2
  56. data/examples/regexp_crossword/beginner-1.txt +0 -0
  57. data/examples/regexp_crossword/beginner-2.txt +0 -0
  58. data/examples/regexp_crossword/beginner-3.txt +0 -0
  59. data/examples/regexp_crossword/beginner-4.txt +0 -0
  60. data/examples/regexp_crossword/beginner-5.txt +0 -0
  61. data/examples/regexp_crossword/experienced-1.txt +0 -0
  62. data/examples/regexp_crossword/experienced-2.txt +0 -0
  63. data/examples/regexp_crossword/experienced-3.txt +0 -0
  64. data/examples/regexp_crossword/experienced-4.txt +0 -0
  65. data/examples/regexp_crossword/experienced-5.txt +0 -0
  66. data/examples/regexp_crossword/tutorial-1.txt +0 -0
  67. data/examples/regexp_crossword/tutorial-2.txt +0 -0
  68. data/examples/regexp_crossword/tutorial-3.txt +0 -0
  69. data/examples/regexp_crossword/tutorial-4.txt +0 -0
  70. data/examples/regexp_crossword/tutorial-5.txt +0 -0
  71. data/examples/regexp_crossword/tutorial-6.txt +0 -0
  72. data/examples/regexp_crossword/tutorial-7.txt +0 -0
  73. data/examples/regexp_crossword/tutorial-8.txt +0 -0
  74. data/examples/regexp_crossword/tutorial-9.txt +0 -0
  75. data/examples/regexp_crossword_solver +2 -2
  76. data/examples/regexp_solver +2 -2
  77. data/examples/regexp_string_matcher.rb +0 -0
  78. data/examples/renzoku +124 -0
  79. data/examples/renzoku-1.txt +17 -0
  80. data/examples/sandwich_sudoku +101 -0
  81. data/examples/sandwich_sudoku-1.txt +10 -0
  82. data/examples/selfref +2 -2
  83. data/examples/simple_regexp_parser.rb +0 -0
  84. data/examples/skyscrapers +118 -0
  85. data/examples/skyscrapers-1.txt +6 -0
  86. data/examples/skyscrapers-2.txt +11 -0
  87. data/examples/star_battle +134 -0
  88. data/examples/star_battle-1.txt +10 -0
  89. data/examples/stitches +180 -0
  90. data/examples/stitches-1.txt +11 -0
  91. data/examples/sudoku +2 -2
  92. data/examples/sudoku-1.txt +0 -0
  93. data/examples/suguru +199 -0
  94. data/examples/suguru-1.txt +17 -0
  95. data/examples/verbal_arithmetic +2 -2
  96. data/examples/yajilin +268 -0
  97. data/examples/yajilin-1.txt +10 -0
  98. data/lib/z3/ast.rb +0 -0
  99. data/lib/z3/context.rb +0 -0
  100. data/lib/z3/exception.rb +0 -0
  101. data/lib/z3/expr/arith_expr.rb +0 -0
  102. data/lib/z3/expr/array_expr.rb +0 -0
  103. data/lib/z3/expr/bitvec_expr.rb +47 -2
  104. data/lib/z3/expr/bool_expr.rb +0 -0
  105. data/lib/z3/expr/expr.rb +3 -3
  106. data/lib/z3/expr/float_expr.rb +0 -0
  107. data/lib/z3/expr/int_expr.rb +0 -0
  108. data/lib/z3/expr/real_expr.rb +0 -0
  109. data/lib/z3/expr/rounding_mode_expr.rb +0 -0
  110. data/lib/z3/expr/set_expr.rb +0 -0
  111. data/lib/z3/func_decl.rb +0 -0
  112. data/lib/z3/goal.rb +0 -0
  113. data/lib/z3/hacks.rb +0 -0
  114. data/lib/z3/interface.rb +0 -0
  115. data/lib/z3/low_level.rb +0 -0
  116. data/lib/z3/low_level_auto.rb +138 -10
  117. data/lib/z3/model.rb +0 -0
  118. data/lib/z3/optimize.rb +0 -0
  119. data/lib/z3/printer.rb +0 -0
  120. data/lib/z3/probe.rb +0 -0
  121. data/lib/z3/solver.rb +0 -0
  122. data/lib/z3/sort/array_sort.rb +0 -0
  123. data/lib/z3/sort/bitvec_sort.rb +0 -0
  124. data/lib/z3/sort/bool_sort.rb +0 -0
  125. data/lib/z3/sort/float_sort.rb +0 -0
  126. data/lib/z3/sort/int_sort.rb +0 -0
  127. data/lib/z3/sort/real_sort.rb +0 -0
  128. data/lib/z3/sort/rounding_mode_sort.rb +0 -0
  129. data/lib/z3/sort/set_sort.rb +0 -0
  130. data/lib/z3/sort/sort.rb +0 -0
  131. data/lib/z3/tactic.rb +0 -0
  132. data/lib/z3/very_low_level.rb +5 -1
  133. data/lib/z3/very_low_level_auto.rb +35 -3
  134. data/lib/z3.rb +0 -0
  135. data/spec/array_expr_spec.rb +0 -0
  136. data/spec/array_sort_spec.rb +0 -0
  137. data/spec/bitvec_expr_spec.rb +13 -0
  138. data/spec/bitvec_sort_spec.rb +0 -0
  139. data/spec/bool_expr_spec.rb +0 -0
  140. data/spec/bool_sort_spec.rb +0 -0
  141. data/spec/coverage_helper.rb +0 -0
  142. data/spec/expr_spec.rb +0 -0
  143. data/spec/float_expr_spec.rb +0 -0
  144. data/spec/float_sort_spec.rb +0 -0
  145. data/spec/goal_spec.rb +0 -0
  146. data/spec/int_expr_spec.rb +0 -0
  147. data/spec/int_sort_spec.rb +0 -0
  148. data/spec/integration/abc_path_spec.rb +21 -0
  149. data/spec/integration/algebra_problems_spec.rb +0 -0
  150. data/spec/integration/aquarium_spec.rb +27 -0
  151. data/spec/integration/basic_int_math_spec.rb +0 -0
  152. data/spec/integration/basic_logic_spec.rb +0 -0
  153. data/spec/integration/bit_tricks_spec.rb +0 -0
  154. data/spec/integration/bridges_spec.rb +0 -0
  155. data/spec/integration/cats_organized_neatly_spec.rb +14 -0
  156. data/spec/integration/cicruit_problem_spec.rb +0 -0
  157. data/spec/integration/color_nonogram_spec.rb +28 -0
  158. data/spec/integration/crossflip_spec.rb +0 -0
  159. data/spec/integration/dominion_spec.rb +14 -0
  160. data/spec/integration/dominosa_spec.rb +21 -0
  161. data/spec/integration/eulero_spec.rb +11 -0
  162. data/spec/integration/four_hackers_puzzle_spec.rb +0 -0
  163. data/spec/integration/futoshiki_spec.rb +23 -0
  164. data/spec/integration/geometry_problem_spec.rb +0 -0
  165. data/spec/integration/kakurasu_spec.rb +18 -0
  166. data/spec/integration/kakuro_spec.rb +0 -0
  167. data/spec/integration/killer_sudoku_spec.rb +10 -0
  168. data/spec/integration/kinematics_problems_spec.rb +0 -0
  169. data/spec/integration/knights_puzzle_spec.rb +11 -11
  170. data/spec/integration/kropki_spec.rb +19 -0
  171. data/spec/integration/letter_connections_spec.rb +0 -0
  172. data/spec/integration/light_up_spec.rb +0 -0
  173. data/spec/integration/minisudoku_spec.rb +0 -0
  174. data/spec/integration/miracle_sudoku_spec.rb +15 -0
  175. data/spec/integration/mortal_coil_puzzle_spec.rb +8 -6
  176. data/spec/integration/nanro_spec.rb +39 -0
  177. data/spec/integration/nine_clocks_spec.rb +30 -0
  178. data/spec/integration/nonogram_spec.rb +0 -0
  179. data/spec/integration/oneofus_spec.rb +0 -0
  180. data/spec/integration/pyramid_nonogram_spec.rb +0 -0
  181. data/spec/integration/regexp_crossword_solver_spec.rb +1 -1
  182. data/spec/integration/regexp_solver_spec.rb +0 -0
  183. data/spec/integration/renzoku_spec.rb +23 -0
  184. data/spec/integration/sandwich_sudoku_spec.rb +15 -0
  185. data/spec/integration/selfref_spec.rb +0 -0
  186. data/spec/integration/skyscraper_spec.rb +10 -0
  187. data/spec/integration/star_battle_spec.rb +27 -0
  188. data/spec/integration/stitches_spec.rb +25 -0
  189. data/spec/integration/sudoku_spec.rb +0 -0
  190. data/spec/integration/suguru_spec.rb +23 -0
  191. data/spec/integration/verbal_arithmetic_spec.rb +0 -0
  192. data/spec/integration/yajilin_spec.rb +25 -0
  193. data/spec/integration/zebra_puzzle_spec.rb +0 -0
  194. data/spec/interface_spec.rb +0 -0
  195. data/spec/model_spec.rb +0 -0
  196. data/spec/optimize_spec.rb +3 -1
  197. data/spec/printer_spec.rb +0 -0
  198. data/spec/probe_spec.rb +0 -0
  199. data/spec/real_expr_spec.rb +0 -0
  200. data/spec/real_sort_spec.rb +0 -0
  201. data/spec/rounding_mode_expr_spec.rb +0 -0
  202. data/spec/rounding_mode_sort_spec.rb +0 -0
  203. data/spec/set_expr_spec.rb +15 -9
  204. data/spec/set_sort_spec.rb +0 -0
  205. data/spec/solver_spec.rb +1 -2
  206. data/spec/sort_spec.rb +0 -0
  207. data/spec/spec_helper.rb +15 -0
  208. data/spec/tactic_spec.rb +0 -0
  209. data/spec/z3_spec.rb +0 -0
  210. metadata +85 -5
@@ -65,7 +65,7 @@ class CLogicPuzzleSolver
65
65
  v
66
66
  end
67
67
 
68
- def solve!
68
+ def call
69
69
  # Everyone occurs twice, this is not quite that
70
70
  solver.assert Z3.Add(*@digit_vars.values) == 90
71
71
 
@@ -106,7 +106,7 @@ class CLogicPuzzleSolver
106
106
  end
107
107
  end
108
108
 
109
- CLogicPuzzleSolver.new.solve!
109
+ CLogicPuzzleSolver.new.call
110
110
 
111
111
  # The puzzle:
112
112
  """
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Inspired by Hungry Cat Picross app
4
+
5
+ require "pathname"
6
+ require "paint"
7
+ require_relative "../lib/z3"
8
+
9
+ # With pluses, it means continuous
10
+ # Without pluses, it is not continuous
11
+
12
+ class ColorNonogram
13
+ def initialize(path)
14
+ data = Pathname(path).readlines.map(&:chomp)
15
+ @colors = data.shift.split
16
+ @csize = @colors.size
17
+ raise unless data.shift.empty?
18
+ @cols = []
19
+ @rows = []
20
+ target = @cols
21
+ while row = data.shift
22
+ if row.empty?
23
+ target = @rows
24
+ next
25
+ end
26
+ target << parse_row(row)
27
+ end
28
+ @xsize = @cols.size
29
+ @ysize = @rows.size
30
+ @board = {}
31
+ @solver = Z3::Solver.new
32
+ end
33
+
34
+ def call
35
+ sanity_checks
36
+ setup_board_vars
37
+ constraint_lines
38
+
39
+ if @solver.satisfiable?
40
+ @model = @solver.model
41
+ print_board!
42
+ else
43
+ puts "There is no solution"
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def constraint_lines
50
+ @ysize.times do |y|
51
+ constraint_line @rows[y], row_vars(y)
52
+ end
53
+ @xsize.times do |x|
54
+ constraint_line @cols[x], col_vars(x)
55
+ end
56
+ end
57
+
58
+ def constraint_line(color_constraints, line_vars)
59
+ color_constraints.each_with_index do |(expected_total, expected_continuity), color_index|
60
+ correct_color = line_vars.map{|vi| (vi == color_index) }
61
+ total = Z3.Add(*correct_color.map{|cc| cc.ite(1, 0)})
62
+ @solver.assert total == expected_total
63
+ # For 0 / 1 this is all
64
+ if expected_total >= 2
65
+ continuity = continuity_somewhere(correct_color, expected_total)
66
+ @solver.assert continuity == expected_continuity
67
+ end
68
+ end
69
+ end
70
+
71
+ def continuity_somewhere(correct, total)
72
+ Z3.Or(*correct.size.times.map{|start_i|
73
+ continuity_at(correct, total, start_i)
74
+ })
75
+ end
76
+
77
+ def continuity_at(correct, total, start_index)
78
+ end_index = start_index + total - 1
79
+ return Z3.False unless correct[end_index]
80
+ Z3.And(*correct[start_index, total])
81
+ end
82
+
83
+
84
+ def row_vars(y)
85
+ @xsize.times.map{|x| @board[[x,y]] }
86
+ end
87
+
88
+ def col_vars(x)
89
+ @ysize.times.map{|y| @board[[x,y]] }
90
+ end
91
+
92
+ def print_board!
93
+ # Add colors please
94
+ @ysize.times do |y|
95
+ @xsize.times do |x|
96
+ v = @model[@board[[x,y]]].to_i
97
+ print Paint[v, @colors[v]], " "
98
+ end
99
+ print "\n"
100
+ end
101
+ end
102
+
103
+ def setup_board_vars
104
+ @xsize.times do |x|
105
+ @ysize.times do |y|
106
+ v = Z3.Int("c#{x},#{y}")
107
+ @solver.assert v >= 0
108
+ @solver.assert v < @csize
109
+ @board[[x,y]] = v
110
+ end
111
+ end
112
+ end
113
+
114
+ # Z3 will return can't solve, but we might as well get better errors
115
+ def sanity_checks
116
+ @rows.each do |row|
117
+ e = row.map(&:first).sum
118
+ raise "Wrong row count" unless e == @xsize
119
+ end
120
+
121
+ @cols.each do |col|
122
+ e = col.map(&:first).sum
123
+ raise "Wrong col count" unless e == @ysize
124
+ end
125
+ end
126
+
127
+ def parse_row(row)
128
+ parts = row.split.map{|x| parse_part(x)}
129
+ unless parts.size == @csize
130
+ raise "Incorrect row size, expected #{@csize}: #{row.inspect}"
131
+ end
132
+ parts
133
+ end
134
+
135
+ def parse_part(x)
136
+ case x
137
+ when "."
138
+ [0, false]
139
+ when /\A(\d+)\z/
140
+ [$1.to_i, false]
141
+ when /\A(\d+)\+\z/
142
+ [$1.to_i, true]
143
+ else
144
+ raise "Incorrect part #{x.inspect}"
145
+ end
146
+ end
147
+ end
148
+
149
+ path = ARGV[0] || Pathname(__dir__) + "color_nonogram-1.txt"
150
+ ColorNonogram.new(path).call
@@ -0,0 +1,23 @@
1
+ red black white brown
2
+
3
+ 2+ . . 8
4
+ 6 . 2 2
5
+ 4 1 3 2
6
+ 5 1 2 2
7
+ 6 . 2+ 2
8
+ 6 . 2+ 2
9
+ 4 1 3 2
10
+ 6 2+ 1 1
11
+ 9+ . . 1
12
+ 2+ . . 8
13
+
14
+ . . . 10+
15
+ 6 . 2+ 2
16
+ 8+ . . 2
17
+ 4 2 2 2
18
+ 5 2+ . 3+
19
+ 9+ . . 1
20
+ 2 1 6 1
21
+ 4 . 5+ 1
22
+ 8+ . . 2
23
+ 4 . . 6
data/examples/crossflip CHANGED
@@ -46,7 +46,7 @@ class CrossFlipSolver
46
46
  result
47
47
  end
48
48
 
49
- def solve!
49
+ def call
50
50
  @solver = Z3::Solver.new
51
51
 
52
52
  @ysize.times do |y|
@@ -76,7 +76,5 @@ class CrossFlipSolver
76
76
  end
77
77
  end
78
78
 
79
-
80
-
81
79
  board = "1021,1000"
82
- CrossFlipSolver.new(board).solve!
80
+ CrossFlipSolver.new(board).call
data/examples/dominion ADDED
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "pathname"
4
+ require "set"
5
+ require_relative "../lib/z3"
6
+
7
+ # https://www.janko.at/Raetsel/Dominion/index.htm
8
+
9
+ class Dominion
10
+ def initialize(path)
11
+ @data = Pathname(path).readlines.map(&:chomp)
12
+ @ysize = @data.size
13
+ @xsize = @data[0].size
14
+ raise unless @data.all?{|line| line.size == @xsize}
15
+ @vars = (@data.flat_map(&:chars).uniq - ["."]).sort.map.with_index{|v,c| [v,c+1]}.to_h
16
+ @areas_count = @vars.size
17
+ @solver = Z3::Solver.new
18
+ end
19
+
20
+ def on_board?(x,y)
21
+ x >= 0 and y >= 0 and x < @xsize and y < @ysize
22
+ end
23
+
24
+ def given(x,y)
25
+ return unless on_board?(x,y)
26
+ @vars[@data[y][x]] || nil
27
+ end
28
+
29
+ def var(x,y)
30
+ return unless on_board?(x,y)
31
+ Z3.Int("c[#{x},#{y}]")
32
+ end
33
+
34
+ def dvar(x,y)
35
+ return unless on_board?(x,y)
36
+ Z3.Int("d[#{x},#{y}]")
37
+ end
38
+
39
+ def domino?(x,y)
40
+ var(x,y) == 0
41
+ end
42
+
43
+ def neighbours(x,y)
44
+ [
45
+ [x-1,y],
46
+ [x+1,y],
47
+ [x,y-1],
48
+ [x,y+1],
49
+ ].select{|nx,ny| on_board?(nx,ny)}
50
+ end
51
+
52
+ def count_neighbour_dominoes(x,y)
53
+ Z3.Add(*neighbours(x,y).map{|x,y| domino?(x,y).ite(1,0) })
54
+ end
55
+
56
+ def assign_primary
57
+ seen = Set[]
58
+ @primary = Set[]
59
+ each_xy do |x,y|
60
+ g = given(x,y)
61
+ next unless g
62
+ next if seen.include?(g)
63
+ @primary << [x,y]
64
+ seen << g
65
+ end
66
+ end
67
+
68
+ def primary?(x,y)
69
+ @primary.include?([x,y])
70
+ end
71
+
72
+ def each_xy
73
+ @ysize.times do |y|
74
+ @ysize.times do |x|
75
+ yield(x,y)
76
+ end
77
+ end
78
+ end
79
+
80
+ def assert_cells_are_assigned_to_areas
81
+ each_xy do |x,y|
82
+ @solver.assert var(x,y) >= 0
83
+ @solver.assert var(x,y) <= @areas_count
84
+ g = given(x,y)
85
+ if g
86
+ @solver.assert var(x,y) == g
87
+ end
88
+ end
89
+ end
90
+
91
+ def assert_dominos
92
+ each_xy do |x,y|
93
+ @solver.assert(
94
+ domino?(x,y).implies(count_neighbour_dominoes(x,y) == 1)
95
+ )
96
+ end
97
+ end
98
+
99
+ def assert_areas_separated
100
+ each_xy do |x,y|
101
+ v = var(x,y)
102
+ neighbours(x,y).each do |nx,ny|
103
+ n = var(nx,ny)
104
+ @solver.assert Z3.Or(n == 0, v == 0, n == v)
105
+ end
106
+ end
107
+ end
108
+
109
+ def assert_each_area_connected
110
+ # Just some bogus big value
111
+ max_value = @xsize * @ysize + 10
112
+ each_xy do |x,y|
113
+ @solver.assert dvar(x,y) >= 0
114
+ @solver.assert dvar(x,y) < max_value
115
+
116
+ next_ds_same_area = neighbours(x,y).map{|nx,ny| (var(nx,ny) == var(x,y)).ite(dvar(nx,ny) + 1, max_value) }
117
+ next_d = next_ds_same_area.reduce{|a,b| (a <= b).ite(a, b) }
118
+ @solver.assert(
119
+ dvar(x,y) == Z3.Or(primary?(x,y), domino?(x,y)).ite(0, next_d)
120
+ )
121
+ end
122
+ end
123
+
124
+ def call
125
+ assign_primary
126
+
127
+ assert_cells_are_assigned_to_areas
128
+ assert_dominos
129
+ assert_areas_separated
130
+
131
+ assert_each_area_connected
132
+
133
+ if @solver.satisfiable?
134
+ @model = @solver.model
135
+ print_solution
136
+ else
137
+ puts "There is no solution"
138
+ end
139
+ end
140
+
141
+ def print_solution
142
+ @ysize.times do |y|
143
+ @ysize.times do |x|
144
+ c = @model[var(x,y)].to_i
145
+ print @vars.invert[c] || "*"
146
+ end
147
+ print "\n"
148
+ end
149
+ end
150
+ end
151
+
152
+ path = ARGV[0] || Pathname(__dir__) + "dominion-1.txt"
153
+ Dominion.new(path).call
@@ -0,0 +1,8 @@
1
+ ..A....B
2
+ ....A...
3
+ F.......
4
+ ........
5
+ ..E.G...
6
+ .G.....D
7
+ ........
8
+ .....C.C
data/examples/dominosa ADDED
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "pathname"
4
+ require_relative "../lib/z3"
5
+
6
+ class Dominosa
7
+ def initialize(path)
8
+ @data = Pathname(path).readlines.map{|line| line.split.map(&:to_i)}
9
+ @solver = Z3::Solver.new
10
+ @ysize = @data.size
11
+ @xsize = @data[0].size
12
+ end
13
+
14
+ def connect_var(x1,y1,x2,y2)
15
+ Z3.Bool("#{x1},#{y1}-#{x2},#{y2}")
16
+ end
17
+
18
+ def valid?(x,y)
19
+ return false if x < 0 or y < 0
20
+ return false if x >= @xsize or y >= @ysize
21
+ true
22
+ end
23
+
24
+ def down_var(x,y)
25
+ return unless valid?(x,y+1)
26
+ connect_var(x,y,x,y+1)
27
+ end
28
+
29
+ def up_var(x,y)
30
+ return unless valid?(x,y-1)
31
+ connect_var(x,y-1,x,y)
32
+ end
33
+
34
+ def right_var(x,y)
35
+ return unless valid?(x+1,y)
36
+ connect_var(x,y,x+1,y)
37
+ end
38
+
39
+ def left_var(x,y)
40
+ return unless valid?(x-1,y)
41
+ connect_var(x-1,y,x,y)
42
+ end
43
+
44
+ def each_x
45
+ @xsize.times do |x|
46
+ yield(x, x == @xsize - 1)
47
+ end
48
+ end
49
+
50
+ def each_y
51
+ @ysize.times do |y|
52
+ yield(y, y == @ysize - 1)
53
+ end
54
+ end
55
+
56
+ def assert_no_duplicate_connections
57
+ all = Hash.new{|ht,k| ht[k] = []}
58
+
59
+ each_y do |y, last_y|
60
+ each_x do |x, last_x|
61
+ unless last_x
62
+ c = right_var(x,y)
63
+ v = [@data[y][x], @data[y][x+1]].sort
64
+ all[v] << c
65
+ end
66
+
67
+ unless last_y
68
+ c = down_var(x,y)
69
+ v = [@data[y][x], @data[y+1][x]].sort
70
+ all[v] << c
71
+ end
72
+ end
73
+ end
74
+
75
+ all.each do |_, vars|
76
+ @solver.assert Z3.Add(*vars.map{|v| v.ite(1,0) }) == 1
77
+ end
78
+ end
79
+
80
+ def assert_solution_made_of_dominoes
81
+ @ysize.times do |y|
82
+ @xsize.times do |x|
83
+ vars = [
84
+ down_var(x,y),
85
+ right_var(x,y),
86
+ left_var(x,y),
87
+ up_var(x,y),
88
+ ].compact
89
+ @solver.assert Z3.Add(*vars.map{|v| v.ite(1,0) }) == 1
90
+ end
91
+ end
92
+ end
93
+
94
+ def call
95
+ assert_no_duplicate_connections
96
+ assert_solution_made_of_dominoes
97
+
98
+ if @solver.satisfiable?
99
+ @model = @solver.model
100
+ print_solution
101
+ else
102
+ puts "There is no solution"
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ def print_connection(var)
109
+ c = @model[var].to_b
110
+ print c ? "*" : " "
111
+ end
112
+
113
+ def print_solution
114
+ each_y do |y, last_y|
115
+ each_x do |x, last_x|
116
+ print @data[y][x]
117
+ print_connection right_var(x,y) unless last_x
118
+ end
119
+ print "\n"
120
+
121
+ unless last_y
122
+ each_x do |x, last_x|
123
+ print_connection down_var(x,y)
124
+ print " " unless last_x
125
+ end
126
+ print "\n"
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ path = ARGV[0] || Pathname(__dir__) + "dominosa-1.txt"
133
+ Dominosa.new(path).call
@@ -0,0 +1,8 @@
1
+ 5 5 7 4 1 1 4 2 3
2
+ 2 0 2 4 7 5 4 2 0
3
+ 4 0 0 3 2 0 3 4 5
4
+ 1 1 6 4 6 5 2 5 6
5
+ 6 1 1 7 7 5 6 3 6
6
+ 7 3 0 2 4 2 1 6 3
7
+ 6 5 6 0 1 3 3 0 0
8
+ 4 5 7 7 7 2 1 7 3
data/examples/eulero ADDED
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "pathname"
4
+ require "set"
5
+ require_relative "../lib/z3"
6
+
7
+ # https://www.janko.at/Raetsel/Eulero/index.htm
8
+
9
+ class Eulero
10
+ def initialize(path)
11
+ @data = Pathname(path).readlines.map(&:chomp).map(&:split)
12
+ @size = @data.size
13
+ raise unless @data.all?{|row| row.size == @size}
14
+ raise unless @data.flatten.all?{|c| c =~ /\A[A-Z\.][1-9\.]\z/ }
15
+ @solver = Z3::Solver.new
16
+ end
17
+
18
+ def lvar(x,y)
19
+ Z3.Int("l[#{x},#{y}]")
20
+ end
21
+
22
+ def nvar(x,y)
23
+ Z3.Int("n[#{x},#{y}]")
24
+ end
25
+
26
+ def xvar(x,y)
27
+ Z3.Int("x[#{x},#{y}]")
28
+ end
29
+
30
+ def assert_vars
31
+ @size.times do |y|
32
+ @size.times do |x|
33
+ @solver.assert lvar(x,y) >= 1
34
+ @solver.assert lvar(x,y) <= @size
35
+ @solver.assert nvar(x,y) >= 1
36
+ @solver.assert nvar(x,y) <= @size
37
+ @solver.assert xvar(x,y) == @size * lvar(x,y) + nvar(x,y)
38
+
39
+ dl = @data[y][x][0]
40
+ dn = @data[y][x][1]
41
+ if dl =~ /[A-Z]/
42
+ @solver.assert lvar(x,y) == (dl.ord - "A".ord + 1)
43
+ end
44
+ if dn =~ /[1-9]/
45
+ @solver.assert nvar(x,y) == (dn.ord - "1".ord + 1)
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ def assert_cols
52
+ @size.times do |x|
53
+ @solver.assert Z3.Distinct(*@size.times.map{|y| lvar(x,y)})
54
+ @solver.assert Z3.Distinct(*@size.times.map{|y| nvar(x,y)})
55
+ end
56
+ end
57
+
58
+ def assert_rows
59
+ @size.times do |y|
60
+ @solver.assert Z3.Distinct(*@size.times.map{|x| lvar(x,y)})
61
+ @solver.assert Z3.Distinct(*@size.times.map{|x| nvar(x,y)})
62
+ end
63
+ end
64
+
65
+ def assert_distinct
66
+ xvars = @size.times.map{|x| @size.times.map{|y| xvar(x,y) }}.flatten
67
+ @solver.assert Z3.Distinct(*xvars)
68
+ end
69
+
70
+ def call
71
+ assert_vars
72
+ assert_cols
73
+ assert_rows
74
+ assert_distinct
75
+
76
+ if @solver.satisfiable?
77
+ @model = @solver.model
78
+ print_solution
79
+ else
80
+ puts "There is no solution"
81
+ end
82
+ end
83
+
84
+ def print_solution
85
+ @size.times do |y|
86
+ @size.times do |x|
87
+ l = @model[lvar(x,y)].to_i
88
+ n = @model[nvar(x,y)].to_i
89
+ print ("A".ord + l - 1).chr
90
+ print ("1".ord + n - 1).chr
91
+ print " "
92
+ end
93
+ print "\n"
94
+ end
95
+ end
96
+ end
97
+
98
+ path = ARGV[0] || Pathname(__dir__) + "eulero-1.txt"
99
+ Eulero.new(path).call
@@ -0,0 +1,5 @@
1
+ C. .. .. .1 ..
2
+ .. .. B3 .. ..
3
+ E. .3 .. .. .5
4
+ .. E. .. .. D.
5
+ .. .. .. .4 .1
@@ -58,7 +58,7 @@ class LogicPuzzle
58
58
  @vars[key][idx] == val_idx
59
59
  end
60
60
 
61
- def solve!
61
+ def call
62
62
  add_assertions!
63
63
  if @solver.satisfiable?
64
64
  @solver.model.each do |k,v|
@@ -152,7 +152,7 @@ class FourHackersPuzzle < LogicPuzzle
152
152
  end
153
153
  end
154
154
 
155
- FourHackersPuzzle.new.solve!
155
+ FourHackersPuzzle.new.call
156
156
 
157
157
  """
158
158
  There is little we can do but wait, so we may as well take another job.