Skip to content

Commit

Permalink
Merge 3a5f069 into ef0e02c
Browse files Browse the repository at this point in the history
  • Loading branch information
eliotwrobson committed Nov 4, 2023
2 parents ef0e02c + 3a5f069 commit e94e100
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 33 deletions.
49 changes: 19 additions & 30 deletions automata/fa/dfa.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ def __len__(self) -> int:
"""Returns the cardinality of the language represented by the DFA."""
return self.cardinality()

def to_partial(self, *, retain_names: bool = False, minify: bool = True) -> Self:
def to_partial(self, *, retain_names: bool = False) -> Self:
"""
Turns a DFA (complete or not) into a partial DFA.
Removes dead states and trap states (except the initial state)
Expand All @@ -242,18 +242,6 @@ def to_partial(self, *, retain_names: bool = False, minify: bool = True) -> Self
new_states = live_states & non_trap_states
new_states.add(self.initial_state)

if minify:
# No need to alter transitions here, since unused entries in
# that dict are removed automatically by the minify call
return self.__class__._minify(
reachable_states=new_states,
input_symbols=self.input_symbols,
transitions=self.transitions,
initial_state=self.initial_state,
reachable_final_states=self.final_states & new_states,
retain_names=retain_names,
)

return self.__class__(
states=new_states,
input_symbols=self.input_symbols,
Expand Down Expand Up @@ -454,30 +442,31 @@ def minify(self, retain_names: bool = False) -> Self:
"""

if self.allow_partial:
# In the case of a partial DFA, we want to try to condense
# possible trap states before the main minify operation.
graph = self._get_digraph()
live_states = nx.descendants(graph, self.initial_state) | {
self.initial_state
# In the case of a partial DFA, we need to add back the trap state for
# minimization to work right
trap_state = self._get_trap_state_id()
default_to_trap = {symbol: trap_state for symbol in self.input_symbols}
transitions = {
state: {**default_to_trap, **lookup}
for state, lookup in self.transitions.items()
}
non_trap_states = set(self.final_states).union(
*(nx.ancestors(graph, state) for state in self.final_states)
)
reachable_states = live_states & non_trap_states
reachable_states.add(self.initial_state)
transitions[trap_state] = default_to_trap

else:
# Compute reachable states and final states
bfs_states = self.__class__._bfs_states(
self.initial_state, lambda state: iter(self.transitions[state].items())
)
reachable_states = set(bfs_states)
transitions = cast(DFATransitionsT, self.transitions)

bfs_states = self.__class__._bfs_states(
self.initial_state, lambda state: iter(transitions[state].items())
)
reachable_states = set(bfs_states)

reachable_final_states = self.final_states & reachable_states

return self.__class__._minify(
reachable_states=reachable_states,
input_symbols=self.input_symbols,
transitions=self.transitions,
transitions=transitions,
initial_state=self.initial_state,
reachable_final_states=reachable_final_states,
retain_names=retain_names,
Expand Down Expand Up @@ -509,7 +498,7 @@ def _minify(

# Per input-symbol backmap (tgt -> origin states)
transition_back_map: Dict[str, Dict[DFAStateT, List[DFAStateT]]] = {
symbol: {end_state: list() for end_state in reachable_states}
symbol: {end_state: [] for end_state in reachable_states}
for symbol in input_symbols
}

Expand All @@ -518,7 +507,7 @@ def _minify(
for symbol, end_state in path.items():
symbol_dict = transition_back_map[symbol]
# If statement here needed to ignore certain transitions
# when minifying a partial DFA.
# from non-reachable states.
if end_state in symbol_dict:
symbol_dict[end_state].append(start_state)

Expand Down
17 changes: 14 additions & 3 deletions tests/test_dfa.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,13 @@ def test_equivalence_partials(self) -> None:
complete_dfa = self.partial_dfa.to_complete()
self.assertEqual(self.partial_dfa, self.partial_dfa)
self.assertEqual(self.partial_dfa, complete_dfa)
self.assertEqual(self.partial_dfa, complete_dfa.to_partial(minify=False))
self.assertEqual(self.partial_dfa, complete_dfa.to_partial())

dfa = DFA.from_finite_language(
language={"ab", "abcb"}, input_symbols={"a", "b", "c"}, as_partial=True
)

self.assertEqual(dfa.minify(), dfa)

def test_equivalence_minify(self) -> None:
"""Should be equivalent after minify."""
Expand Down Expand Up @@ -1276,7 +1282,7 @@ def test_minify_partial_dfa(self) -> None:
allow_partial=True,
)

minified_partial_dfa = partial_dfa_extra_state.minify()
minified_partial_dfa = partial_dfa_extra_state.minify().to_partial()
self.assertEqual(len(minified_partial_dfa.states), 4)
self.assertEqual(minified_partial_dfa, partial_dfa_extra_state)

Expand Down Expand Up @@ -1628,6 +1634,8 @@ def test_minimal_finite_language_large(self, as_partial: bool) -> None:
{"a", "b"}, language, as_partial=as_partial
)
minimal_dfa = equiv_dfa.minify()
if as_partial:
minimal_dfa = minimal_dfa.to_partial()

self.assertEqual(equiv_dfa, minimal_dfa)
self.assertEqual(len(equiv_dfa.states), len(minimal_dfa.states))
Expand Down Expand Up @@ -2015,7 +2023,10 @@ def test_contains_prefix(self, as_partial: bool) -> None:
input_symbols = {"a", "n", "o", "b"}

prefix_dfa = DFA.from_prefix(input_symbols, "nano", as_partial=as_partial)
self.assertEqual(len(prefix_dfa.states), len(prefix_dfa.minify().states))
minimal_dfa = prefix_dfa.minify()
if as_partial:
minimal_dfa = minimal_dfa.to_partial()
self.assertEqual(len(prefix_dfa.states), len(minimal_dfa.states))

subset_dfa = DFA.from_finite_language(
input_symbols,
Expand Down

0 comments on commit e94e100

Please sign in to comment.