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/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.
|