In [1]:
from tock import *

Nondeterministic finite automata

A nondeterministic finite automaton (NFA) can bilocate, even multilocate: on reading an input symbol, there may be more than one state that it can change to, and it pursues all possible paths in parallel. If any path reaches the end of the input in a final state, then the automaton accepts the string.


In [2]:
m = read_csv("../examples/sipser-1-27.csv")
m.is_finite(), m.is_deterministic()


Out[2]:
(True, False)

In [3]:
to_table(m)


Out[3]:
ε 0 1
>q1 q1 {q1,q2}
q2 q3 q3
q3 q4
@q4 q4 q4

There are two main differences in this table from a DFA. First, a cell can have more than one state. For example, if the machine is in state q1 and the next input symbol is a 1, then the machine can change to either q1 or q2. Use curly braces for sets of states. (It's an error to omit them.) If a cell has no states, either write {} or leave the cell blank.

The second difference is that there is a column for the empty string, which is written &. (The ampersand is supposed to look like "et", so I figured it would be a good approximation to ε, which is a Greek "e".) If the machine is in state q2, then it can change to state q3 without reading in any input symbols.

This is what the state transition diagram looks like:


In [4]:
to_graph(m)


%3 _START 0 q1 _START->0 0->0 0 1 1 q2 0->1 1 2 q3 1->2 0 ε 3 q4 2->3 1 3->3 0 1

Now it's a little easier to see that this machine accepts strings that contain either 101 or 11. Let's run the automaton on a string:


In [5]:
run(m, "1 0 1 1 0 1")


%3 _START 0 q1 _START->0 1 q2 0->1 2 q1 0->2 3 q3 1->3 4 q3 1->4 5 q1 2->5 6 q4 3->6 7 q2 5->7 8 q1 5->8 9 q4 6->9 10 q3 7->10 11 q2 8->11 12 q1 8->12 13 q4 9->13 10->9 14 q3 11->14 15 q3 11->15 16 q1 12->16 17 q4 13->17 14->17 18 q2 16->18 19 q1 16->19 20 q3 18->20 21 22 21->22 1 23 22->23 0 24 23->24 1 25 24->25 1 26 25->26 0 27 26->27 1

As in a DFA run, each node indicates a configuration, that is, a state that the machine can be in at a particular time. Nodes in the same column correspond to the same input position. An edge between two configurations means that the automaton can move from one to the other. Note that unlike in a DFA run, a node can have more than one outgoing edge. Since there is a path that ends with a double node, the machine accepted the string.

Here's another string:


In [6]:
run(m, "0 1 0 0 1 0")


%3 _START 0 q1 _START->0 1 q1 0->1 2 q2 1->2 3 q1 1->3 4 q3 2->4 5 q3 2->5 6 q1 3->6 7 q1 6->7 8 q2 7->8 9 q1 7->9 10 q3 8->10 11 q3 8->11 12 q1 9->12 13 14 13->14 0 15 14->15 1 16 15->16 0 17 16->17 0 18 17->18 1 19 18->19 0

The absence of a double node means that the string was rejected.

In a nondeterministic automaton, it's possible that there are infinitely many runs for a given input string. That's not a problem -- consider the following NFA, which (for some reason) loops through a bunch of epsilon transitions before reading in a single 0.


In [7]:
m = read_csv("../examples/nfaloop.csv")
to_graph(m)


%3 _START 0 q1 _START->0 1 q2 0->1 0 2 q3 0->2 ε 2->2 ε

In [8]:
run(m, "0")


%3 _START 2 q1 _START->2 0 q3 0->0 1 q2 2->0 2->1 3 4 3->4 0

This graph represents an infinite number of runs, each of which loops in state q1 for some number of times, then moves on to state q2.