automaton-simulate 0.1.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dayu811
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.4
2
+ Name: automaton-simulate
3
+ Version: 0.1.0
4
+ Summary: A library for simulating automatons
5
+ Author-email: Dayu811 <13691283074@163.com>
6
+ License: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Dynamic: license-file
14
+
15
+ # py-automaton
16
+
17
+ A lightweight Python library for simulating automata and turing machine.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pip install automaton-simulate
@@ -0,0 +1,8 @@
1
+ # py-automaton
2
+
3
+ A lightweight Python library for simulating automata and turing machine.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install automaton-simulate
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "automaton-simulate"
7
+ version = "0.1.0"
8
+ description = "A library for simulating automatons"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.8"
12
+ authors = [{name = "Dayu811", email = "13691283074@163.com"}]
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Operating System :: OS Independent",
17
+ ]
18
+ dependencies = []
19
+
20
+ [tool.setuptools.packages.find]
21
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,13 @@
1
+ from turing_machine_main import TuringMachine
2
+ from turing_machine_multitape import MultiTapeTuringMachine
3
+ from turing_machine_nondeterministic import NondeterministicTuringMachine
4
+ from turing_machine_formal_language import Enumerator, recognizer
5
+
6
+ from automaton_main import FiniteAutomaton, PushdownAutomaton, LinearBoundedAutomaton
7
+
8
+ from random_machine import random_machine, two_symbols_machine
9
+
10
+ __version__ = "0.1.2"
11
+ __all__ = ["TuringMachine", "MultiTapeTuringMachine", "NondeterministicTuringMachine", "Enumerator", "recognizer",
12
+ "FiniteAutomaton", "PushdownAutomaton", "LinearBoundedAutomaton",
13
+ "random_machine", "two_symbols_machine"]
@@ -0,0 +1,271 @@
1
+ class FiniteAutomaton:
2
+ def __init__(self, states, alphabet, transition, initial_state, accept_state, initial_word):
3
+
4
+ self.states = states
5
+ self.alphabet = alphabet
6
+ self.transition = transition
7
+ self.initial_state = initial_state
8
+ self.accept_state = accept_state
9
+ self.current_state = initial_state
10
+ self.word = initial_word
11
+
12
+ def run(self):
13
+ for i in range(len(self.word)):
14
+ symbol = self.word[i]
15
+ if symbol not in self.alphabet:
16
+ raise ValueError(f"Write symbol '{symbol}' is not in alphabet")
17
+
18
+ key = (self.current_state, symbol)
19
+
20
+ if key not in self.transition:
21
+ raise RuntimeError(f"transition function undefined: '{key}'")
22
+ self.current_state = self.transition[key]
23
+
24
+ return self.current_state == self.accept_state
25
+
26
+ def __repr__(self):
27
+ return(
28
+ f"FiniteAutomaton(states={self.states}, alphabet={self.alphabet}, "
29
+ f"initial_state={self.initial_state!r}, accept_state={self.accept_state!r}, "
30
+ f"current_state={self.current_state!r}, initial_word={self.word!r}, "
31
+ f"transition={self.transition})"
32
+ )
33
+
34
+
35
+
36
+ class PushdownAutomaton:
37
+
38
+ "transition[(state, symbol, stack_top)] = (next_state, push_string)"
39
+
40
+ def __init__(self, states, alphabet, stack_alphabet, transition,
41
+ initial_state, accept_state, initial_word, initial_stack_symbol):
42
+
43
+ self.states = states
44
+ self.alphabet = alphabet
45
+ self.stack_alphabet = stack_alphabet
46
+ self.transition = transition
47
+ self.initial_state = initial_state
48
+ self.accept_state = accept_state
49
+ self.current_state = initial_state
50
+ self.word = initial_word
51
+ self.initial_stack_symbol = initial_stack_symbol
52
+ self.stack = [] if initial_stack_symbol is None else [initial_stack_symbol]
53
+
54
+ def _apply(self, key):
55
+ next_state, push_string = self.transition[key]
56
+ for ch in push_string:
57
+ if ch not in self.stack_alphabet:
58
+ raise ValueError(
59
+ f"stack symbol '{ch}' in push string '{push_string}' "
60
+ f"is not in stack alphabet")
61
+ self.current_state = next_state
62
+ if self.stack:
63
+ self.stack.pop()
64
+ self.stack.extend(reversed(push_string))
65
+
66
+ def run(self):
67
+
68
+ self.current_state = self.initial_state
69
+ self.stack = [] if self.initial_stack_symbol is None else [self.initial_stack_symbol]
70
+
71
+ i = 0
72
+ while True:
73
+ top = self.stack[-1] if self.stack else None
74
+
75
+ key = (self.current_state, None, top)
76
+ if key in self.transition:
77
+ self._apply(key)
78
+ continue
79
+
80
+ if i == len(self.word):
81
+ break
82
+
83
+ symbol = self.word[i]
84
+ i += 1
85
+ if symbol not in self.alphabet:
86
+ raise ValueError(f"Input symbol '{symbol}' is not in alphabet")
87
+
88
+ key = (self.current_state, symbol, top)
89
+ if key not in self.transition:
90
+ raise RuntimeError(f"transition function undefined: '{key}'")
91
+ self._apply(key)
92
+
93
+ return self.current_state == self.accept_state
94
+
95
+ def __repr__(self):
96
+ return (
97
+ f"PushdownAutomaton(states={self.states}, alphabet={self.alphabet}, "
98
+ f"stack_alphabet={self.stack_alphabet}, "
99
+ f"initial_state={self.initial_state!r}, accept_state={self.accept_state!r}, "
100
+ f"current_state={self.current_state!r}, initial_word={self.word!r}, "
101
+ f"initial_stack_symbol={self.initial_stack_symbol!r}, "
102
+ f"stack={self.stack!r}, transition={self.transition})"
103
+ )
104
+
105
+
106
+ class LinearBoundedAutomaton:
107
+ #默认方法:属性及初始化
108
+ def __init__(self, states, alphabet, transition, initial_state, accept_state, reject_state, tape_length = 100, blank_symbol = " ", tape_alphabet = None, initial_tape = ""):
109
+
110
+ #七元组属性
111
+ self.states = states #有限状态集合
112
+ self.alphabet = alphabet #输入字母表
113
+ if tape_alphabet is None: #纸带字母表,包含输入字母表和空白符
114
+ self.tape_alphabet = list(alphabet) + [blank_symbol]
115
+ else:
116
+ self.tape_alphabet = tape_alphabet
117
+ self.transition = transition #转移函数
118
+ self.initial_state = initial_state #初始状态
119
+ self.accept_state = accept_state #接受状态
120
+ self.reject_state = reject_state #拒绝状态
121
+
122
+ #合法性检测
123
+ if tape_length <= 0:
124
+ raise ValueError("tape_length must be greater than 0")
125
+
126
+ #初始化纸带
127
+ self.tape_length = tape_length #纸带长度
128
+ self.blank_symbol = blank_symbol #空白符号
129
+ self.tape = [blank_symbol] * tape_length #写入初始空白符号
130
+ self.head = 0 #初始读写头位置
131
+ self.current_state = initial_state #当前状态为初始状态
132
+ self.steps = 0 #初始步数
133
+
134
+ #输入原始字符串
135
+ for i, char in enumerate(initial_tape):
136
+ if i < self.tape_length:
137
+ if char in self.tape_alphabet:
138
+ self.tape[i] = char
139
+ else:
140
+ raise ValueError(f"Initial_tape character '{char}' is not in tape_alphabet")
141
+
142
+ self.reset_data = (states, alphabet, transition, initial_state, accept_state, reject_state, tape_length, blank_symbol, tape_alphabet)
143
+
144
+ #读取方法
145
+ def read(self):
146
+ return self.tape[self.head]
147
+
148
+ #写入方法
149
+ def write(self, symbol):
150
+ if symbol in self.tape_alphabet:
151
+ self.tape[self.head] = symbol
152
+ else:
153
+ raise ValueError(f"Write symbol '{symbol}' is not in tape_alphabet") #写入符号不在纸带字母表内
154
+
155
+ #移动方法
156
+ def move(self, direction):
157
+ #读写头右移
158
+ if direction == "Right":
159
+ if self.head < self.tape_length - 1:
160
+ self.head += 1
161
+ else:
162
+ raise RuntimeError("Head moved out of bounds: reached right end of tape") #右方越界
163
+ #读写头左移
164
+ elif direction == "Left":
165
+ if self.head > 0:
166
+ self.head -= 1
167
+ else:
168
+ raise RuntimeError("Head moved out of bounds: reached left end of tape") #左方越界
169
+ #读写头不动
170
+ elif direction == "Stay":
171
+ pass
172
+ #方向未定义
173
+ else:
174
+ raise ValueError(f"Invalid move direction: '{direction}'") #无效的方向
175
+
176
+ #单步运行方法
177
+ def step(self):
178
+ if self.current_state == self.accept_state or self.current_state == self.reject_state:
179
+ return False
180
+
181
+ symbol = self.read()
182
+ key = (self.current_state, symbol)
183
+
184
+ if key not in self.transition:
185
+ raise RuntimeError(f"transition function undefined: '{key}'")
186
+ new_state, write_symbol, direction = self.transition[key]
187
+
188
+ self.write(write_symbol)
189
+ self.move(direction)
190
+ self.current_state = new_state
191
+ self.steps += 1
192
+ return True
193
+
194
+ #运行方法
195
+ def run(self,max_steps=10000):
196
+ self.steps = 0
197
+ while self.steps < max_steps and self.step():
198
+ pass
199
+ return self.tape_content()
200
+
201
+ #结果输出方法
202
+ def tape_content(self):
203
+ return "".join(self.tape)
204
+
205
+ #调试方法
206
+ def __repr__(self):
207
+ return(
208
+ f"LinearBoundedAutomaton(states={self.states}, alphabet={self.alphabet}, tape_alphabet={self.tape_alphabet}, "
209
+ f"initial_state={self.initial_state!r}, accept_state={self.accept_state!r}, reject_state={self.reject_state!r}, blank_symbol={self.blank_symbol!r}, "
210
+ f"tape_length={self.tape_length}, current_state={self.current_state!r}, initial_tape={self.tape!r}, "
211
+ f"head={self.head}, steps={self.steps}, "
212
+ f"transition={self.transition})"
213
+ )
214
+
215
+ if __name__ == "__main__":
216
+ LBA = LinearBoundedAutomaton(
217
+ states = {"q0","q_accept"},
218
+ alphabet = {"0","1"},
219
+ tape_alphabet = {"0","1"},
220
+ transition = {
221
+ ("q0", "0"): ("q1", "1", "Right"),
222
+ ("q0", "1"): ("q_accept", "1", "Stay"),
223
+ ("q1", "0"): ("q1", "1", "Left"),
224
+ ("q1", "1"): ("q_accept", "1", "Stay")
225
+ },
226
+ blank_symbol = "0",
227
+ initial_state = "q0",
228
+ accept_state = "q_accept",
229
+ reject_state = "q3",
230
+ initial_tape="00010"
231
+ )
232
+
233
+ print(LBA)
234
+ print(LBA.run())
235
+
236
+ FA = FiniteAutomaton(
237
+ states = {"q0", "q1", "q_accept"},
238
+ alphabet = {"a", "b"},
239
+ transition = {
240
+ ("q0", "a"): ("q1"), ("q0", "b"): ("q0"),
241
+ ("q1", "a"): ("q1"), ("q1", "b"): ("q_accept"),
242
+ ("q_accept", "a"): ("q1"), ("q_accept", "b"): ("q0")
243
+ },
244
+ initial_state = "q0",
245
+ accept_state = "q_accept",
246
+ initial_word = "abbab"
247
+ )
248
+ print(FA, FA.run())
249
+ pda = PushdownAutomaton(
250
+ states={'q0', 'q1', 'q2'},
251
+ alphabet={'a', 'b'},
252
+ stack_alphabet={'A', 'Z'}, # Z 是栈底标记
253
+ transition={
254
+ ('q0', 'a', 'Z'): ('q0', 'AZ'), # 读 a:压入 A
255
+ ('q0', 'a', 'A'): ('q0', 'AA'),
256
+ ('q0', 'b', 'A'): ('q1', ''), # 读 b:弹出 A
257
+ ('q1', 'b', 'A'): ('q1', ''),
258
+ ('q1', None, 'Z'): ('q2', 'Z'), # ε:a、b 数量一致 → 接受态
259
+ },
260
+ initial_state='q0',
261
+ accept_state='q2',
262
+ initial_word='aaabbb',
263
+ initial_stack_symbol='Z',
264
+ )
265
+
266
+ for w in ['ab', 'aabb', 'aaabbb', 'aaabb', 'aab', 'aabbb']:
267
+ pda.word = w
268
+ try:
269
+ print(w, '->', pda.run())
270
+ except RuntimeError as e:
271
+ print(w, '-> False (拒绝:无可用转移,', e.args[0], ')')
@@ -0,0 +1,7 @@
1
+ def busy_beaver_shift(states_number):
2
+ busy_beaver_S_l = [1, 6, 21, 107, 47176870]
3
+ return int(busy_beaver_S_l[states_number])
4
+
5
+ def busy_beaver_sigma(states_number):
6
+ busy_beaver_sigma_l = [1, 4, 6, 13, 4098]
7
+ return int(busy_beaver_sigma_l[states_number])
@@ -0,0 +1,49 @@
1
+ import random
2
+ import string
3
+ from turing_machine_main import TuringMachine
4
+
5
+
6
+ def random_machine(states_number, symbol_number):
7
+
8
+ states = tuple(f"q{i}" for i in range(states_number)) + ("q_accept", "q_reject")
9
+ alphabet = random.sample(string.ascii_lowercase, symbol_number)
10
+ blank = random.choice(alphabet)
11
+
12
+ transitions = {
13
+ (f"q{i}", alphabet[j]): (
14
+ random.choice(states),
15
+ random.choice(alphabet),
16
+ random.choice(["Left", "Right", "Stay"]),
17
+ )
18
+ for i in range(states_number)
19
+ for j in range(symbol_number)
20
+ }
21
+
22
+ return (states, alphabet, transitions, "q0", "q_accept", "q_reject",
23
+ blank, list(alphabet))
24
+
25
+
26
+ def two_symbols_machine(states_number):
27
+ states = tuple(f"q{i}" for i in range(states_number)) + ("q_accept", "q_reject")
28
+ alphabet = ["0", "1"]
29
+ blank = random.choice(alphabet)
30
+ transitions = {
31
+ (f"q{i}", alphabet[j]): (
32
+ random.choice(states),
33
+ random.choice(alphabet),
34
+ random.choice(["Left", "Right", "Stay"]),
35
+ )
36
+ for i in range(states_number)
37
+ for j in range(2)
38
+ }
39
+ return (states, alphabet, transitions, "q0", "q_accept", "q_reject",
40
+ blank, list(alphabet))
41
+
42
+
43
+ if __name__ == "__main__":
44
+ for i in range(100):
45
+ try:
46
+ tm = TuringMachine(*random_machine(3, 2)) # ① 必须传两个参数
47
+ print(f"#{i}: {tm.run()}")
48
+ except:
49
+ print(1)
@@ -0,0 +1,104 @@
1
+ import itertools
2
+ import copy
3
+ from turing_machine_main import TuringMachine
4
+
5
+ class Enumerator:
6
+ def __init__(self, machines, alphabet):
7
+ self.machines = list(machines)
8
+ self.alphabet = sorted(alphabet) if alphabet else ["0", "1"]
9
+
10
+ @staticmethod
11
+ def nth_string(alphabet, k):
12
+ m = k - 1
13
+ if m == 0:
14
+ return ""
15
+ base = len(alphabet)
16
+ m -= 1 # 跳过空串
17
+ length = 1
18
+ while m >= base ** length:
19
+ m -= base ** length
20
+ length += 1
21
+ chars = []
22
+ for _ in range(length):
23
+ m, r = divmod(m, base)
24
+ chars.append(alphabet[r])
25
+ return "".join(reversed(chars))
26
+
27
+ @staticmethod
28
+ def enumerate_string(alphabet):
29
+ yield ""
30
+ for length in itertools.count(1):
31
+ for chars in itertools.product(alphabet, repeat=length):
32
+ yield "".join(chars)
33
+
34
+ # 对所有 (j, k, i)(均 >=1)按 j+k+i 递增的顺序
35
+ @staticmethod
36
+ def enumerate_task():
37
+ for s in itertools.count(3):
38
+ for j in range(1, s - 1):
39
+ for k in range(1, s - j):
40
+ yield (j, k, s - j - k)
41
+
42
+ def run(self, max_outputs=None):
43
+ yielded = set()
44
+ outputs = 0
45
+ for j, k, i in self.enumerate_task():
46
+ if j > len(self.machines):
47
+ continue
48
+
49
+ machine = copy.deepcopy(self.machines[j - 1])
50
+ input_string = self.nth_string(self.alphabet, k)
51
+
52
+ machine.reset()
53
+ machine.set_input(input_string)
54
+
55
+ steps = 0
56
+ try:
57
+ while steps < i and not machine.is_halted:
58
+ machine.step()
59
+ steps += 1
60
+ except RuntimeError:
61
+ pass
62
+
63
+ if machine.is_accept and input_string not in yielded:
64
+ yielded.add(input_string)
65
+ yield input_string
66
+ outputs += 1
67
+ if max_outputs is not None and outputs >= max_outputs:
68
+ return
69
+
70
+
71
+ def recognizer(machine, input, max_steps):
72
+ sim = copy.deepcopy(machine)
73
+ sim.set_input(input)
74
+ while sim.steps <= max_steps:
75
+ if not sim.step():
76
+ return True
77
+ return False
78
+
79
+
80
+
81
+ if __name__ == "__main__":
82
+ a = TuringMachine(
83
+ states = {"q0","q1", "q_accept","q_reject"},
84
+ alphabet = {"0","1"},
85
+ tape_alphabet = {"0","1"},
86
+ transition = {
87
+ ("q0", "0"): ("q1", "1", "Right"),
88
+ ("q0", "1"): ("q_accept", "1", "Stay"),
89
+ ("q1", "0"): ("q1", "1", "Left"),
90
+ ("q1", "1"): ("q_accept", "1", "Stay")
91
+ },
92
+ blank_symbol = "0",
93
+ initial_state = "q0",
94
+ accept_state = "q_accept",
95
+ reject_state = "q_reject",
96
+ initial_tape="00010"
97
+ )
98
+
99
+
100
+ enum = Enumerator([a], ["0", "1"])
101
+ for output in enum.run(max_outputs=10):
102
+ print(output)
103
+
104
+ print(recognizer(a,"101",10), recognizer(a, "000", 1))
@@ -0,0 +1,169 @@
1
+ class TuringMachine:
2
+ """七元组图灵机 M = (Q, Σ, Γ, δ, q0, q_accept, q_reject)"""
3
+ def __init__(self, states, alphabet, transition, initial_state, accept_state,
4
+ reject_state, blank_symbol=" ", tape_alphabet=None, initial_tape=""):
5
+
6
+ self.states = set(states)
7
+ self.alphabet = set(alphabet)
8
+
9
+ if tape_alphabet is None:
10
+ self.tape_alphabet = self.alphabet | {blank_symbol}
11
+ else:
12
+ self.tape_alphabet = set(tape_alphabet) | {blank_symbol}
13
+
14
+ self.transition = transition
15
+ self.initial_state = initial_state
16
+ self.accept_state = accept_state
17
+ self.reject_state = reject_state
18
+ self.blank_symbol = blank_symbol
19
+
20
+ for s in (initial_state, accept_state):
21
+ if s not in self.states:
22
+ raise ValueError(f"State '{s}' is not in states")
23
+ for (q, sym), (q2, ws, d) in transition.items():
24
+ if q not in self.states or q2 not in self.states:
25
+ raise ValueError(f"Transition {((q, sym), (q2, ws, d))} uses undefined states")
26
+ if ws not in self.tape_alphabet:
27
+ raise ValueError(f"Transition write symbol '{ws}' is not in tape_alphabet")
28
+
29
+ for char in initial_tape:
30
+ if char not in self.tape_alphabet:
31
+ raise ValueError(f"Initial_tape character '{char}' is not in tape_alphabet")
32
+
33
+ # ---- 初始化纸带:输入从下标 0 开始,读写头指向第一个字符 ----
34
+ self.tape = list(initial_tape)
35
+ self.head = 0
36
+ self.current_state = initial_state
37
+ self.steps = 0
38
+
39
+ self._initial_tape = list(self.tape)
40
+
41
+ def _ensure_cell(self):
42
+ while self.head < 0:
43
+ self.tape.insert(0, self.blank_symbol)
44
+ self.head += 1
45
+ while self.head >= len(self.tape):
46
+ self.tape.append(self.blank_symbol)
47
+
48
+ def read(self):
49
+ self._ensure_cell()
50
+ return self.tape[self.head]
51
+
52
+ def write(self, symbol):
53
+ if symbol not in self.tape_alphabet:
54
+ raise ValueError(f"Write symbol '{symbol}' is not in tape_alphabet")
55
+ self._ensure_cell()
56
+ self.tape[self.head] = symbol
57
+
58
+ def move(self, direction):
59
+ if direction == "Right":
60
+ self.head += 1
61
+ elif direction == "Left":
62
+ self.head -= 1
63
+ elif direction == "Stay":
64
+ pass
65
+ else:
66
+ raise ValueError(f"Unknown direction: '{direction}'")
67
+ self._ensure_cell()
68
+
69
+ def step(self):
70
+ if self.current_state in (self.accept_state, self.reject_state):
71
+ return False
72
+ key = (self.current_state, self.read())
73
+ if key not in self.transition:
74
+ raise RuntimeError(f"transition function undefined: '{key}'")
75
+ new_state, write_symbol, direction = self.transition[key]
76
+ self.write(write_symbol)
77
+ self.move(direction)
78
+ self.current_state = new_state
79
+ self.steps += 1
80
+ return True
81
+
82
+ def run(self, max_steps=10000):
83
+ self.reset()
84
+ while self.steps < max_steps and self.step():
85
+ pass
86
+ return self.tape_content()
87
+
88
+ def reset(self):
89
+ self.tape = list(self._initial_tape)
90
+ self.head = 0
91
+ self.current_state = self.initial_state
92
+ self.steps = 0
93
+
94
+ def set_input(self, initial_tape):
95
+ for i, char in enumerate(initial_tape):
96
+ if char not in self.tape_alphabet:
97
+ raise ValueError(f"Initial_tape character '{char}' is not in tape_alphabet")
98
+
99
+ tape_length = getattr(self, "tape_length", None)
100
+
101
+ if tape_length is not None and len(initial_tape) > tape_length:
102
+ raise ValueError(
103
+ f"initial_tape length {len(initial_tape)} exceeds tape_length {tape_length}"
104
+ )
105
+ if tape_length is not None:
106
+ self.tape = [self.blank_symbol] * tape_length
107
+ for i, char in enumerate(initial_tape):
108
+ self.tape[i] = char
109
+ else:
110
+ self.tape = list(initial_tape) or [self.blank_symbol]
111
+
112
+
113
+ self.head = 0
114
+ self.current_state = self.initial_state
115
+ self.steps = 0
116
+
117
+
118
+ self._initial_tape = list(self.tape)
119
+
120
+ return self.tape
121
+
122
+ @property
123
+ def is_halted(self):
124
+ return self.current_state in (self.accept_state, self.reject_state)
125
+
126
+ @property
127
+ def is_accept(self):
128
+ return self.current_state == self.accept_state
129
+
130
+ def tape_content(self):
131
+ return "".join(self.tape)
132
+
133
+ def __repr__(self):
134
+ return (
135
+ f"TuringMachine(states={self.states}, alphabet={self.alphabet}, "
136
+ f"tape_alphabet={self.tape_alphabet}, initial_state={self.initial_state!r}, "
137
+ f"accept_state={self.accept_state!r}, reject_state={self.reject_state!r}, "
138
+ f"blank_symbol={self.blank_symbol!r}, current_state={self.current_state!r}, "
139
+ f"tape={''.join(self.tape)!r}, head={self.head}, steps={self.steps})"
140
+ )
141
+
142
+
143
+ if __name__ == "__main__":
144
+ # 一台"纸带上含有 1 就接受"的图灵机,顺便演示读写头越界处理
145
+ def make_tm(input_tape):
146
+ return TuringMachine(
147
+ states={"q0", "q_accept", "q_reject"},
148
+ alphabet={"0", "1"},
149
+ tape_alphabet={"0", "1", "_"},
150
+ transition={
151
+ ("q0", "0"): ("q0", "0", "Right"),
152
+ ("q0", "1"): ("q_accept", "1", "Stay"),
153
+ ("q0", "_"): ("q_reject", "_", "Stay"),
154
+ },
155
+ blank_symbol="_",
156
+ initial_state="q0",
157
+ accept_state="q_accept",
158
+ reject_state="q_reject",
159
+ initial_tape=input_tape,
160
+ )
161
+
162
+ tm = make_tm("00010")
163
+ print(tm)
164
+ print("tape:", tm.run())
165
+ print("state:", tm.current_state, "| steps:", tm.steps)
166
+
167
+ tm2 = make_tm("00000")
168
+ print("tape:", tm2.run())
169
+ print("state:", tm2.current_state, "| steps:", tm2.steps)
@@ -0,0 +1,134 @@
1
+ class MultiTapeTuringMachine:
2
+ #默认方法:属性及初始化
3
+ def __init__(self, tapes_number, states, alphabet, transition, initial_state, accept_state, reject_state, tape_length = 100, blank_symbol = " ", tape_alphabet = None, initial_tape = ""):
4
+
5
+ self.tapes_number = tapes_number #纸带数量
6
+
7
+ #七元组属性
8
+ self.states = states #有限状态集合
9
+ self.alphabet = alphabet #输入字母表
10
+ if tape_alphabet is None: #纸带字母表,包含输入字母表和空白符
11
+ self.tape_alphabet = alphabet + [blank_symbol]
12
+ else:
13
+ self.tape_alphabet = tape_alphabet
14
+ self.transition = transition #转移函数
15
+ self.initial_state = initial_state #初始状态
16
+ self.accept_state = accept_state #接受状态
17
+ self.reject_state = reject_state #拒绝状态
18
+
19
+ #合法性检测
20
+ if tape_length <= 0:
21
+ raise ValueError("tape_length must be greater than 0")
22
+
23
+ #初始化纸带(动态扩展列表:只存放已触及的格子,读写头越界时自动补空白符)
24
+ self.tape_length = tape_length #保留原属性仅供兼容/参考,不再限制纸带长度
25
+ self.blank_symbol = blank_symbol #空白符号
26
+ self.tapes = [[self.blank_symbol] for _ in range(self.tapes_number)] #每条纸带初始一格空白
27
+ self.heads = [0] * self.tapes_number #初始化多个读写头位置
28
+ self.current_state = initial_state #当前状态为初始状态
29
+ self.steps = 0 #初始步数
30
+
31
+ #输入原始字符串(写入第 0 条纸带,按需扩展)
32
+ for i, char in enumerate(initial_tape):
33
+ if char in self.tape_alphabet:
34
+ if i == 0:
35
+ self.tapes[0][i] = char
36
+ else:
37
+ self.tapes[0].append(char)
38
+ else:
39
+ raise ValueError(f"Initial_tape character '{char}' is not in tape_alphabet")
40
+
41
+ #读取方法
42
+ def read(self):
43
+ return [self.tapes[i][self.heads[i]] for i in range(self.tapes_number)]
44
+
45
+ #写入方法
46
+ def write(self, symbols):
47
+ for i in range(self.tapes_number):
48
+ if symbols[i] in self.tape_alphabet:
49
+ self.tapes[i][self.heads[i]] = symbols[i]
50
+ else:
51
+ raise ValueError(f"Write symbol '{symbols[i]}' is not in tape_alphabet") #写入符号不在纸带字母表内
52
+
53
+ #移动方法
54
+ def move(self, directions):
55
+ for i in range(self.tapes_number):
56
+ #读写头右移(越界时在纸带末端动态补充空白符)
57
+ if directions[i] == "Right":
58
+ if self.heads[i] < len(self.tapes[i]) - 1:
59
+ self.heads[i] += 1
60
+ else:
61
+ self.tapes[i].append(self.blank_symbol)
62
+ self.heads[i] += 1
63
+ #读写头左移(越界时在纸带左端动态补充空白符,读写头停留原位指向新补的空白格)
64
+ elif directions[i] == "Left":
65
+ if self.heads[i] > 0:
66
+ self.heads[i] -= 1
67
+ else:
68
+ self.tapes[i].insert(0, self.blank_symbol)
69
+ #读写头不动
70
+ elif directions[i] == "Stay":
71
+ pass
72
+ #方向未定义
73
+ else:
74
+ raise ValueError(f"Invalid move direction '{directions[i]}' for head {i}") #无效的方向
75
+
76
+ #单步运行方法
77
+ def step(self):
78
+ if self.current_state == self.accept_state or self.current_state == self.reject_state:
79
+ return False
80
+
81
+ symbol = self.read()
82
+ key = (self.current_state, tuple(symbol))
83
+
84
+ if key not in self.transition:
85
+ raise RuntimeError(f"transition function undefined: '{key}'")
86
+ new_state, write_symbols, directions = self.transition[key]
87
+
88
+ self.write(write_symbols)
89
+ self.move(directions)
90
+ self.current_state = new_state
91
+ self.steps += 1
92
+ return True
93
+
94
+ #运行方法
95
+ def run(self, max_steps=10000):
96
+ self.steps = 0
97
+ while self.steps < max_steps and self.step():
98
+ pass
99
+ return self.tape_content()
100
+
101
+ #结果输出方法
102
+ def tape_content(self):
103
+ return ["".join(self.tapes[i]) for i in range(self.tapes_number)]
104
+
105
+ #调试方法
106
+ def __repr__(self):
107
+ return(
108
+ f"MultiTapeTuringMachine(states={self.states}, alphabet={self.alphabet}, tape_alphabet={self.tape_alphabet}, "
109
+ f"initial_state={self.initial_state!r}, accept_state={self.accept_state!r}, reject_state={self.reject_state!r}, blank_symbol={self.blank_symbol!r}, "
110
+ f"tape_length={self.tape_length}, current_state={self.current_state!r}, initial_tape={self.tapes[0]!r}, tapes_number={self.tapes_number}, "
111
+ f"heads={self.heads}, steps={self.steps}, "
112
+ f"transition={self.transition})"
113
+ )
114
+
115
+ if __name__ == "__main__":
116
+ MTM = MultiTapeTuringMachine(
117
+ states = {"q0","q_accept"},
118
+ alphabet = {"0","1"},
119
+ tape_alphabet = {"0","1"},
120
+ transition = {
121
+ ("q0", ("0", "0")): ("q1", ("1", "0"), ("Right", "Stay")),
122
+ ("q1", ("0", "0")): ("q1", ("1", "0"), ("Left", "Stay")),
123
+ ("q1", ("1", "0")): ("q_accept", ("1", "0"), ("Stay", "Stay"))
124
+ },
125
+ blank_symbol = "0",
126
+ initial_state = "q0",
127
+ accept_state = "q_accept",
128
+ reject_state = "q3",
129
+ initial_tape="00010",
130
+ tapes_number=2
131
+ )
132
+
133
+ print(MTM.run())
134
+ print(MTM)
@@ -0,0 +1,151 @@
1
+ from collections import deque
2
+ from turing_machine_main import TuringMachine
3
+
4
+
5
+ class NondeterministicTuringMachine(TuringMachine):
6
+
7
+ def __init__(self, states, alphabet, transition, initial_state, accept_state,
8
+ reject_state, blank_symbol=" ", tape_alphabet=None, initial_tape=""):
9
+
10
+ if not isinstance(transition, dict):
11
+ raise TypeError("transition must be a dict")
12
+ for key, rules in transition.items():
13
+ if not isinstance(rules, list):
14
+ raise TypeError(
15
+ f"transition[{key!r}] must be a list of "
16
+ f"(new_state, write_symbol, direction)"
17
+ )
18
+ for rule in rules:
19
+ if not (isinstance(rule, tuple) and len(rule) == 3):
20
+ raise TypeError(
21
+ f"each rule in transition[{key!r}] must be a 3-tuple "
22
+ f"(new_state, write_symbol, direction), got {rule!r}"
23
+ )
24
+
25
+ super().__init__(states, alphabet, {}, initial_state, accept_state,
26
+ reject_state, blank_symbol, tape_alphabet, initial_tape)
27
+ self.transition = transition
28
+ self._initial_tape = list(self.tape) or [self.blank_symbol] # 供 run() 每次从头开始
29
+
30
+ # ---------- 语义校验 ----------
31
+ for (state, symbol), rules in transition.items():
32
+ if state not in self.states:
33
+ raise ValueError(f"transition state '{state}' is not in states")
34
+ for new_state, write_symbol, direction in rules:
35
+ if new_state not in self.states:
36
+ raise ValueError(f"new state '{new_state}' is not in states")
37
+ if write_symbol not in self.tape_alphabet:
38
+ raise ValueError(f"write symbol '{write_symbol}' is not in tape_alphabet")
39
+ if direction not in ("Right", "Left", "Stay"):
40
+ raise ValueError(f"invalid move direction: '{direction}'")
41
+
42
+ # ---------- 纸带动态扩展 ----------
43
+ @staticmethod
44
+ def _move_head(tape, head, direction, blank_symbol):
45
+ if direction == "Right":
46
+ if head == len(tape) - 1:
47
+ tape.append(blank_symbol) # 右端动态补一格空白
48
+ return head + 1
49
+ if direction == "Left":
50
+ if head == 0:
51
+ tape.insert(0, blank_symbol) # 左端动态补一格空白
52
+ return 0
53
+ return head - 1
54
+ return head # Stay
55
+
56
+ def _canonical_key(self, state, tape, head):
57
+ start = 0
58
+ end = len(tape)
59
+ while start < head and tape[start] == self.blank_symbol:
60
+ start += 1
61
+ while end > head + 1 and tape[end - 1] == self.blank_symbol:
62
+ end -= 1
63
+ return (state, tuple(tape[start:end]), head - start)
64
+
65
+ def _next_configurations(self, state, tape, head, steps):
66
+ rules = self.transition.get((state, tape[head]))
67
+ if not rules:
68
+ return [] # 无转移定义:死分支
69
+
70
+ successors = []
71
+ for new_state, write_symbol, direction in rules:
72
+ new_tape = list(tape)
73
+ new_tape[head] = write_symbol
74
+ new_head = self._move_head(new_tape, head, direction, self.blank_symbol)
75
+ successors.append((new_state, new_tape, new_head, steps + 1))
76
+ return successors
77
+
78
+ def run(self, max_steps=10000):
79
+ # 每次 run 都从初始配置重新开始(纸带、读写头、状态全部复位)
80
+ self.tape = list(self._initial_tape)
81
+ self.head = 0
82
+ self.current_state = self.initial_state
83
+ self.steps = 0
84
+
85
+ # 广度优先搜索遍历所有非确定性分支
86
+ start = (self.initial_state, list(self.tape), 0, 0)
87
+ queue = deque([start])
88
+ visited = {self._canonical_key(*start[:3])} # 规范化去重,防纸带无限扩展
89
+
90
+ while queue:
91
+ state, tape, head, steps = queue.popleft()
92
+
93
+ if state == self.accept_state:
94
+ self.current_state = state
95
+ self.tape = tape
96
+ self.head = head
97
+ self.steps = steps
98
+ return self.tape_content()
99
+
100
+ if state == self.reject_state or steps >= max_steps:
101
+ continue
102
+
103
+ for nxt in self._next_configurations(state, tape, head, steps):
104
+ nstate, ntape, nhead, nsteps = nxt
105
+ key = self._canonical_key(nstate, ntape, nhead)
106
+ if key in visited:
107
+ continue
108
+ visited.add(key)
109
+ queue.append(nxt)
110
+
111
+ return None # 所有分支均未接受
112
+
113
+ def step(self):
114
+ raise NotImplementedError(
115
+ "NondeterministicTuringMachine not supported step(), please use run()"
116
+ )
117
+
118
+ def __repr__(self):
119
+ return (
120
+ f"NondeterministicTuringMachine(states={self.states}, alphabet={self.alphabet}, "
121
+ f"tape_alphabet={self.tape_alphabet}, initial_state={self.initial_state!r}, "
122
+ f"accept_state={self.accept_state!r}, reject_state={self.reject_state!r}, "
123
+ f"blank_symbol={self.blank_symbol!r}, current_state={self.current_state!r}, "
124
+ f"tape={''.join(self.tape)!r}, head={self.head}, steps={self.steps})"
125
+ )
126
+
127
+
128
+ if __name__ == "__main__":
129
+ # 非确定性地判定"纸带上含有 11 子串"
130
+ def make_ntm(input_tape):
131
+ return NondeterministicTuringMachine(
132
+ states={"q0", "q1", "q_accept", "q_reject"},
133
+ alphabet={"0", "1"},
134
+ tape_alphabet={"0", "1", "_"},
135
+ transition={
136
+ ("q0", "0"): [("q0", "0", "Right")],
137
+ ("q0", "1"): [("q0", "1", "Right"), ("q1", "1", "Right")],
138
+ ("q0", "_"): [("q_reject", "_", "Stay")],
139
+ ("q1", "1"): [("q_accept", "1", "Stay")],
140
+ },
141
+ blank_symbol="_",
142
+ initial_state="q0",
143
+ accept_state="q_accept",
144
+ reject_state="q_reject",
145
+ initial_tape=input_tape,
146
+ )
147
+
148
+ ntm = make_ntm("01011")
149
+ print(ntm.run())
150
+ print(ntm.current_state, ntm.steps)
151
+ print(ntm)
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.4
2
+ Name: automaton-simulate
3
+ Version: 0.1.0
4
+ Summary: A library for simulating automatons
5
+ Author-email: Dayu811 <13691283074@163.com>
6
+ License: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Dynamic: license-file
14
+
15
+ # py-automaton
16
+
17
+ A lightweight Python library for simulating automata and turing machine.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pip install automaton-simulate
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/automaton_simulate/__init__.py
5
+ src/automaton_simulate/automaton_main.py
6
+ src/automaton_simulate/function_affiliate.py
7
+ src/automaton_simulate/random_machine.py
8
+ src/automaton_simulate/turing_machine_formal_language.py
9
+ src/automaton_simulate/turing_machine_main.py
10
+ src/automaton_simulate/turing_machine_multitape.py
11
+ src/automaton_simulate/turing_machine_nondeterministic.py
12
+ src/automaton_simulate.egg-info/PKG-INFO
13
+ src/automaton_simulate.egg-info/SOURCES.txt
14
+ src/automaton_simulate.egg-info/dependency_links.txt
15
+ src/automaton_simulate.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ automaton_simulate