z3 0.0.20180624 → 0.0.20211213
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -4
- data/Rakefile +15 -8
- data/examples/abc_path +187 -0
- data/examples/abc_path-1.txt +7 -0
- data/examples/algebra_problems +12 -12
- data/examples/aquarium +133 -0
- data/examples/aquarium-1.txt +11 -0
- data/examples/bridges +2 -2
- data/examples/cats_organized_neatly +133 -0
- data/examples/cats_organized_neatly-10.txt +15 -0
- data/examples/cats_organized_neatly-3.txt +8 -0
- data/examples/cats_organized_neatly-48.txt +32 -0
- data/examples/circuit_problems +4 -4
- data/examples/clogic_puzzle +2 -2
- data/examples/color_nonogram +150 -0
- data/examples/color_nonogram-1.txt +23 -0
- data/examples/crossflip +2 -4
- data/examples/dominion +153 -0
- data/examples/dominion-1.txt +8 -0
- data/examples/dominosa +133 -0
- data/examples/dominosa-1.txt +8 -0
- data/examples/eulero +99 -0
- data/examples/eulero-1.txt +5 -0
- data/examples/four_hackers_puzzle +2 -2
- data/examples/futoshiki +128 -0
- data/examples/futoshiki-1.txt +17 -0
- data/examples/kakurasu +73 -0
- data/examples/kakurasu-1.txt +2 -0
- data/examples/kakuro +2 -2
- data/examples/killer_sudoku +88 -0
- data/examples/killer_sudoku-1.txt +17 -0
- data/examples/killer_sudoku-2.txt +53 -0
- data/examples/kinematics_problems +20 -20
- data/examples/knights_puzzle +2 -2
- data/examples/kropki +100 -0
- data/examples/kropki-1.txt +13 -0
- data/examples/letter_connections +2 -2
- data/examples/light_up +2 -2
- data/examples/minisudoku +2 -2
- data/examples/miracle_sudoku +135 -0
- data/examples/miracle_sudoku-1.txt +9 -0
- data/examples/mortal_coil_puzzle +2 -2
- data/examples/nanro +245 -0
- data/examples/nanro-1.txt +8 -0
- data/examples/nine_clocks +106 -0
- data/examples/nonogram +2 -2
- data/examples/pyramid_nonogram +2 -2
- data/examples/regexp_crossword_solver +2 -2
- data/examples/regexp_solver +2 -2
- data/examples/renzoku +124 -0
- data/examples/renzoku-1.txt +17 -0
- data/examples/sandwich_sudoku +101 -0
- data/examples/sandwich_sudoku-1.txt +10 -0
- data/examples/selfref +2 -2
- data/examples/simple_regexp_parser.rb +58 -56
- data/examples/skyscrapers +118 -0
- data/examples/skyscrapers-1.txt +6 -0
- data/examples/skyscrapers-2.txt +11 -0
- data/examples/star_battle +134 -0
- data/examples/star_battle-1.txt +10 -0
- data/examples/stitches +180 -0
- data/examples/stitches-1.txt +11 -0
- data/examples/sudoku +2 -2
- data/examples/suguru +199 -0
- data/examples/suguru-1.txt +17 -0
- data/examples/verbal_arithmetic +2 -2
- data/examples/yajilin +268 -0
- data/examples/yajilin-1.txt +10 -0
- data/lib/z3/ast.rb +8 -0
- data/lib/z3/expr/bitvec_expr.rb +10 -0
- data/lib/z3/expr/expr.rb +16 -15
- data/lib/z3/low_level.rb +6 -2
- data/lib/z3/low_level_auto.rb +180 -36
- data/lib/z3/optimize.rb +5 -4
- data/lib/z3/printer.rb +29 -1
- data/lib/z3/sort/bitvec_sort.rb +1 -0
- data/lib/z3/sort/sort.rb +9 -5
- data/lib/z3/very_low_level.rb +8 -5
- data/lib/z3/very_low_level_auto.rb +45 -9
- data/spec/bitvec_expr_spec.rb +35 -21
- data/spec/bitvec_sort_spec.rb +4 -0
- data/spec/expr_spec.rb +62 -0
- data/spec/integration/abc_path_spec.rb +21 -0
- data/spec/integration/aquarium_spec.rb +27 -0
- data/spec/integration/cats_organized_neatly_spec.rb +14 -0
- data/spec/integration/color_nonogram_spec.rb +28 -0
- data/spec/integration/dominion_spec.rb +14 -0
- data/spec/integration/dominosa_spec.rb +21 -0
- data/spec/integration/eulero_spec.rb +11 -0
- data/spec/integration/futoshiki_spec.rb +23 -0
- data/spec/integration/kakurasu_spec.rb +18 -0
- data/spec/integration/killer_sudoku_spec.rb +10 -0
- data/spec/integration/knights_puzzle_spec.rb +11 -11
- data/spec/integration/kropki_spec.rb +19 -0
- data/spec/integration/miracle_sudoku_spec.rb +15 -0
- data/spec/integration/mortal_coil_puzzle_spec.rb +8 -6
- data/spec/integration/nanro_spec.rb +39 -0
- data/spec/integration/nine_clocks_spec.rb +30 -0
- data/spec/integration/oneofus_spec.rb +7 -15
- data/spec/integration/regexp_crossword_solver_spec.rb +1 -1
- data/spec/integration/renzoku_spec.rb +23 -0
- data/spec/integration/sandwich_sudoku_spec.rb +15 -0
- data/spec/integration/skyscraper_spec.rb +10 -0
- data/spec/integration/star_battle_spec.rb +27 -0
- data/spec/integration/stitches_spec.rb +25 -0
- data/spec/integration/suguru_spec.rb +23 -0
- data/spec/integration/yajilin_spec.rb +25 -0
- data/spec/interface_spec.rb +18 -0
- data/spec/optimize_spec.rb +6 -4
- data/spec/printer_spec.rb +30 -0
- data/spec/set_expr_spec.rb +14 -8
- data/spec/solver_spec.rb +4 -5
- data/spec/spec_helper.rb +15 -0
- 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
|
data/examples/circuit_problems
CHANGED
@@ -45,7 +45,7 @@ class CircuitProblem
|
|
45
45
|
@solver.assert @pins[a][:voltage] == @pins[b][:voltage]
|
46
46
|
end
|
47
47
|
|
48
|
-
def
|
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.
|
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.
|
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.
|
159
|
+
problem.call "I V", "I L"
|
160
160
|
end
|
161
161
|
end
|
162
162
|
|
data/examples/clogic_puzzle
CHANGED
@@ -65,7 +65,7 @@ class CLogicPuzzleSolver
|
|
65
65
|
v
|
66
66
|
end
|
67
67
|
|
68
|
-
def
|
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.
|
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
|
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).
|
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
|
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
|
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
|
@@ -58,7 +58,7 @@ class LogicPuzzle
|
|
58
58
|
@vars[key][idx] == val_idx
|
59
59
|
end
|
60
60
|
|
61
|
-
def
|
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.
|
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.
|