diff --git a/docs/notebooks/DFA.ipynb b/docs/notebooks/DFA.ipynb
new file mode 100644
index 00000000..deff1a69
--- /dev/null
+++ b/docs/notebooks/DFA.ipynb
@@ -0,0 +1,1051 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "dd1b7bfa",
+ "metadata": {},
+ "source": [
+ "## Finite State Machine (FSM)\n",
+ "\n",
+ "(Text and examples modified and borrowed from the one and only [Prof. Jeff Erickson](http://algorithms.wtf/#models))\n",
+ "\n",
+ "A finite-state machine is a formal model of any system/machine/algorithm that can exist in a finite number of states. Transitions among these states is based on a sequence of input symbols. For example, the following algorithm `MultipleOf5` determines whether a binary string `w[0..n-1]` of bits represents a multiple of 5:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "b502be80",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def mutlipleOf5(w):\n",
+ " rem, n = 0, len(w)\n",
+ " for i in range(n):\n",
+ " rem = (2 * rem + ord(w[i])) % 5\n",
+ " if rem == 0:\n",
+ " return True\n",
+ " else:\n",
+ " return False"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e9963cf8",
+ "metadata": {},
+ "source": [
+ "(We can test this function out. **12** is **1100** in binary while **15** is **1111** in binary. So, `multipleOf5(\"1100\")` should return `False` while `multipleOf5(\"1111\")` should return `True`. Try it out yourself!)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "046153fa",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "False\n",
+ "True\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(mutlipleOf5(\"1100\"))\n",
+ "print(mutlipleOf5(\"1111\"))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2467cf8f",
+ "metadata": {},
+ "source": [
+ "We can envision the variable `rem` having 5 distinct values: 0, 1, 2, 3, 4, and can consequently represent it using a FSM with each state s1, s2, s3, s4 representing a possible value of `rem`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "60c3b6c4",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ "DFA(states={'s3', 's4', 's1', 's0', 's2'}, input_symbols={'0', '1'}, transitions={'s0': {'0': 's0', '1': 's1'}, 's1': {'0': 's2', '1': 's3'}, 's2': {'0': 's4', '1': 's0'}, 's3': {'0': 's1', '1': 's2'}, 's4': {'0': 's3', '1': 's4'}}, initial_state='s0', final_states={'s0'}, allow_partial=False)"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from automata.fa.dfa import DFA\n",
+ "\n",
+ "mutlipleOf5_fsm = DFA(\n",
+ " states={'s0', 's1', 's2', 's3', 's4'},\n",
+ " input_symbols={'0', '1'},\n",
+ " transitions={\n",
+ " 's0': {'0': 's0', '1': 's1'},\n",
+ " 's1': {'0': 's2', '1': 's3'},\n",
+ " 's2': {'0': 's4', '1': 's0'},\n",
+ " 's3': {'0': 's1', '1': 's2'},\n",
+ " 's4': {'0': 's3', '1': 's4'}\n",
+ " },\n",
+ " initial_state='s0',\n",
+ " final_states={'s0'}\n",
+ ")\n",
+ "\n",
+ "mutlipleOf5_fsm"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "66dfd52f",
+ "metadata": {},
+ "source": [
+ "Run the code below a few times to verify its correctness for youself:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "9825667c",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Please enter your input: 1111\n",
+ "Accepted\n"
+ ]
+ }
+ ],
+ "source": [
+ "if mutlipleOf5_fsm.accepts_input(input('Please enter your input: ')):\n",
+ " print('Accepted')\n",
+ "else:\n",
+ " print('Rejected')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5e48007f",
+ "metadata": {},
+ "source": [
+ "## DFAs\n",
+ "\n",
+ "Finite-State Machines are also known as deterministic finite-state automata. It's \"deterministic\" because the behavior of the machine is completely determined by the input strings. (We will cover NFAs –– non-deterministic finite-state automata –– in a later section.)\n",
+ "\n",
+ "Formally, every finite-state machine consists of five components:\n",
+ "1. An arbitrary finite set $\\Sigma$, called the **input alphabet**.\n",
+ "2. Another arbitrary finite set **Q**, whose elements are called **states**.\n",
+ "3. An arbitrary **transition** function $\\delta: Q \\times \\Sigma \\rightarrow Q$\n",
+ "4. A **state state** $\\textbf{s} \\in Q$\n",
+ "5. A subset $A \\subseteq Q$ of **accepting states**.\n",
+ "\n",
+ "Scroll above and note how `mutlipleOf5_fsm` is precisely defined by these five components as inputs."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "acbea5da",
+ "metadata": {},
+ "source": [
+ "### How does it work?\n",
+ "The behavior of a finite-state machine is governed by an input string $\\omega$, which is a finite\n",
+ "sequence of symbols from the input alphabet $\\Sigma$. \n",
+ "\n",
+ "The machine reads the symbols in $\\omega$ one at a time in order (from left to right). At all times, the machine has a current state ***q***; initially ***q*** is the machine’s start state ***s***. Each time the machine reads a symbol ***a*** from the input string, its current state transitions from ***q*** to $\\delta(q, a)$. \n",
+ "\n",
+ "After all the characters have been read, the machine accepts $\\omega$ if the current state is in ***A*** and rejects $\\omega$ otherwise. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d8a94836",
+ "metadata": {},
+ "source": [
+ "#### Let's explore another example:\n",
+ "The following DFA accepts all binary strings ending in an odd number of '1's. Write out a few binary strings for yourself and trace through the graph to prove its correctness. **q1** is the only accepting state, denoted by the concentric circles."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "67d76f2b",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ "DFA(states={'q2', 'q0', 'q1'}, input_symbols={'0', '1'}, transitions={'q0': {'0': 'q0', '1': 'q1'}, 'q1': {'0': 'q0', '1': 'q2'}, 'q2': {'0': 'q2', '1': 'q1'}}, initial_state='q0', final_states={'q1'}, allow_partial=False)"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "odd_ones_ending_dfa = DFA(\n",
+ " states={'q0', 'q1', 'q2'},\n",
+ " input_symbols={'0', '1'},\n",
+ " transitions={\n",
+ " 'q0': {'0': 'q0', '1': 'q1'},\n",
+ " 'q1': {'0': 'q0', '1': 'q2'},\n",
+ " 'q2': {'0': 'q2', '1': 'q1'}\n",
+ " },\n",
+ " initial_state='q0',\n",
+ " final_states={'q1'}\n",
+ ")\n",
+ "\n",
+ "odd_ones_ending_dfa"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "743d59b2",
+ "metadata": {},
+ "source": [
+ "The start state, **q0** is denoted by the unattached arrow pointing at it. The string \"1\" would be accepted because you would navigate from **q0** to **q1**. \"11\" would create the chain **q0 -> q1 -> q2**, which would be rejected. \"111\" would return to **q1**, and be accepted.\n",
+ "\n",
+ "Careful analysis shows that that this DFA, with three states and six transitions can be written more with only two states and three transitions:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "d646b08e",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ "DFA(states={frozenset({'q2', 'q0'}), frozenset({'q1'})}, input_symbols={'0', '1'}, transitions={frozenset({'q2', 'q0'}): {'0': {'q2', 'q0'}, '1': {'q1'}}, frozenset({'q1'}): {'0': {'q2', 'q0'}, '1': {'q2', 'q0'}}}, initial_state={'q2', 'q0'}, final_states={frozenset({'q1'})}, allow_partial=False)"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "minimal_odd_ones_ending_dfa = odd_ones_ending_dfa.minify(retain_names=True)\n",
+ "minimal_odd_ones_ending_dfa"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "76dc2040",
+ "metadata": {},
+ "source": [
+ "Notice how this **minimal DFA** `minimal_odd_ones_ending_dfa` accepts and rejects all the strings that `odd_ones_ending_dfa` does. We say that both `minimal_odd_ones_ending_dfa` and `odd_ones_ending_dfa` have the same language. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4c519ec2",
+ "metadata": {},
+ "source": [
+ "## Finite State Automata and Formal Languages\n",
+ "\n",
+ "The language of a finite state machine *M*, denoted **L(*M*)**, is the set of all strings in $\\Sigma^{*}$[[1]](#cite_note-1) that *M* accepts. More formally, if $M = (\\Sigma, Q, \\delta, s, A)$, then *L(M) := $\\{w \\in \\Sigma^{*} \\mid \\delta^{*}(s, w) \\in A\\}$*[[2]](#cite_note-2). We call a language **automatic**, or **regular**, if it is the language of some finite state machine.\n",
+ "\n",
+ "Automatic languages have several closure properties –– Let *L* and *L'* be arbitrary automatic languages over an arbitrary alphabet $\\Sigma$. Then,\n",
+ "- $\\bar L = \\Sigma^{*}\\backslash L$ is automatic\n",
+ "- L $\\cup$ L' is automatic\n",
+ "- L $\\cap$ L' is automatic\n",
+ "- L $\\backslash$ L' is automatic\n",
+ "- L $\\oplus$ L' is automatic\n",
+ "\n",
+ "By Kleene's Theorem, for any regular expression R, there is a DFA *M* such that *L(R) = L(M)*. For any DFA *M*, there is a regular expression *R* such that *L(M) = L(R)*, which implies that the set of ***regular*** languages is also closed under the simple boolean operations defined above. We are going through this very quickly and very briefly –– please refer to [these lecture notes](https://jeffe.cs.illinois.edu/teaching/algorithms/models/03-automata.pdf) for more detail.\n",
+ "\n",
+ "### Closure Property Examples\n",
+ "We can easily create a DFA that accepts all binary strings except for those that end in an odd number of 1's:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "a789002f",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ "DFA(states={0, 1}, input_symbols={'0', '1'}, transitions={0: {'0': 1, '1': 1}, 1: {'0': 1, '1': 0}}, initial_state=1, final_states={1}, allow_partial=False)"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "odd_ones_ending_dfa.complement(retain_names=False, minify=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b144a520",
+ "metadata": {},
+ "source": [
+ "Trace it through with some examples to prove its correctness to yourself.\n",
+ "\n",
+ "Next, we can construct a DFA that accepts all binary strings that represent a multiple of 5 OR end in an odd number of 1's:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "fd2eb520",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Please enter your input: 111\n",
+ "Accepted\n"
+ ]
+ }
+ ],
+ "source": [
+ "union_dfa = mutlipleOf5_fsm.union(odd_ones_ending_dfa, retain_names=False, minify=True)\n",
+ "if union_dfa.accepts_input(input('Please enter your input: ')):\n",
+ " print('Accepted')\n",
+ "else:\n",
+ " print('Rejected')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0d85a7fd",
+ "metadata": {},
+ "source": [
+ "Notice how '111' (7 in decimal) and '1111' (15 in decimal) are both accepted?\n",
+ "\n",
+ "We can also use the intersection operation to contructor a DFA that accepts only binary strings that represent a multiple of 5 AND end in a odd number of 1's: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "92403fa9",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Please enter your input: 101\n",
+ "Accepted\n"
+ ]
+ }
+ ],
+ "source": [
+ "intersection_dfa = mutlipleOf5_fsm.intersection(odd_ones_ending_dfa, retain_names=False, minify=True)\n",
+ "if intersection_dfa.accepts_input(input('Please enter your input: ')):\n",
+ " print('Accepted')\n",
+ "else:\n",
+ " print('Rejected')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5f210909",
+ "metadata": {},
+ "source": [
+ "Now, both '111' (7 in decimal) and '1111' (15 in decimal) are rejected; however, '101' (5 in decimal) is accepted because it satisfies both conditions.\n",
+ "\n",
+ "## Creation Functions\n",
+ "\n",
+ "Now that we've covered the basics, we can move to the more advanced creation functions.\n",
+ "\n",
+ "`from_substring` \"directly computes the minimal DFA recognizing strings containing the given substring.\" For example, if I want a DFA that accepts binary strings that contain the substring \"101\", I can write the following:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "id": "b762dae6",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ "DFA(states={0, 1, 2, 3}, input_symbols={'0', '1'}, transitions={0: {'0': 0, '1': 1}, 1: {'0': 2, '1': 1}, 2: {'0': 0, '1': 3}, 3: {'0': 3, '1': 3}}, initial_state=0, final_states={3}, allow_partial=False)"
+ ]
+ },
+ "execution_count": 28,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "one_zero_one_substring_dfa = DFA.from_substring(\n",
+ " input_symbols={'0', '1'},\n",
+ " substring=\"101\",\n",
+ " contains=True,\n",
+ " must_be_suffix=False\n",
+ ")\n",
+ "\n",
+ "one_zero_one_dfa"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c016e474",
+ "metadata": {},
+ "source": [
+ "Notice how the DFA enters an accepting state immediately once a \"101\" is read?\n",
+ "\n",
+ "We can even generate a DFA that accepts binary strings containing the *subsequence* \"101\":"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "id": "1623dda1",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ "DFA(states={0, 1, 2, 3}, input_symbols={'0', '1'}, transitions={0: {'0': 0, '1': 1}, 1: {'0': 2, '1': 1}, 2: {'0': 2, '1': 3}, 3: {'0': 3, '1': 3}}, initial_state=0, final_states={3}, allow_partial=False)"
+ ]
+ },
+ "execution_count": 31,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "one_zero_one_dfa = DFA.from_subsequence(\n",
+ " input_symbols={'0', '1'},\n",
+ " subsequence=\"101\",\n",
+ " contains=True,\n",
+ ")\n",
+ "\n",
+ "one_zero_one_dfa"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "bfa19104",
+ "metadata": {},
+ "source": [
+ "Isn't that cool? Note the differences between the two DFA's and the strings that they accept. If you don't know the differene between a substring and subsequence, write out all the strings that are accepted by each DFA and look for patterns.\n",
+ "\n",
+ "There's plenty more DFA functions to explare on the [DFA class API](../../api/fa/class-dfa). Go crazy!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "aa29ff9d",
+ "metadata": {},
+ "source": [
+ "- The Kleene Closure $\\Sigma^{*}$ is the set of all strings obtained by concatenating a sequence of zero or more strings from $\\Sigma$\\.\n",
+ "\n",
+ "- $\\delta^{*}$ extends the definition of the transition function $\\delta: Q \\times \\Sigma \\rightarrow Q$ of any finite-state machine to $\\delta^{*}: Q \\times \\Sigma^{*} \\rightarrow Q$:\n",
+ "\\begin{equation}\n",
+ "\\delta^{*}(q,w) =\n",
+ " \\begin{cases}\n",
+ " q & \\text{if $w = \\epsilon$} \\\\\n",
+ " \\delta^{*}(\\delta(q, a), x) & \\text{if $w = ax$}\n",
+ " \\end{cases}\n",
+ "\\end{equation}"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.9.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/mkdocs.yml b/mkdocs.yml
index 06ca5cc9..f1849f8b 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -56,6 +56,7 @@ plugins:
# enabled_if_env: ENABLE_PDF_EXPORT
- macros:
enabled_if_env: ENABLE_PDF_EXPORT
+ - mkdocs-jupyter
- mkdocstrings:
enabled: !ENV [ENABLE_MKDOCSTRINGS, true]
custom_templates: templates
@@ -116,6 +117,8 @@ nav:
- index.md
- migration.md
- characteristics.md
+ - Theory:
+ - DFA Introduction: notebooks/DFA.ipynb
- Examples:
- examples/fa-examples.md
- examples/perf-examples.md
diff --git a/requirements.docs.txt b/requirements.docs.txt
index 043ca8ec..2117a03c 100644
--- a/requirements.docs.txt
+++ b/requirements.docs.txt
@@ -1,4 +1,5 @@
mkdocs==1.4.2
+mkdocs-jupyter==0.24.8
mkdocs-material==9.1.9
mkdocs-macros-plugin==0.7.0
mkdocstrings==0.23.0