├── .drone.yml ├── .gitignore ├── CITATION.cff ├── LICENSE ├── README.md ├── assets └── dfa1.svg ├── dfa ├── __init__.py ├── dfa.py ├── draw.py └── utils.py ├── poetry.lock ├── pyproject.toml └── tests ├── test_dfa.py ├── test_draw.py └── test_utils.py /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | name: default 3 | 4 | steps: 5 | - name: test 6 | image: python:3.10 7 | commands: 8 | - pip install poetry 9 | - poetry install 10 | - poetry run pytest 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use this software, please cite it as below." 3 | authors: 4 | - family-names: "Vazquez-Chanlatte" 5 | given-names: "Marcell" 6 | orcid: "https://orcid.org/0000-0002-1248-0000" 7 | - family-names: "Shah" 8 | given-names: "Ameesh" 9 | title: "dfa" 10 | version: 1 11 | date-released: 2022-05-16 12 | url: "https://github.com/mvcisback/dfa" 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Marcell Vazquez-Chanlatte 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DFA 2 | 3 | A simple python implementation of a DFA. 4 | 5 | [![Build Status](https://cloud.drone.io/api/badges/mvcisback/dfa/status.svg)](https://cloud.drone.io/mvcisback/dfa) 6 | [![PyPI version](https://badge.fury.io/py/dfa.svg)](https://badge.fury.io/py/dfa) 7 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 8 | 9 | 10 | **Table of Contents** 11 | 12 | - [Installation](#installation) 13 | - [Usage](#usage) 14 | - [Membership Queries](#membership-queries) 15 | - [Transitions and Traces](#transitions-and-traces) 16 | - [Non-boolean output alphabets](#non-boolean-output-alphabets) 17 | - [Moore Machines](#moore-machines) 18 | - [DFA <-> Dictionary](#dfa---dictionary) 19 | - [Computing Reachable States](#computing-reachable-states) 20 | - [Sampling Paths](#sampling-paths) 21 | - [Running interactively (Co-Routine API)](#running-interactively-co-routine-api) 22 | - [Visualizing DFAs](#visualizing-dfas) 23 | 24 | 25 | 26 | 27 | **Features:** 28 | 29 | 1. State can be any Hashable object. 30 | 2. Alphabet can be any finite sequence of Hashable objects. 31 | 3. Designed to be immutable and hashable (assuming components are 32 | immutable and hashable). 33 | 4. Design choice to allow transition map and accepting set to be 34 | given as functions rather than an explicit `dict` or `set`. 35 | 36 | # Installation 37 | 38 | If you just need to use `dfa`, you can just run: 39 | 40 | `$ pip install dfa` 41 | 42 | For developers, note that this project uses the 43 | [poetry](https://poetry.eustace.io/) python package/dependency 44 | management tool. Please familarize yourself with it and then 45 | run: 46 | 47 | `$ poetry install` 48 | 49 | # Usage 50 | 51 | The `dfa` api is centered around the `DFA` object. 52 | 53 | By default, the `DFA` object models a `Deterministic Finite Acceptor`, 54 | e.g., a recognizer of a Regular Language. 55 | 56 | **Example Usage:** 57 | ```python 58 | from dfa import DFA 59 | 60 | dfa1 = DFA( 61 | start=0, 62 | inputs={0, 1}, 63 | label=lambda s: (s % 4) == 3, 64 | transition=lambda s, c: (s + c) % 4, 65 | ) 66 | 67 | dfa2 = DFA( 68 | start="left", 69 | inputs={"move right", "move left"}, 70 | label=lambda s: s == "left", 71 | transition=lambda s, c: "left" if c == "move left" else "right", 72 | ) 73 | ``` 74 | 75 | ## Membership Queries 76 | 77 | ```python 78 | assert dfa1.label([1, 1, 1, 1]) 79 | assert not dfa1.label([1, 0]) 80 | 81 | assert dfa2.label(["move right"]*100 + ["move left"]) 82 | assert not dfa2.label(["move left", "move right"]) 83 | ``` 84 | 85 | ## Transitions and Traces 86 | 87 | ```python 88 | assert dfa1.transition([1, 1, 1]) == 3 89 | assert list(dfa1.trace([1, 1, 1])) == [0, 1, 2, 3] 90 | ``` 91 | 92 | ## Non-boolean output alphabets 93 | 94 | Sometimes, it is useful to model an automata which can label a word 95 | using a non-Boolean alphabet. For example, `{True, False, UNSURE}`. 96 | 97 | The `DFA` object supports this by specifying the output alphabet. 98 | 99 | ```python 100 | UNSURE = None 101 | 102 | def my_labeler(s): 103 | if s % 4 == 2: 104 | return None 105 | return (s % 4) == 3 106 | 107 | 108 | dfa3 = DFA( 109 | start=0, 110 | inputs={0, 1}, 111 | label=my_labeler, 112 | transition=lambda s, c: (s + c) % 4, 113 | outputs={True, False, UNSURE}, 114 | ) 115 | ``` 116 | 117 | **Note:** If `outputs` is set to `None`, then no checks are done that 118 | the outputs are within the output alphabet. 119 | 120 | ```python 121 | dfa3 = DFA( 122 | start=0, 123 | inputs={0, 1}, 124 | label=my_labeler, 125 | transition=lambda s, c: (s + c) % 4, 126 | outputs=None, 127 | ) 128 | ``` 129 | 130 | ## Moore Machines 131 | 132 | Finally, by reinterpreting the structure of the `DFA` object, one can 133 | model a Moore Machine. For example, in 3 state counter, `dfa1`, the 134 | Moore Machine can output the current count. 135 | 136 | ```python 137 | assert dfa1.transduce(()) == () 138 | assert dfa1.transduce((1,)) == (False,) 139 | assert dfa1.transduce((1, 1, 1, 1)) == (False, False, False, True) 140 | ``` 141 | 142 | ## Language Queries 143 | 144 | Utility functions are available for testing if a language: 145 | 146 | 1. Is empty: `utils.find_word` 147 | 2. Is equivilent to another language: `utils.find_equiv_counterexample` 148 | 3. Is a subset of a another language: `utils.find_subset_counterexample` 149 | 150 | These operate by returning `None` if the property holds, i.e., 151 | `lang(dfa1) = ∅, lang(dfa1) ≡ lang(dfa2), lang(dfa1) ⊆ lang(dfa2)`, and 152 | returning a counterexample `Word` otherwise. 153 | 154 | ## DFA <-> Dictionary 155 | 156 | Note that `dfa` provides helper functions for going from a dictionary 157 | based representation of a deterministic transition system to a `DFA` 158 | object and back. 159 | 160 | ```python 161 | from dfa import dfa2dict, dict2dfa 162 | 163 | # DFA encoded a nested dictionaries with the following 164 | # signature. 165 | # : (