z3 0.0.20180624 → 0.0.20211213

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) 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/bitvec_expr.rb +10 -0
  72. data/lib/z3/expr/expr.rb +16 -15
  73. data/lib/z3/low_level.rb +6 -2
  74. data/lib/z3/low_level_auto.rb +180 -36
  75. data/lib/z3/optimize.rb +5 -4
  76. data/lib/z3/printer.rb +29 -1
  77. data/lib/z3/sort/bitvec_sort.rb +1 -0
  78. data/lib/z3/sort/sort.rb +9 -5
  79. data/lib/z3/very_low_level.rb +8 -5
  80. data/lib/z3/very_low_level_auto.rb +45 -9
  81. data/spec/bitvec_expr_spec.rb +35 -21
  82. data/spec/bitvec_sort_spec.rb +4 -0
  83. data/spec/expr_spec.rb +62 -0
  84. data/spec/integration/abc_path_spec.rb +21 -0
  85. data/spec/integration/aquarium_spec.rb +27 -0
  86. data/spec/integration/cats_organized_neatly_spec.rb +14 -0
  87. data/spec/integration/color_nonogram_spec.rb +28 -0
  88. data/spec/integration/dominion_spec.rb +14 -0
  89. data/spec/integration/dominosa_spec.rb +21 -0
  90. data/spec/integration/eulero_spec.rb +11 -0
  91. data/spec/integration/futoshiki_spec.rb +23 -0
  92. data/spec/integration/kakurasu_spec.rb +18 -0
  93. data/spec/integration/killer_sudoku_spec.rb +10 -0
  94. data/spec/integration/knights_puzzle_spec.rb +11 -11
  95. data/spec/integration/kropki_spec.rb +19 -0
  96. data/spec/integration/miracle_sudoku_spec.rb +15 -0
  97. data/spec/integration/mortal_coil_puzzle_spec.rb +8 -6
  98. data/spec/integration/nanro_spec.rb +39 -0
  99. data/spec/integration/nine_clocks_spec.rb +30 -0
  100. data/spec/integration/oneofus_spec.rb +7 -15
  101. data/spec/integration/regexp_crossword_solver_spec.rb +1 -1
  102. data/spec/integration/renzoku_spec.rb +23 -0
  103. data/spec/integration/sandwich_sudoku_spec.rb +15 -0
  104. data/spec/integration/skyscraper_spec.rb +10 -0
  105. data/spec/integration/star_battle_spec.rb +27 -0
  106. data/spec/integration/stitches_spec.rb +25 -0
  107. data/spec/integration/suguru_spec.rb +23 -0
  108. data/spec/integration/yajilin_spec.rb +25 -0
  109. data/spec/interface_spec.rb +18 -0
  110. data/spec/optimize_spec.rb +6 -4
  111. data/spec/printer_spec.rb +30 -0
  112. data/spec/set_expr_spec.rb +14 -8
  113. data/spec/solver_spec.rb +4 -5
  114. data/spec/spec_helper.rb +15 -0
  115. metadata +104 -24
@@ -0,0 +1,32 @@
1
+ .......
2
+ .......
3
+ ........
4
+ ........
5
+ ........
6
+ ........
7
+ .......
8
+ .....
9
+
10
+ aaaaa
11
+ aaaaa
12
+
13
+ bbb
14
+ bbbb
15
+ bbbb
16
+
17
+ ccc
18
+ cccc
19
+
20
+ ddd
21
+ ddd
22
+ ddd
23
+
24
+ eee
25
+ e
26
+
27
+ ffff
28
+ f
29
+
30
+ gggg
31
+ gggg
32
+ gggg
@@ -45,7 +45,7 @@ class CircuitProblem
45
45
  @solver.assert @pins[a][:voltage] == @pins[b][:voltage]
46
46
  end
47
47
 
48
- def solve!(*vars)
48
+ def call(*vars)
49
49
  setup_flow_rules!
50
50
  with_solved_model do |model|
51
51
  model.each do |n,v|
@@ -115,7 +115,7 @@ def problem_1!
115
115
  problem.connect "R1b", "R2a"
116
116
  problem.connect "R2b", "R3a"
117
117
  problem.connect "R3b", "V+"
118
- problem.solve! "I V"
118
+ problem.call "I V"
119
119
  end
120
120
 
121
121
  def problem_2!
@@ -131,7 +131,7 @@ def problem_2!
131
131
  problem.connect "V+", "R1b"
132
132
  problem.connect "V+", "R2b"
133
133
  problem.connect "V+", "R3b"
134
- problem.solve! "I V"
134
+ problem.call "I V"
135
135
  end
136
136
 
137
137
  def problem_3!
@@ -156,7 +156,7 @@ def problem_3!
156
156
  problem.connect "D3+", "V+"
157
157
  problem.connect "D4-", "Lb"
158
158
  problem.connect "D4+", "V-"
159
- problem.solve! "I V", "I L"
159
+ problem.call "I V", "I L"
160
160
  end
161
161
  end
162
162
 
@@ -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.