summaryrefslogtreecommitdiff
path: root/nfa/generate-nfa.py
blob: 6255bca9aa0242a312319eb7ea3bc5fd7585e757 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#!/usr/bin/env python
# Usage:
#   $0 [number-of-states] | z3 -smt -in
# Examples:
#   $0 25

import itertools
import sys

# Number of steps
m = 25 if len(sys.argv) <= 1 else int(sys.argv[1])

# all possible strings
lang = [
    "aa",
    "bb",
    "aba",
    "baa",
    "abab",
    "babb",
    "bbba",
]

# sledgehammer approach: specify all bad words
fulllang = []
for n in range(1, 4 + 1):
    fulllang += [
        "".join(x)
        for x in itertools.product(*["ab"] * n)
    ]
# Remove good words.
badlang = [word for word in fulllang if word not in lang]

literals = []
preds = []
extrafuns = []

# Populate Q = { q0, q1, ..., q_(m-1) } with unique values.
literals += ["q%d" % i for i in range(m)]
preds += ["(= q%d %d)" % (i, i) for i in range(m)]

def mks(word_index, letter_index):
    """Return variable for a state of a word after some transitions."""
    return "s%d_%d" % (word_index, letter_index)

# Assuming a single initial state (which may or may not be the final state),
# add states for every letter j in each word i.
for i, word in enumerate(lang):
    for j, letter in enumerate(word):
        v = mks(i, j)
        literals += [v]
        # Each of the states must be assigned to any of the states in Q
        #preds += ["(>= %s 0)" % v, "(< %s %d)" % (v, m)]

# Transition Δ: Q × Σ -> P(Q). For Σ = {a,b}, we assume this range:
# transitions (0..; 1..2; 0..)
extrafuns += ["(trans Int Int Int)"]
# final states F \subset Q
extrafuns += ["(final Int Bool)"]
# Require a transition between each letter
for i, word in enumerate(lang):
    state_prev = "q0"
    for j, letter in enumerate(word):
        state_next = mks(i, j)
        label = "ab".index(letter)
        preds += ["(= (trans %s %d) %s)" % (state_prev, label, state_next)]
        state_prev = state_next
    preds += ["(final %s)" % state_prev]

# "a" and "b" are not allowed to accept (length 1)
#preds += ["(not (final (trans q0 %d)))" % label for label in range(2)]
for i, word in enumerate(badlang):
    state_prev = "q0"
    move = "q0"
    preconds = []
    for j, letter in enumerate(word):
        label = "ab".index(letter)
        move = "(trans %s %d)" % (move, label)
        # Ensure result is within range.
        preconds += ["(>= %s 0)" % move, "(< %s %d)" % (move, m)]
    preds += ["(=> (and %s) (not (final %s)))" % (" ".join(preconds), move)]
    #preds += ["(not (final %s))" % move]
    #valid = "(>= {move} 0) (< {move} {m})".format(**vars())
    #preds += ["(=> (and %s) (not (final %s)))" % (valid, move)]

# Try to fit any of the letter states with any state from Q
for i, word in enumerate(lang):
    for j, letter in enumerate(word):
        alts = []
        for qi in range(m):
            state = "q%d" % qi
            alts += ["(= %s %s)" % (state, mks(i, j))]
        preds += ["(or %s)" % " ".join(alts)]

# Begin generator
s = """(benchmark test.smt
:logic QF_UFLIA
:extrafuns (
"""
s += "\n".join("(%s Int)" % x for x in literals)
s += "\n" + "\n".join(extrafuns)
s += """
)
:formula (and
"""
s += "\n".join(preds)
s += """
))
"""

try:
    import sys
    print(s)
    # Hack to avoid printing a ugly error in case stdin of next pipe is closed.
    sys.stdout.flush()
    sys.stdout.close()
except BrokenPipeError:
    pass