\documentclass{article}

\usepackage{xspace,graphicx,hyperref,paralist}

\newtheorem{illustration}{Illustration}[section]


\newcounter{exer}
\renewcommand{\theexer}{\thechapter:\arabic{exer}}

\newcommand{\FIXIT}[1]{\typeout #1 -- {\LARGE\sf #1}}

%---
\newcommand{\TM}{{Turing machine}\xspace}
\newcommand{\TMs}{{Turing machines}\xspace}
\newcommand{\NPCness}{{NP-completeness}\xspace}
\newcommand{\NPC}{{NP-complete}\xspace}
\newcommand{\NPH}{{NP-hard}\xspace}
\newcommand{\sfP}{{\sf P}\xspace}
\newcommand{\sfNP}{{\sf NP}\xspace}
\newcommand{\sfNPC}{{\sf NPC}\xspace}
\newcommand{\sfNPH}{{\sf NPH}\xspace}
\newcommand{\sfCoNP}{{\sf CoNP}\xspace}
\newcommand{\sfCoNPC}{{\sf CoNPC}\xspace}
\newcommand{\BA}{{B\"{u}chi automata}\xspace}
\newcommand{\BAon}{{B\"{u}chi automaton}\xspace}
\newcommand{\TL}{{Temporal Logic}\xspace}
\newcommand{\LTL}{{linear-time temporal logic}\xspace}
\newcommand{\CTL}{{Computational Tree Logic}\xspace}
\newcommand{\KS}{{Kripke structure}\xspace}
\newcommand{\KSs}{{Kripke structures}\xspace}
\newcommand{\OR}{{$\omega$-regular}\xspace}
%---


\oddsidemargin 0cm
\textwidth 17cm

\begin{document}


\title{\large\bf CS 3100 -- Models of Computation -- Fall 2011 --  Notes for L7}

\maketitle

\section{On the role of DFA and NFA, IMPORTANT TERMINOLOGY}

\begin{itemize}
\item A DFA $D$ is a five-tuple $(Q,\Sigma,\delta,q_0,F)$, and you can look
  up notes5.pdf to recap what these fields are. Pay attention to $\Sigma$
  for now.

\noindent  The language of a DFA $D$ is
 \[ {\cal L}(D) = \{ w \;\mid\; \hat{\delta}(q_0,w) \in F  \}. \]
Here, $\hat{\delta}$ is the \verb|run_dfa| function.

\item Of course, the language of a DFA is defined by
  all the strings \verb|s| for which the function \verb|accepts|
  below returns true:

\begin{verbatim}
def accepts(D, q, s):
    """ Checks for DFA acceptance.
    """
    return run_dfa(D, q, s) in D["F"]
\end{verbatim}

\item A string is not accepted (or rejected) if the function \verb|accepts|
  would return {\tt False}.

\item An NFA
  $N$ is a structure $(Q,\Sigma,\delta,q_0,F)$, and we explain these
  components below. Again, pay attention to $\Sigma$
  for now.

\item Given such a DFA $D$, only consider strings over $\Sigma^*$ with respect
  to this DFA. Thus, if $\Sigma=\{a,b,c\}$, consider all possible strings
  over $\Sigma=\{a,b,c\}$ whenever doing any analysis with respect to this DFA.
  Never consider any other symbol such as $0$ or $1$ or $-$ or $.$ or < or >
  or $<$ or $>$ 
  or  $!$ or whatever other symbol you might be tempted to consider 
  with respect to this DFA.
  %
  You can't even consider puffs of smoke signals as your ``dot'' and ``dash''
  to communicate with this DFA.

\item Likewise, if $\{?,0,a,\rightarrow\}$ is your $\Sigma$, then
  simply consider strings over these symbols 

  Example:
  $?a0?\rightarrow?a$ may be a string considered.

  However, never ever consider a string of the form
  $1?a0?b\rightarrow?a$ with respect to this DFA because such a string
  is not in the $\Sigma^*$ for this $\Sigma$.

\item For our ``Python-built DFA'' we don't allow $\rightarrow$ or < or > or other
  funny characters in the alphabet. We are concerned with only single character symbols
  for DFA experimented with in Python.
  
\item Now, given a $\Sigma$, you must check that it is a non-empty set. Never never
  have a DFA with an empty $\Sigma$.

\item Of course $\Sigma=\{0\}$ is allowed.
  Also $\Sigma=\{a\}$ is allowed.
  This is like a DFA connected to a keyboard  that has a single key.
  You will never make a typing mistake!

\item All of what we said about a DFA with respect to $\Sigma$
  are also true of an NFA. Later we will study regular expressions; even
  for them all this is true.

\item The ``job'' of a DFA or an NFA is to {\bf separate strings from
    $\Sigma^*$ into two bins}

  \begin{itemize}
  \item Those that are accepted

  \item Those that are not accepted (or ``rejected'')

  \end{itemize}

  Note that it is only strings over $\Sigma^*$ that are so classified.
  The universe of strings is $\Sigma^*$.
  
\item $L$ is a regular language  if $L$ is the language of a DFA (or
  equivalently, that of an NFA).

\item  A string that is accepted  takes the DFA (or NFA as the case may be) 
  from its initial state to the final state (or one of its final states)

\item A DFA/NFA rejecting a string is really accepting a string
  from the complement of $L$. Complementation is done with
  respect to $\Sigma^*$---see the complement function in {\tt lang.py}

\begin{verbatim}
def lcomplem(L,alph,m):
    """Complement L relative to alphabset alph. alph is also given as a set of strings.
       We subtract from the "star up to m" of the alphabet alph, the language L.
    """
    return star(alph,m) - L
\end{verbatim}

\end{itemize}





\section{On NFA}

\noindent In the last set of notes, we presented a few facts:
%
\begin{itemize}
\item Regular languages are those languages specified
  by a DFA. Each regular language is a language (a set of strings) $L$ such that
  there exists a DFA who language $L$ is.
\item Some regular languages are easy to specify using a DFA whereas for
  some regular languages, their DFA become too hard to draw out.
  In many of those cases, NFA prove to far easier. 
  We give two examples below.
\end{itemize}


\noindent We now  formally introduce nondeterministic finite automata.
Let $\Sigma_{\varepsilon}$ stand for $(\Sigma\cup\{\varepsilon\})$.
	\index{$\Sigma_{\varepsilon} = \Sigma\cup\{\varepsilon\}$}
A {\em nondeterministic finite-state automaton}
$N$ is a structure $(Q,\Sigma,\delta,q_0,F)$, where:
\begin{itemize}
\item $Q$, a {\em finite non-empty} set of states;
\item $\Sigma$, a {\em finite non-empty} alphabet;
\item $\delta: Q\times \Sigma_{\varepsilon} \rightarrow 2^Q$,
	a {\em total}
	transition function.
\item $q_0\in Q$, an initial state; and	
\item $F\subseteq Q$, a {\em finite, possibly empty} set of
	final states.
\end{itemize}
%
Note,
	that the range of $\delta$ includes $\emptyset$; therefore, if
	for some state $q$, $\delta(q,x)=\emptyset$,
	the move from state $q$ on input $x$ is essentially to a ``black hole''.
%
That is, the NFA is {\bf no state at all}. 
%
Since the moves of any machine are always {\em from} a state {\em to} a state, and if the
{\em from} states come from an empty set, naturally the {\em to} states also end up being
the empty set.
%
We will formally define the language of an NFA in 
\S~\ref{sec:lang-nfa}.


\noindent{\bf NFA acceptance:\/} An
 NFA $N$ accepts a string $s$ if {\em any one} of its runs on a string $s$ leaves the
NFA in one of its final states $F$.

\noindent{\bf The language of an NFA:\/} The language of an NFA $N$ is all the strings
it accepts.

\noindent{\bf Note on $\varepsilon$:\/} Note that an NFA can take
$\varepsilon$ moves ``whenever it likes'' ({\bf provided
such a move is available for the NFA}).

 These $\varepsilon$ are 
incorporated into the run in an obvious way. For example, a run
of the NFA of the form

\[ \varepsilon 0101 \varepsilon 1 \varepsilon 0 \varepsilon \]

is the same as the run of the same NFA on 

\[  010110 \]


That is, inserting $\varepsilon$ in a string does not alter the string.

\noindent A {\em non-deterministic finite-state automaton} (NFA) 
is a preferred alternative to DFA for specifying regular languages.
%
Using an NFA, we can be sure of two things:

\begin{itemize}
\item For many languages, the use of an NFA can make the automaton
  specification exponentially more succinct. That is,
  the number of states you draw and the number of transitions you draw
  will be vastly smaller in number than for a DFA.

\item For no language is an NFA worse than (larger than) a DFA.
  In fact, a DFA is a special case of an NFA (one where there is no non-determinism).
  Thus no NFA needs to be bigger than an equivalent DFA.
\end{itemize}

\noindent Here are three languages whose definitions are made much easier by following the NFA route:

\begin{itemize}

\item An NFA for $L_{a}$ where
  \[ L_{a} = \{ x \;\mid\; the\; third\; last\; symbol\; of\; x\; is\; a\; 1 \} \]

  \includegraphics[width=0.5\textwidth]{L1xxxAccSS.pdf}

\item  An NFA for $L_{b}  = L_1 L_2$ ($L_1$ concatenated with $L_2$)
 where

  \( L_1 = \) strings ending in a $1$.

  \( L_2 = \) the language of strings with equal numbers of 01 and 10 changes.

\item   An NFA for $L_{c} = L_1\cup L_2$, the union of $L_1$ and $L_2$ which are defined above.
  
\end{itemize}

\section{NFA simplifies construction of ``Union and Concatenation languages''}

More specifically, for languages defined through the Union or Concatenation operation, 
one can define NFAs for the individual languages and then compose them in the obvious way
to obtain the desired language. 
%
\begin{itemize}
\item For a ``concatenation language'', 
  \begin{itemize}
  \item Connect every final state of the first NFA using a $\varepsilon$ labeled edge
    to the initial state of the second NFA.
  \item Make every final state of the first NFA non-final.
  \end{itemize}
  In effect, the final states of the resulting NFA are just the final states of the second NFA.

\item For a ``union language,'' introduce a new state from which lead to the NFA of the individual
  languages using $\varepsilon$ moves.
  %
  In effect, the final states of the resulting NFA are the union of the final states of the individual NFA.
\end{itemize}

Sometimes it is dead-easy to see that some languages can be dramatically simplified.
For instance, you can rewrite the above languages as follows:

\begin{itemize}
\item $L_b$  as (do it first!)\footnote{\tiny all strings over $\{0,1\}^*$ (all strings drawn from<
 $\{0,1\}^*$) that contain a $1$.}
Figure~\ref{fig:lb} shows this NFA.

\item $L_c$  as (do it first!)\footnote{\tiny 
The complement of the language of strings that begin
with a 1 and end with a 0. In other words, the complement
of 1(0+1)$^*$0---as we would expression in a regular-expression
notation later.}
Figure~\ref{fig:lc} shows this NFA.
\end{itemize}

\noindent{\bf Note that in the JFLAP-drawn NFA, the Greek
symbol $\lambda$ is used in lieu of $\varepsilon$. They mean
the same thing. } Read the JFLAP documentation for more information.

JFLAP is available
on our CADE machines as \verb|/home/cs3100/jflap/bin/jflap|. 
%
JFLAP is extremely easy to install on your own own machines,
 from \verb|http://www.cs.duke.edu/csed/jflap/|


\begin{figure}
  \centering
  \includegraphics[width=0.5\textwidth]{ConcatLbLang.pdf}
\caption{Language $L_{b}$ has this NFA}
\label{fig:lb}
\end{figure}

\begin{figure}
  \centering
  \includegraphics[width=0.5\textwidth]{UnionEnds1EqChanges.pdf}  
\caption{Language $L_{c}$ has this NFA}
\label{fig:lc}
\end{figure}

But often we cannot so simplify languages. 

\begin{itemize}
\item Let $L_{div3} = $ the set of all strings over $\{0,1\}^*$ that are evenly divisible by 3 (when fed MSB-first).
\item Let $L_{ends1011} = $ the set of all strings over $\{0,1\}^*$ ending in $1011$. 
\end{itemize}


Now try to simplify this language in your mind:

\begin{itemize}
\item $L_{cat1} = L_{div3} \; L_{ends1011}$
\item $L_{union1} = L_{div3} \; \cup\; L_{ends1011}$
\end{itemize}


\section{NFA does not help simplify Intersection Languages}

However, NFAs are not helpful in rendering languages formed through intersection.
For example for $L_e$, there is no direct way to draw an NFA. 

$L_e = $ the set of all strings containing a $1011$ {\bf and} are evenly divisible by 3 (when fed MSB-first).

One has to painstakingly understand the language in question and draw an NFA directly.
%
Another algorithm that we shall study later is to build DFA for the individual
languages and perform a language intersection operation on the DFA. 
More on that later.


\section{Illustration of NFA and DFA using JFLAP}

I will demonstrate the use of JFLAP to study NFA and DFA.
%
Assignment \#4 is on the use of JFLAP.


\section{The language of an NFA}
\label{sec:lang-nfa}

We now present the concept of the language of an NFA through
Python. 
%
You may not follow it rightaway, but give it a skim.
%
Then come to the next section where the ideas are presented
through math.
%
Then when you understand something better, read the Python
code, play again with JFLAP, etc.

\subsection{Through Python}

\begin{footnotesize}
\begin{verbatim}
# First, import a few modules defined earlier.

from lang import *
from dfa import *
from praut import *

# Many higher-order functions are kept in functools. Import that now.

from functools import reduce

def fst(p):
    """ First of a pair."""
    return p[0]

def snd(p):
    """ Second of a pair."""
    return p[1]

def fn_dom(F):
    """ For functions represented as hash-maps (dicts), return their domain as a set.
    """
    return {k for k in F.keys()}

def fn_range(F):
    """ For functions represented as hash-maps (dicts), return their range as a set.
    """    
    return {v for v in F.values()}

def mk_nfa(Q, Sigma, Delta, q0, F):
    """Make an NFA, doing the consistency checks needed.
    """
    assert(Sigma != {})
    assert("" not in Sigma)  # We don't allow epsilon in any alphabet (except for a GNFA)
    assert(q0 in Q)
    assert(set(F) <= Q)
    assert(fn_dom(Delta) <= product(Q, Sigma | {""}))
    # Delta maps state x input to sets of states...
    # One way to say this
    # assert(fn_range(Delta) <= pow(Q))
    # But this causes trouble as sets can't be members of sets..
    for x in list(Delta.values()):
        assert(set(x) <= Q)
    #
    return({"Q":Q, "Sigma":Sigma, "Delta":Delta, "q0":q0, "F":F})

# Here is how an NFA is made.

Q1 = {'S0','S1'}

Sigma1 = {'a','b'}

# Notice how smoothly we can define Delta1 - exactly as in math!

Delta1 = { ('S0', 'a'): {'S0', 'S1'}, 
           ('S1', 'a'): {'S0'}, 
           ('S1', 'b'): {}, 
           ('S0', '') : {'S1'} }

q01 = 'S0'

F1 = {'S1'}

NFA1 = mk_nfa(Q1, Sigma1, Delta1, q01, F1)

def mktot_nfa(N):
    """ Given a partially specified NFA, 
    make it total by transitioning to the set of states {} wherever
    a move is undefined.
    """
    add_delta = { (q,c) : set({}) for q in N["Q"] for c in (N["Sigma"] | {""}) if (q,c) not in N["Delta"] }
    #
    add_delta.update(N["Delta"])
    #
    return {"Q": N["Q"], "Sigma": N["Sigma"], "q0": N["q0"], "F": N["F"], "Delta": add_delta}

def prnfa(N):
        """Prints the NFA neatly.
        """
        # Make the NFA total before printing
        Nt = mktot_nfa(N)
        print("")
        print("Q:", Nt["Q"])
        print("Sigma:", Nt["Sigma"])
        print("q0:", Nt["q0"])
        print("F:", Nt["F"])
        print("Delta:")
        print("\t".join(map(str, Nt["Q"])))
        print("----------------------------------------------------------------------------------------")
        for c in (Nt["Sigma"] | {""}):
            nxt_qs = [Nt["Delta"][(q, c)] for q in Nt["Q"]]
            print("\t".join(map(str, nxt_qs)) + "\t\t" + c)
            print("")

def step_nfa(N, q, c):
    """Run NFA N from state q on character c or "". Return the next state.
    Step is without E-closure.
    """
    assert(c in (N["Sigma"] | {""}))
    assert(q in N["Q"])
    # We have to run it wrt the total version of the NFA. Expensive, so special case this check.
    if (q,c) in N["Delta"].keys():
        return N["Delta"][(q,c)]
    else:
        return set({})

#-- Eclosure version 1 ---------------------------------------------------
# I leave it here for you to look at...
#
# def eps_fix(set_states, chain_len, N):
#     if (chain_len == 0):
#         return set_states
#     else:
#         all_state_sets_one_eps_away = list(map(lambda s: step_nfa(N, s, ""), set_states))
#         # basis case added to make reduction succeed        
#         all_states_one_eps_away = reduce(lambda x, y: set(x) | set(y), all_state_sets_one_eps_away + [ set() ] )
#         return eps_fix( set(all_states_one_eps_away) | set(set_states), chain_len - 1, N)
# 
# def eclosure(Q, N):
#     """Given nfa N and a SET OF states Q, close Q, return eclosure as a set of states.
#     """
#     return set() if Q == {} else eps_fix(Q, len(N["Delta"].items()), N)
#------------------------------------------------------------------------

def ech(Allsofar, Previous, N):
    """Extend Allsofar until nothing new (that's not in Previous).
    """
    if (Allsofar == Previous):
        return Allsofar
    else:
        all_state_sets_one_eps_away = list(map(lambda q: step_nfa(N, q, ""), Allsofar))
         # basis case added to make reduction succeed        
        all_states_one_eps_away = reduce(lambda x, y: set(x) | set(y), all_state_sets_one_eps_away + [ set({}) ] )
        return ech(set(all_states_one_eps_away) | set(Allsofar), Allsofar, N)
    
def eclosure(Q, N):
    """Given nfa N and a SET OF states Q, close Q, return eclosure as a set of states.
    """
    return ech(Q, set({}), N)

def ec_step_nfa(Q, c, N):
    """Return all states one "c" step away from Q (E-close before/after). i.e.,
    E-close Q, run N from all those states, Eclose those.
    If c is "", then se simply E-close q.
    len(NFA1["Delta"].items()) is the longest chain in the NFA.. we can iterate that much.
    """
    Eclosure = eclosure(Q, N)
    #
    all_state_sets_one_c_away = list(map(lambda s: step_nfa(N, s, c), Eclosure))
    #
    # basis case added to make reduction succeed
    all_states_one_c_away = reduce(lambda x, y: set(x) | set(y), all_state_sets_one_c_away + [ set({}) ] )
    #
    Eclosure_again = eclosure(all_states_one_c_away, N)
    #
    return Eclosure_again


def run_nfa(N, Q, s):
    """Run NFA from a set of states Q.
    """
    return eclosure(Q, N) if s=="" else run_nfa(N, ec_step_nfa(Q, s[0], N), s[1:])

def accepts_nfa(N, q, s):
    """NFA acceptance.
    """
    return (run_nfa(N, {q}, s) & N["F"]) != set({})

def accepts_nfav(N, q, s):
    """NFA acceptance - verbose version.
    """
    if (run_nfa(N, {q}, s) & N["F"]) != set({}):
        print("NFA accepts '" + s + "' by reaching " + str(run_nfa(N, {q}, s)))
        return True
    else:
        print("NFA rejects '" + s + "'")
        return False


ecs_S0_a = ec_step_nfa({'S0'}, 'a', NFA1)
ecs_S0_b = ec_step_nfa({'S0'}, 'b', NFA1)
#
ecs_S1_a = ec_step_nfa({'S1'}, 'a', NFA1)
ecs_S1_b = ec_step_nfa({'S1'}, 'b', NFA1) 
#
ec_S0 = eclosure({'S0'}, NFA1)
ec_S1 = eclosure({'S1'}, NFA1)


Q2 = {'S0', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6'}

Sigma2 = {'0','1'}

Delta2 = { ('S0', '')  : { 'S1' },
           ('S0', '0') : { 'S0' },
           ('S0', '1') : { 'S0', 'S5' },
           ('S1', '0') : { 'S2' },
           ('S2', '0') : { 'S3' },
           ('S2', '1') : { 'S3' },
           ('S3', '0') : { 'S4' },
           ('S3', '1') : { 'S4' },
           ('S5', '0') : { 'S6' },
           ('S5', '1') : { 'S6' } }

q02 = 'S0'

F2 = {'S4', 'S6'}

NFA2 = mk_nfa(Q2, Sigma2, Delta2, q02, F2)


ec_S0 = eclosure({'S0'}, NFA2)
ec_S1 = eclosure({'S1'}, NFA2)
ec_S4 = eclosure({'S4'}, NFA2)

ecs_S0_0 = ec_step_nfa({'S0'}, '0', NFA2)
ecs_S0_1 = ec_step_nfa({'S0'}, '1', NFA2)

ecs_S1_0 = ec_step_nfa({'S1'}, '0', NFA2)
ecs_S1_1 = ec_step_nfa({'S1'}, '1', NFA2)

ecs_S2_0 = ec_step_nfa({'S2'}, '0', NFA2)
ecs_S2_1 = ec_step_nfa({'S2'}, '1', NFA2)



assert(run_nfa(NFA2, {'S0'}, '000') == {'S3', 'S2', 'S1', 'S0', 'S4'})

assert(run_nfa(NFA2, {'S0'}, '00') == {'S3', 'S2', 'S1', 'S0'})

assert(run_nfa(NFA2, {'S0'}, '0') == {'S2', 'S1', 'S0'})

assert(run_nfa(NFA2, {'S0'}, '1') == {'S1', 'S0', 'S5'})

assert(run_nfa(NFA2, {'S0'}, '11') == {'S1', 'S0', 'S6', 'S5'})

assert(run_nfa(NFA2, {'S0'}, '101') == {'S3', 'S1', 'S0', 'S5'})


assert(accepts_nfa(NFA2, 'S0', '101') == False)

assert(accepts_nfa(NFA2, 'S0', '111') == True)

assert(accepts_nfa(NFA2, 'S0', '') == False)

assert(accepts_nfa(NFA2, 'S0', '0') == False)

assert(accepts_nfa(NFA2, 'S0', '00') == False)

assert(accepts_nfa(NFA2, 'S0', '000') == True)

assert(accepts_nfa(NFA2, 'S0', '001') == True)

assert(accepts_nfa(NFA2, 'S0', '011') == True)

assert(accepts_nfa(NFA2, 'S0', '100') == False)

assert(accepts_nfa(NFA2, 'S0', '10') == True)

assert(accepts_nfav(NFA2, 'S0', '10') == True) # prints also


assert(accepts_nfav(NFA2, 'S0', '010') == True) # prints also

assert(accepts_nfav(NFA2, 'S0', '010') == True) # prints also

assert(accepts_nfav(NFA2, 'S0', '0100') == False) # prints also

assert(accepts_nfav(NFA2, 'S0', '01000') == True) # prints NFA accepts '01000' by reaching {'S3', 'S2', 'S1', 'S0', 'S4'}


\end{verbatim}
\end{footnotesize}

\subsection{Through Math}

We can define the language of an NFA using the idea of
$Eclosure$.
%
This notion, in effect, considers all
$\varepsilon$-laden interpretations of strings
in ``one fell swoop.'' 
%
That is, when asked ``does this NFA accept \(  010110 \)'',
we must consider  strings of the form
\( \varepsilon 0101 \varepsilon 1 \varepsilon 0 \varepsilon \)
where a $\varepsilon$ is snuck in wherever possible.
%
This is precisely the idea of an NFA being allowed to
take a $\varepsilon$ wherever possible.


\subsection{The language of an NFA}

The language of an NFA consists of all those sequences
of symbols that can be encountered while
tracing a path from the start state to some final state.
%--
We eliminate all occurrences of
$\varepsilon$ from such sequences {\bf unless the
entire sequence consists of
$\varepsilon$s, in which case, we
turn the sequence into a single\footnote{Please note that $\varepsilon$ is {\em not} part of
	the alphabet of the NFA. Never, never, never! Same deal as with
      a DFA!}
$\varepsilon$.}
%--

\subsection{$Eclosure$ (also known as $\varepsilon$-closure)}
\label{sec:eclosure}
%--
%--
\begin{figure}[tbh]
%  {\hfill\psfig{file=CH9/complex-nfa.ps,width=4cm,height=12cm,angle=-90,clip=} \hfill}
\centering
\includegraphics[scale=.7]{complex-nfa.pdf}
\caption{An example NFA}
\label{fig:complex-nfa}
\end{figure}

	\index{$\varepsilon$-closure}

$Eclosure(q)$ obtains, starting from a state $q$, 
the set of states reached by an NFA 
traversing zero or more $\varepsilon$ labeled transitions.
%--
The best way to see which states are included in $Eclosure(q)$, for
any $q$, is to imagine the following:
\begin{quote}
Apply a {\em high-voltage} to state $q$;
imagine that every $\varepsilon$ edge is a diode that
conducts in the direction of the arrow; now see which are the
states that would be fatal to touch due to the high-voltage;\footnote{An {\em ideal} non-leaky diode that
does not break down.}
all those are in $Eclosure(q)$. 
\end{quote}
% \footnotetext{An {\em ideal} non-leaky diode that does not break down.}

\noindent{\bf Example:} 
Let us obtain the $Eclosure$ of various states
in Figure~\ref{fig:complex-nfa}.
\begin{list}{$\bullet$}{\addtolength{\itemsep}{-1.6\itemsep}}
\item The Eclosure of state $IF$ is $IF$ itself.
	The high-voltage spreads from $IF$  to itself.
\item What is $Eclosure$ of $FA$?
	Applying high-voltage to this
	state, it spreads to state
	$FA$, to $B0$, to $A1$, and
	finally, to $FB$. For example,
$Eclosure(FA)=\{FA,B0,A1,FB\}$.
\end{list}
%--
Unfortunately, an intuitive definition
in terms of voltages isn't rigorous
enough!
%--
Hence, we set up an
alternate
definition of $Eclosure(q)$
as follows:
\begin{list}{$\bullet$}{\addtolength{\itemsep}{-1.6\itemsep}}
\item First, we need a way to compute all states
which can be reached from a given state
by traversing $\varepsilon$ edges.

Let $\rightarrow \subseteq Q\times Q$ be an arbitrary relation over $Q$.
%--
	\index{NFA!$\rightarrow$}
	\index{$\rightarrow$}
%--
Then, given $\rightarrow \;\subseteq Q\times Q$,
we will define a postfix usage of this operator, namely  $q\rightarrow$
%--
	\index{$q\rightarrow$},
%--
to be the {\em image}
of $q$ under $\rightarrow$:
\[ q \rightarrow\; = \;\{ x \;\mid\; \langle q,x\rangle \; \in\; \rightarrow \}. \]
%--

\item Now define the relation \shortstack{$\varepsilon$\\$\rightarrow$}
which is a subset of $Q\times Q$:
%--
\index{\shortstack{$\varepsilon$\\$\rightarrow$}}
%--
\begin{itemize}
\item[] \shortstack{$\varepsilon$\\$\rightarrow$}
$= \{\langle q_1,q_2\rangle \;\mid\; q_1,q_2\in Q \wedge
         q_2 \in \delta(q_1,\varepsilon) \}.$
\end{itemize}
%--
\item Next, define the reflexive and transitive closure
of \shortstack{$\varepsilon$\\$\rightarrow$}
in the usual way.
Call it \shortstack{$\varepsilon$\\$\rightarrow^{*}$}.
\end{list}
%--
\index{\shortstack{$\varepsilon$\\$\rightarrow^{*}$}}
%--
Given all this, 
we define
 \[ Eclosure(q) = q\shortstack{$\varepsilon$\\$\rightarrow^{*}$}.\]
%
Here, we use the \shortstack{$\varepsilon$\\$\rightarrow^{*}$} as a postfix
operator.
%
The reflexive part above is very important to
ensure that $q$ gets included within $Eclosure(q)$.
%--
The overall effect of employing
\shortstack{$\varepsilon$\\$\rightarrow^{*}$} as a postfix operator
is to force all $\varepsilon$-only paths to be considered.

\noindent{\bf Example:} Redoing our
example with respect to
Figure~\ref{fig:complex-nfa},
\[ Eclosure(FA) = FA
\shortstack{$\varepsilon$\\$\rightarrow^{*}$}.\]
\begin{list}{$\bullet$}{\addtolength{\itemsep}{-1.6\itemsep}}
\item This ``grabs'' state $FA$ itself
(the reflexive part of
\shortstack{$\varepsilon$\\$\rightarrow^{*}$} does this).
\item Next, $B0$ enters this set, as
	it is one step away.
	In fact, $B0$ is in
$FA \shortstack{$\varepsilon$\\$\rightarrow^{1}$}$.
	
\item Next, $A1$ enters this set, as
	it is two steps away.
	In fact, $A1$ is in
$FA \shortstack{$\varepsilon$\\$\rightarrow^{2}$}$.

\item Finally, $FB$
	enters this set, as
	it is three steps away.
	In fact, $FB$ is in
$FA \shortstack{$\varepsilon$\\$\rightarrow^{3}$}$.
\item No more states enter
	$Eclosure(FA)$.
\end{list}

As said earlier, $Eclosure$ helps define the behavior of
an NFA, as well as its language more directly.
%--
It also helps us define the NFA to DFA conversion algorithm,
as we shall see very soon.


\subsection{Language of an NFA}
%--
	\index{NFA!language}
%--
Having defined
$Eclosure$, we can now
formally define the language of
an NFA.
For a string $x\in \Sigma^*$, define $\hat{\delta}(q_0,x)$
%--
  \index{$\hat{\delta}$}
%--  
	of an NFA to be the set of states reached
	starting from $Eclosure(q_0)$ and traversing
	all the symbols in $x$, taking an $Eclosure$ after
	every step.
We are, in effect, taking the image of $q_0$ under string $x$,
except that we are allowing an arbitrary number of $\varepsilon$s to
be arbitrarily inserted into $x$.
Also, we will
overload $Eclosure$ to work over sets of states in the obvious manner, as
follows:
\[ Eclosure(S) = \{ x \;\mid\; \exists s\in S\; : \; x\in Eclosure(s) \}. \]
This definition can also be written as
\[ Eclosure(S) = \cup_{s\in S}\; Eclosure(s). \]
Likewise, we overload $\delta$ to work over sets of states:
\[ \delta(S,a) = \{ x \;\mid\; \exists s\in S\; : \; x\in \delta(s,a) \}. \]

\noindent Now we define $\hat{\delta}(q,x)$, the `string transfer function,'
for state $q$ and string $x$ inductively as follows:
%--
\begin{itemize}
\item[] $\hat{\delta}(q,\varepsilon) = Eclosure(q)$.
\item[] For $a\in\Sigma$ and $x\in \Sigma^*$,
\item[] $\;\; \hat{\delta}(q,a x)
   = \{ y \;\mid\; \exists s\in Eclosure(\delta(Eclosure(q),a)) \;:\;  y\in \hat{\delta}(s,x) \}. $
\end{itemize}
%--
In other words, for every symbol $a$ in $ax$, we $Eclose$ state $q$, ``run''
$a$ from each one of these states, and $Eclose$ the resulting states. From
each state $s$ that results, we recursively run $x$.
%
Notice that we apply $Eclosure$ before as well as afterward.
%
While this is strictly redundant, it leads to definitions that are simpler and
more general, and hence easier to reason about.\footnote{Another way to set up the
definitions would have been to start the NFA
 in the $Eclosure$ of its start state, and at each stage
 perform a $\delta$ step followed by one $Eclosure$ step.}
%
Specifically, in the definition of $\hat{\delta}(q,a x)$, we need not assume that
$q$ is an already $Eclosed$ state, even though in the current context of its usage,
we will be inductively guaranteeing that to be the case because: (i)~we begin with
$\hat{\delta}(q,\varepsilon) = Eclosure(q)$, and (ii)~in $\hat{\delta}(q,a x)$, we
restore ``Eclosedness.''
%
\label{page:double-eclosure}
%
% \footnotetext{Another way to set up the definitions would have been to start the NFA
% in the $Eclosure$ of its start state, and at each stage
% perform a $\delta$ step followed by one $Eclosure$ step.}

\noindent Finally, the language of an NFA $N$ is
 \[ {\cal L}(N) = \{ w \;\mid\; \hat{\delta}(q_0,w) \cap F \neq \emptyset \}. \]
In other words, after running the NFA from state $q_0$ with input $w$,
we see whether any `token' has reached a final state.

\noindent
%--
	\index{NFA!token game}
%--
A good way to intuitively understand the above definitions pertaining
to NFAs is through the following `token game:'
%--
\begin{list}{$\bullet$}{\addtolength{\itemsep}{-1.6\itemsep}}
\item Place a token in state $q_0$. Spread one copy
	of the token to each state in $Eclosure(q_0)$.
\item For each symbol $a$ from $\Sigma$ that is entered,
	advance each token to its set of $a$ successors.
\item $Eclose$ the tokens and continue.
\item If and when one of the tokens reaches some final state,
	the string seen so far is accepted.
\end{list}
%--
\begin{illustration}\rm
The NFA in 
Figure~\ref{fig:complex-nfa}
has string 0001 in its language.
First, $Eclosure(IF)=\{IF\}$.
After the first $0$,
the NFA is in
state $FA$, whose
$Eclosure$ is $\{FA,B0,A1,FB\}$.
%--
After the second $0$, $A1$ goes
to $FA$, and the
$Eclosure$ results in $\{FA,B0,A1,FB\}$.
%--
The same happens after the
third $0$.
%--
After the $1$, $FA$ and $A1$ go
to $A1$, $B0$ goes to $FB$, and
the token in $FB$ goes to $\emptyset$,
resulting in $\{A1,FB\}$, which is
also its own $Eclosure$.
%--
This matches the 
definition
\[\hat{\delta}(q,a x)
   = \{ y \;\mid\; \exists s\in Eclosure(\delta(Eclosure(q),a)) \;:\;  y\in \hat{\delta}(s,x) \} \]
as argued below:
%--
\begin{list}{$\bullet$}{\addtolength{\itemsep}{-1.6\itemsep}}
\item $Eclosure(\delta(Eclosure(IF),0))$ is $\{FA,B0,A1,FB\}$.
\item We recursively process the remaining input $001$ from these states
to reach $\{A1,FB\}$.
\end{list}

\end{illustration}

\end{document}
