Skip to content

Commit df12009

Browse files
committed
feat: should done impl and test for accept string/language, but accept_same_language may need some adjustment after minimization is done
1 parent 4a93d1f commit df12009

3 files changed

Lines changed: 158 additions & 70 deletions

File tree

evaluation_function/schemas/validation.py

Lines changed: 0 additions & 65 deletions
This file was deleted.
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
from itertools import product
2+
from typing import Set
3+
from ..schemas.fsa import FSA
4+
5+
def is_valid_fsa(fsa: FSA) -> bool:
6+
states = set(fsa.states)
7+
alphabet = set(fsa.alphabet)
8+
9+
if fsa.initial_state not in states:
10+
return False
11+
12+
if not set(fsa.accept_states).issubset(states):
13+
return False
14+
15+
for t in fsa.transitions:
16+
if t.from_state not in states:
17+
return False
18+
if t.to_state not in states:
19+
return False
20+
if t.symbol not in alphabet:
21+
return False
22+
23+
return True
24+
25+
def is_deterministic(fsa: FSA) -> bool:
26+
if not is_valid_fsa(fsa):
27+
return False
28+
29+
seen = set()
30+
31+
for t in fsa.transitions:
32+
key = (t.from_state, t.symbol)
33+
if key in seen:
34+
return False
35+
seen.add(key)
36+
37+
return True
38+
39+
def is_complete(fsa: FSA) -> bool:
40+
if not is_deterministic(fsa):
41+
return False
42+
43+
states = set(fsa.states)
44+
alphabet = set(fsa.alphabet)
45+
46+
seen = {(t.from_state, t.symbol) for t in fsa.transitions}
47+
48+
for state in states:
49+
for symbol in alphabet:
50+
if (state, symbol) not in seen:
51+
return False
52+
53+
return True
54+
55+
def classify_fsa(fsa: FSA) -> dict:
56+
return {
57+
"valid": is_valid_fsa(fsa),
58+
"deterministic": is_deterministic(fsa),
59+
"complete": is_complete(fsa),
60+
}
61+
62+
# simple bfs
63+
def accepts_string(fsa: FSA, string: str) -> bool:
64+
"""
65+
Simulate the FSA on a given string.
66+
Returns True if the string is accepted, False otherwise.
67+
"""
68+
current_states: Set[str] = {fsa.initial_state}
69+
70+
for symbol in string:
71+
next_states = set()
72+
for state in current_states:
73+
for t in fsa.transitions:
74+
if t.from_state == state and t.symbol == symbol:
75+
next_states.add(t.to_state)
76+
current_states = next_states
77+
if not current_states:
78+
return False
79+
80+
return any(state in fsa.accept_states for state in current_states)
81+
82+
83+
def fsas_accept_same_string(fsa1: FSA, fsa2: FSA, string: str) -> bool:
84+
"""
85+
Check if two FSAs accept the same given string.
86+
"""
87+
return accepts_string(fsa1, string) and accepts_string(fsa2, string)
88+
89+
def fsas_accept_same_language(fsa1: FSA, fsa2: FSA, max_length: int = 5) -> bool:
90+
"""
91+
Approximate check if two FSAs accept the same language.
92+
Checks all strings over the alphabet up to length `max_length`.
93+
Warning: exponential in alphabet size * max_length.
94+
"""
95+
alphabet = fsa1.alphabet
96+
if set(fsa1.alphabet) != set(fsa2.alphabet):
97+
return False
98+
99+
for length in range(max_length + 1):
100+
for s in product(alphabet, repeat=length):
101+
string = ''.join(s)
102+
if accepts_string(fsa1, string) != accepts_string(fsa2, string):
103+
return False
104+
return True
105+
# Note: This is practical for small alphabets and short strings.
106+
# For full correctness on infinite languages, you need minimized DFA equivalence.
107+
108+
# is_nfa()
109+
# make_complete()
110+
# add_sink_state()
111+

tests/validation/test_fsa_validation.py

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
import unittest
22

3-
from evaluation_function.schemas.validation import (
4-
is_valid_fsa,
5-
is_deterministic,
6-
is_complete,
7-
)
3+
from evaluation_function.validation.validation import *
84

95
from evaluation_function.schemas.fsa import FSA, Transition
106
from .utils import make_fsa
@@ -115,6 +111,52 @@ def test_single_state_complete_fsa(self):
115111
)
116112

117113
self.assertTrue(is_complete(fsa))
114+
115+
def test_accepts_string(self):
116+
fsa = make_fsa(
117+
states=["q0", "q1"],
118+
alphabet=["a"],
119+
transitions=[{"from_state": "q0", "to_state": "q1", "symbol": "a"}],
120+
initial="q0",
121+
accept=["q1"]
122+
)
123+
124+
assert accepts_string(fsa, "a") is True
125+
assert accepts_string(fsa, "") is False
126+
assert accepts_string(fsa, "aa") is False
127+
128+
def test_fsas_accept_same_language(self):
129+
fsa1 = make_fsa(
130+
states=["q0", "q1"],
131+
alphabet=["a"],
132+
transitions=[{"from_state": "q0", "to_state": "q1", "symbol": "a"}],
133+
initial="q0",
134+
accept=["q1"]
135+
)
136+
137+
fsa2 = make_fsa(
138+
states=["s0", "s1"],
139+
alphabet=["a"],
140+
transitions=[{"from_state": "s0", "to_state": "s1", "symbol": "a"}],
141+
initial="s0",
142+
accept=["s1"]
143+
)
144+
145+
fsa3 = make_fsa(
146+
states=["q0", "q1"],
147+
alphabet=["a"],
148+
transitions=[{"from_state": "q0", "to_state": "q1", "symbol": "a"}],
149+
initial="q0",
150+
accept=["q0"] # different accept state
151+
)
152+
153+
# fsa1 and fsa2 are equivalent
154+
assert fsas_accept_same_language(fsa1, fsa2, max_length=1) is True
155+
156+
# fsa1 and fsa3 are not equivalent
157+
assert fsas_accept_same_language(fsa1, fsa3, max_length=1) is False
158+
159+
118160

119161

120162
if __name__ == "__main__":

0 commit comments

Comments
 (0)