├── .gitattributes ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── ExhaustificationAlgorithm.pdf ├── HOWTO.md ├── LICENSE.txt ├── Makefile ├── README.md ├── dist ├── Exh-0.2.tar.gz ├── Exh-0.3.tar.gz ├── Exh-0.4.linux-x86_64.tar.gz ├── Exh-0.4.tar.gz ├── Exh-0.5-py3-none-any.whl ├── Exh-0.5.linux-x86_64.tar.gz ├── Exh-0.5.tar.gz ├── Exh-0.6.linux-x86_64.tar.gz ├── Exh-0.6.tar.gz ├── Exh-0.7.1.tar.gz ├── Exh-0.7.2.tar.gz ├── Exh-0.7.3.tar.gz └── Exh-0.7.tar.gz ├── examples ├── Makefile ├── custom_size_domains │ ├── domain.html │ ├── domain.ipynb │ └── domain.py ├── distributive_implicatures │ ├── DistributiveImplicatures.html │ └── DistributiveImplicatures.ipynb ├── focus │ ├── focus.html │ ├── focus.ipynb │ └── focus.py ├── runnb.py └── tutorial │ ├── Tutorial.html │ ├── Tutorial.ipynb │ ├── Tutorial.py │ └── runnb.py ├── exh ├── __init__.py ├── alternatives.py ├── exhaust.py ├── exts │ ├── focus │ │ ├── __init__.py │ │ ├── formula.py │ │ └── scales.py │ ├── gq │ │ └── __init__.py │ └── subdomain │ │ └── __init__.py ├── fol │ ├── __init__.py │ └── quantifier.py ├── model │ ├── __init__.py │ ├── exceptions.py │ ├── model.py │ ├── options.py │ └── vars.py ├── options.py ├── prop │ ├── __init__.py │ ├── __init__.pyc │ ├── display.py │ ├── evaluate.py │ ├── formula.py │ ├── formula.pyc │ ├── predicate.py │ └── simplify.py ├── scales.py └── utils │ ├── __init__.py │ └── table.py ├── meta.yaml ├── setup.cfg ├── setup.py ├── tests ├── evaluate.py ├── focus.py ├── main.py ├── subdomain.py ├── test └── var_managers.py └── utils ├── py2jp └── runnb.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # py cache 2 | **/__pycache__ 3 | 4 | **/.ipynb_checkpoints 5 | 6 | /AnacondaConsole.lnk 7 | 8 | DistributiveImplicatures.py 9 | 10 | RSA.ipynb 11 | Scratchpad.* 12 | Profiling.* 13 | 14 | # Byte-compiled / optimized / DLL files 15 | __pycache__/ 16 | *.py[cod] 17 | *$py.class 18 | 19 | # C extensions 20 | *.so 21 | 22 | # Distribution / packaging 23 | .Python 24 | build/ 25 | develop-eggs/ 26 | dist/ 27 | downloads/ 28 | eggs/ 29 | .eggs/ 30 | lib/ 31 | lib64/ 32 | parts/ 33 | sdist/ 34 | var/ 35 | wheels/ 36 | share/python-wheels/ 37 | *.egg-info/ 38 | .installed.cfg 39 | *.egg 40 | MANIFEST 41 | 42 | # PyInstaller 43 | # Usually these files are written by a python script from a template 44 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 45 | *.manifest 46 | *.spec 47 | 48 | # Installer logs 49 | pip-log.txt 50 | pip-delete-this-directory.txt 51 | 52 | # Unit test / coverage reports 53 | htmlcov/ 54 | .tox/ 55 | .nox/ 56 | .coverage 57 | .coverage.* 58 | .cache 59 | nosetests.xml 60 | coverage.xml 61 | *.cover 62 | *.py,cover 63 | .hypothesis/ 64 | .pytest_cache/ 65 | cover/ 66 | 67 | # Translations 68 | *.mo 69 | *.pot 70 | 71 | # Django stuff: 72 | *.log 73 | local_settings.py 74 | db.sqlite3 75 | db.sqlite3-journal 76 | 77 | # Flask stuff: 78 | instance/ 79 | .webassets-cache 80 | 81 | # Scrapy stuff: 82 | .scrapy 83 | 84 | # Sphinx documentation 85 | docs/_build/ 86 | 87 | # PyBuilder 88 | .pybuilder/ 89 | target/ 90 | 91 | # Jupyter Notebook 92 | .ipynb_checkpoints 93 | 94 | # IPython 95 | profile_default/ 96 | ipython_config.py 97 | 98 | # pyenv 99 | # For a library or package, you might want to ignore these files since the code is 100 | # intended to run in multiple environments; otherwise, check them in: 101 | # .python-version 102 | 103 | # pipenv 104 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 105 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 106 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 107 | # install all needed dependencies. 108 | #Pipfile.lock 109 | 110 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 111 | __pypackages__/ 112 | 113 | # Celery stuff 114 | celerybeat-schedule 115 | celerybeat.pid 116 | 117 | # SageMath parsed files 118 | *.sage.py 119 | 120 | # Environments 121 | .env 122 | .venv 123 | env/ 124 | venv/ 125 | ENV/ 126 | env.bak/ 127 | venv.bak/ 128 | 129 | # Spyder project settings 130 | .spyderproject 131 | .spyproject 132 | 133 | # Rope project settings 134 | .ropeproject 135 | 136 | # mkdocs documentation 137 | /site 138 | 139 | # mypy 140 | .mypy_cache/ 141 | .dmypy.json 142 | dmypy.json 143 | 144 | # Pyre type checker 145 | .pyre/ 146 | 147 | # pytype static type analyzer 148 | .pytype/ 149 | 150 | # Cython debug symbols 151 | cython_debug/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ================================= 3 | 4 | # 0.7.4 5 | 6 | - fixed bug when changing arity of variables 7 | - made options parameters for Exh truly dynamic ; users can now change them on the fly 8 | - when number of alternatives is large, alternatives are displayed as a list 9 | - added bare-bones support for Generalized Quantifiers (cf exh.exts.gq) 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ================================= 3 | 4 | Thanks! All and any contributions are welcome. 5 | 6 | # How? 7 | 8 | ## Pointing out problems 9 | 10 | There is an "Issues" tab in the repository. You can post any and all problems here. Make sure to indicate the necessary information to reproduce your problem. This includes: 11 | 12 | - The version of the package you use 13 | ```python 14 | import exh 15 | print(exh.__version__) 16 | ``` 17 | - A complete code fragment which reproduces the bug. 18 | 19 | ## Modifying the code 20 | 21 | 1. Fork the repository 22 | 2. Do desired modifications 23 | 3. Make sure to run "test.py" when you're done. If it runs fully without exceptions, you're unlikely to have broken anything. (If you find test cases not already included in "test.py", feel free to add them to "test.py") 24 | 3. Create pull request in this repository 25 | 26 | # Info about the structure of the package 27 | 28 | This is more in-depth info about the package for the purpose of modification 29 | 30 | ## Modules and submodules 31 | * **model**: defines *Universe* and *VarManager*, keeps track of all logical possibilities 32 | - *Universe*: essentially a big truth-table, a wrapper around big numpy array of booleans 33 | - *VarManager*: maps human-readable predicates and propositions (e.g. "p(0)" or "a") to positions in memory (e.g. the 7th bit) 34 | * **prop**: defines abstract base class *Formula* and important sub-class *Pred*, implements propositional calculus 35 | - *Formula* : overrides binary operators (|, &, ~), keep track of open variables, defines display methods (implementation is split across *formula.py*, *evaluate.py*, *display.py*) 36 | - *Pred* : Base class for n-ary predicates (implementation in *predicate.py*) 37 | * **fol**: appends to *prop* the class *Quantifier* to deal with 1st order logic 38 | * **utils**: class Table for displaying pretty HTML or text tables (used for truth tables) 39 | * **exhaust.py** 40 | - *Exhaust* : this class comports all the methods to compute IE and II 41 | - *Exh* : wraps *Exhaust* in *Formula* wrapping 42 | * **alternatives.py**: defines a number of methods for automatic generation of alternatives (these methods is called whenever *Exh* is built with no *alts* argument), + find maximal sets of consistent alternatives -------------------------------------------------------------------------------- /ExhaustificationAlgorithm.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenyC/Exh/2c48806462c7d502e36b17b55547eb11738891a2/ExhaustificationAlgorithm.pdf -------------------------------------------------------------------------------- /HOWTO.md: -------------------------------------------------------------------------------- 1 | How to publish to PyPI 2 | =============================== 3 | 4 | ## Step I: make sure to increase version number 5 | 6 | Version number is located in `exh/__init__.py` 7 | 8 | ## Step II: Create archive files 9 | 10 | ```bash 11 | python setup.py sdist bdist_wheel 12 | ``` 13 | 14 | ## Step III: check package 15 | 16 | ```bash 17 | twine check dist/NAME_OF_DISTFILE 18 | ``` 19 | 20 | ## Step IV : upload package 21 | 22 | ```bash 23 | twine upload dist/FILES_TO_UPLOAD 24 | ``` 25 | 26 | **Caveat:** No reuploads are possible. One ought to make sure that the archive contains everything as desired. 27 | 28 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2020 Keny Chatain 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | all: example test 4 | 5 | example: 6 | cd examples && make 7 | 8 | test: 9 | cd tests && bash test -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Exh 2 | ==================== 3 | 4 | You can find a Jupyter notebook tutorial *Tutorial.ipynb* under *examples/tutorial/* to get a headstart on using the library. 5 | 6 | ## Description 7 | 8 | *Exh* can represent first-order logic formula and can compute the result of applying innocent exclusion exahsutification to the formulas. Internally, it uses the algorithm suggested in Spector (2016), which is faster than simply applying the definition in Fox (2007). 9 | 10 | ## Installation 11 | 12 | The package is available on PyPI. Run the following to install it: 13 | 14 | ```bash 15 | pip install exh 16 | ``` 17 | 18 | ## Features 19 | 20 | - Innocent inclusion, innocent exclusion of propositional formulas 21 | - Computing maximal consistent sets of formulas 22 | - Quantifiers 23 | - Recursively exhaustified formulas (free choice example in tutorial) 24 | 25 | ## Caveats 26 | 27 | - The problem of finding maximally consistent sets of formulas is computationally hard. The algorithm scales up poorly if the number of independent variables is big. It however handles large numbers of alternatives well enough. However, most real-life examples run fast enough. 28 | 29 | ## Dependencies 30 | 31 | - Numpy > 1.14 32 | - Jupyter > 1.0.0 33 | 34 | -------------------------------------------------------------------------------- /dist/Exh-0.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenyC/Exh/2c48806462c7d502e36b17b55547eb11738891a2/dist/Exh-0.2.tar.gz -------------------------------------------------------------------------------- /dist/Exh-0.3.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenyC/Exh/2c48806462c7d502e36b17b55547eb11738891a2/dist/Exh-0.3.tar.gz -------------------------------------------------------------------------------- /dist/Exh-0.4.linux-x86_64.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenyC/Exh/2c48806462c7d502e36b17b55547eb11738891a2/dist/Exh-0.4.linux-x86_64.tar.gz -------------------------------------------------------------------------------- /dist/Exh-0.4.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenyC/Exh/2c48806462c7d502e36b17b55547eb11738891a2/dist/Exh-0.4.tar.gz -------------------------------------------------------------------------------- /dist/Exh-0.5-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenyC/Exh/2c48806462c7d502e36b17b55547eb11738891a2/dist/Exh-0.5-py3-none-any.whl -------------------------------------------------------------------------------- /dist/Exh-0.5.linux-x86_64.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenyC/Exh/2c48806462c7d502e36b17b55547eb11738891a2/dist/Exh-0.5.linux-x86_64.tar.gz -------------------------------------------------------------------------------- /dist/Exh-0.5.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenyC/Exh/2c48806462c7d502e36b17b55547eb11738891a2/dist/Exh-0.5.tar.gz -------------------------------------------------------------------------------- /dist/Exh-0.6.linux-x86_64.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenyC/Exh/2c48806462c7d502e36b17b55547eb11738891a2/dist/Exh-0.6.linux-x86_64.tar.gz -------------------------------------------------------------------------------- /dist/Exh-0.6.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenyC/Exh/2c48806462c7d502e36b17b55547eb11738891a2/dist/Exh-0.6.tar.gz -------------------------------------------------------------------------------- /dist/Exh-0.7.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenyC/Exh/2c48806462c7d502e36b17b55547eb11738891a2/dist/Exh-0.7.1.tar.gz -------------------------------------------------------------------------------- /dist/Exh-0.7.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenyC/Exh/2c48806462c7d502e36b17b55547eb11738891a2/dist/Exh-0.7.2.tar.gz -------------------------------------------------------------------------------- /dist/Exh-0.7.3.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenyC/Exh/2c48806462c7d502e36b17b55547eb11738891a2/dist/Exh-0.7.3.tar.gz -------------------------------------------------------------------------------- /dist/Exh-0.7.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenyC/Exh/2c48806462c7d502e36b17b55547eb11738891a2/dist/Exh-0.7.tar.gz -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | 2 | export KERNEL?= 3 | KERNEL_OPTS = $(if $(KERNEL),--kernel $(KERNEL),) 4 | 5 | UTILS_DIR = ../utils/ 6 | PY2JP = $(UTILS_DIR)py2jp 7 | RUNNB = python $(UTILS_DIR)runnb.py 8 | 9 | all: tutorial domain focus 10 | 11 | .PRECIOUS: tutorial/%.html tutorial/%.ipynb custom_size_domains/%.ipynb custom_size_domains/%.html 12 | 13 | tutorial: tutorial/Tutorial.ipynb tutorial/Tutorial.html 14 | @echo 15 | @echo ">>>>>>>>>>>>>>>>> Tutorial done!" 16 | @echo 17 | 18 | 19 | focus: focus/focus.ipynb focus/focus.html 20 | @echo 21 | @echo ">>>>>>>>>>>>>>>>> Focus done!" 22 | @echo 23 | 24 | domain: custom_size_domains/domain.ipynb custom_size_domains/domain.html 25 | @echo 26 | @echo ">>>>>>>>>>>>>>>>> Custom domains done!" 27 | @echo 28 | 29 | # tutorial: tutorial/tutorial.html 30 | # @echo "> Tutorial done!" 31 | 32 | 33 | %.html: %.ipynb 34 | jupyter nbconvert $< --to html 35 | @echo 36 | @echo ">>>>>>>>>>>>>>>>> Created $@ file" 37 | @echo 38 | 39 | %.ipynb: %.py 40 | $(PY2JP) $< 41 | $(RUNNB) $@ $(KERNEL_OPTS) --run-path .. --inplace 42 | 43 | @echo 44 | @echo ">>>>>>>>>>>>>>>>> Created $@ file" 45 | @echo 46 | 47 | clean: 48 | rm -f tutorial/*.ipynb 49 | rm -f tutorial/*.html 50 | 51 | rm -f custom_size_domains/*.ipynb 52 | rm -f custom_size_domains/*.html 53 | # cd ..; jupyter nbconvert --execute --inplace --to notebook examples/tutorial/Tutorial.ipynb --ExecutePreprocessor.kernel_name=$(IPYKERNEL_NAME) 54 | 55 | 56 | -------------------------------------------------------------------------------- /examples/custom_size_domains/domain.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Setting custom domain sizes\n", 8 | "\n", 9 | "Version > 1.0 is required. It should be available on PyPI or from GitHub directly\n" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": { 16 | "execution": { 17 | "iopub.execute_input": "2021-01-15T08:36:47.853956Z", 18 | "iopub.status.busy": "2021-01-15T08:36:47.841388Z", 19 | "iopub.status.idle": "2021-01-15T08:36:48.057909Z", 20 | "shell.execute_reply": "2021-01-15T08:36:48.058544Z" 21 | } 22 | }, 23 | "outputs": [ 24 | { 25 | "name": "stdout", 26 | "output_type": "stream", 27 | "text": [ 28 | "1.0\n" 29 | ] 30 | } 31 | ], 32 | "source": [ 33 | "import exh\n", 34 | "from exh import *\n", 35 | "print(exh.__version__)\n" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "To create a custom domain of quantification, use the following:\n" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 2, 48 | "metadata": { 49 | "execution": { 50 | "iopub.execute_input": "2021-01-15T08:36:48.071030Z", 51 | "iopub.status.busy": "2021-01-15T08:36:48.069958Z", 52 | "iopub.status.idle": "2021-01-15T08:36:48.075871Z", 53 | "shell.execute_reply": "2021-01-15T08:36:48.077848Z" 54 | } 55 | }, 56 | "outputs": [], 57 | "source": [ 58 | "D7 = Domain(7) # domain of size 7\n" 59 | ] 60 | }, 61 | { 62 | "cell_type": "markdown", 63 | "metadata": {}, 64 | "source": [ 65 | "To use it, you need to define your predicates accordingly.\n", 66 | "Specify the name of the variables that the predicate depends on in the ``depends`` parameter, and a corresponding list of domain.\n", 67 | "\n", 68 | "**Notes:**\n", 69 | " - In version 1.0, you can but do not need to specify an index. Each new created predicate will get a new unique index.\n", 70 | " - The argument ``domains`` need not be provided, in which case the domains will all be ``default_domain``. The behavior of the predicate will then be exactly the same as in versions < 1.0.\n" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": 3, 76 | "metadata": { 77 | "execution": { 78 | "iopub.execute_input": "2021-01-15T08:36:48.090797Z", 79 | "iopub.status.busy": "2021-01-15T08:36:48.089359Z", 80 | "iopub.status.idle": "2021-01-15T08:36:48.093443Z", 81 | "shell.execute_reply": "2021-01-15T08:36:48.094417Z" 82 | } 83 | }, 84 | "outputs": [], 85 | "source": [ 86 | "apple = Pred(\n", 87 | "\tname = \"apple\", \n", 88 | "\tdepends = [\"x\"], \n", 89 | "\tdomains = [D7] # the variable \"x\" ranges over D7\n", 90 | ")\n", 91 | "\n", 92 | "\n", 93 | "eat = Pred(\n", 94 | "\tname = \"eat\", \n", 95 | "\tdepends = [\"x\", \"y\"], \n", 96 | "\tdomains = [D7, default_domain] # the variable \"x\" ranges over D7 ; y over a default domain whose size is bound to ``exh.model.options.domain_quant``\n", 97 | ")\n" 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": {}, 103 | "source": [ 104 | "Quantifiers must also be adjusted. You can use ``Ex_in_``, ``Az_in_\", etc., to create a quantifier that ranges over a custom domain.\n" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": 4, 110 | "metadata": { 111 | "execution": { 112 | "iopub.execute_input": "2021-01-15T08:36:48.106686Z", 113 | "iopub.status.busy": "2021-01-15T08:36:48.104982Z", 114 | "iopub.status.idle": "2021-01-15T08:36:48.109238Z", 115 | "shell.execute_reply": "2021-01-15T08:36:48.108650Z" 116 | } 117 | }, 118 | "outputs": [], 119 | "source": [ 120 | "f = Ex_in_(D7) > apple\n", 121 | "g = Ay > Ex_in_(D7) > eat\n", 122 | "\n" 123 | ] 124 | }, 125 | { 126 | "cell_type": "markdown", 127 | "metadata": {}, 128 | "source": [ 129 | "Keep in mind that large domains incur large cost. For instance, even just the following starts to reach the limit of the reasonable:\n" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": 5, 135 | "metadata": { 136 | "execution": { 137 | "iopub.execute_input": "2021-01-15T08:36:48.126674Z", 138 | "iopub.status.busy": "2021-01-15T08:36:48.126029Z", 139 | "iopub.status.idle": "2021-01-15T08:36:48.131204Z", 140 | "shell.execute_reply": "2021-01-15T08:36:48.131792Z" 141 | } 142 | }, 143 | "outputs": [ 144 | { 145 | "name": "stdout", 146 | "output_type": "stream", 147 | "text": [ 148 | "Number of worlds: 16384 = 2^(7+7)\n", 149 | "3 predicates over D7: 2097152\n" 150 | ] 151 | } 152 | ], 153 | "source": [ 154 | "pear = Pred(\n", 155 | "\tname = \"pear\", \n", 156 | "\tdepends = [\"x\"], \n", 157 | "\tdomains = [D7] # the variable \"x\" ranges over D7\n", 158 | ")\n", 159 | "f = Ex_in_(D7) > apple | pear\n", 160 | "universe = Universe(f = f)\n", 161 | "print(\"Number of worlds:\", universe.n_worlds, \"= 2^(7+7)\")\n", 162 | "\n", 163 | "# For 3 predicates:\n", 164 | "print(\"3 predicates over D7:\", 2 ** (7 * 3)) # 2M" 165 | ] 166 | } 167 | ], 168 | "metadata": { 169 | "anaconda-cloud": {}, 170 | "kernelspec": { 171 | "display_name": "Python 3", 172 | "language": "python", 173 | "name": "python3" 174 | }, 175 | "language_info": { 176 | "codemirror_mode": { 177 | "name": "ipython", 178 | "version": 3 179 | }, 180 | "file_extension": ".py", 181 | "mimetype": "text/x-python", 182 | "name": "python", 183 | "nbconvert_exporter": "python", 184 | "pygments_lexer": "ipython3", 185 | "version": "3.6.11" 186 | } 187 | }, 188 | "nbformat": 4, 189 | "nbformat_minor": 4 190 | } 191 | -------------------------------------------------------------------------------- /examples/custom_size_domains/domain.py: -------------------------------------------------------------------------------- 1 | # %% 2 | """ 3 | # Setting custom domain sizes 4 | 5 | Version > 1.0 is required. It should be available on PyPI or from GitHub directly 6 | """ 7 | 8 | import exh 9 | from exh import * 10 | print(exh.__version__) 11 | 12 | # %% 13 | """ 14 | To create a custom domain of quantification, use the following: 15 | """ 16 | 17 | D7 = Domain(7) # domain of size 7 18 | 19 | # %% 20 | """ 21 | To use it, you need to define your predicates accordingly. 22 | Specify the name of the variables that the predicate depends on in the ``depends`` parameter, and a corresponding list of domain. 23 | 24 | **Notes:** 25 | - In version 1.0, you can but do not need to specify an index. Each new created predicate will get a new unique index. 26 | - The argument ``domains`` need not be provided, in which case the domains will all be ``default_domain``. The behavior of the predicate will then be exactly the same as in versions < 1.0. 27 | """ 28 | apple = Pred( 29 | name = "apple", 30 | depends = ["x"], 31 | domains = [D7] # the variable "x" ranges over D7 32 | ) 33 | 34 | 35 | eat = Pred( 36 | name = "eat", 37 | depends = ["x", "y"], 38 | domains = [D7, default_domain] # the variable "x" ranges over D7 ; y over a default domain whose size is bound to ``exh.model.options.domain_quant`` 39 | ) 40 | 41 | # %% 42 | """ 43 | Quantifiers must also be adjusted. You can use ``Ex_in_``, ``Az_in_", etc., to create a quantifier that ranges over a custom domain. 44 | """ 45 | 46 | f = Ex_in_(D7) > apple 47 | g = Ay > Ex_in_(D7) > eat 48 | 49 | 50 | # %% 51 | """ 52 | Keep in mind that large domains incur large cost. For instance, even just the following starts to reach the limit of the reasonable: 53 | """ 54 | pear = Pred( 55 | name = "pear", 56 | depends = ["x"], 57 | domains = [D7] # the variable "x" ranges over D7 58 | ) 59 | f = Ex_in_(D7) > apple | pear 60 | universe = Universe(f = f) 61 | print("Number of worlds:", universe.n_worlds, "= 2^(7+7)") 62 | 63 | # For 3 predicates: 64 | print("3 predicates over D7:", 2 ** (7 * 3)) # 2M -------------------------------------------------------------------------------- /examples/distributive_implicatures/DistributiveImplicatures.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "Distributive Implicatures\n", 8 | "===========================\n", 9 | "\n", 10 | "From Bar-Lev & Fox (2016)\n", 11 | "\n", 12 | "Imports, inputting version, setting to non-LateX display\n" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "metadata": {}, 18 | "source": [ 19 | "from exh import *\n", 20 | "# print(exh.__version__)\n", 21 | "from exh.model import options\n", 22 | "options.latex_display = False\n", 23 | "options.dom_quant = 6\n", 24 | "\n" 25 | ], 26 | "outputs": [], 27 | "execution_count": null 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "Indicating dependencies on x ; will raise innocuous warnings.\n" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "metadata": {}, 39 | "source": [ 40 | "a(\"x\")\n", 41 | "b(\"x\")\n", 42 | "\n" 43 | ], 44 | "outputs": [], 45 | "execution_count": null 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "metadata": {}, 50 | "source": [ 51 | "# Case study 1: Universal quantifiers\n", 52 | "\n", 53 | "Performing recursive exhaustification step-by-step so that we can see the result of intermediate computations\n" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "metadata": {}, 59 | "source": [ 60 | "scales = [{Existential, Universal}] # we are not allowing conjunctive alternatives\n", 61 | "f = Ax > a | b\n", 62 | "first_exh = Exh(f, scales = scales)\n", 63 | "second_exh = Exh(first_exh, scales = scales)\n", 64 | "\n", 65 | "universe = Universe(f = second_exh)\n" 66 | ], 67 | "outputs": [], 68 | "execution_count": null 69 | }, 70 | { 71 | "cell_type": "code", 72 | "metadata": {}, 73 | "source": [ 74 | "first_exh.diagnose(display = print)\n", 75 | "# Nothing is excludable\n" 76 | ], 77 | "outputs": [], 78 | "execution_count": null 79 | }, 80 | { 81 | "cell_type": "code", 82 | "metadata": {}, 83 | "source": [ 84 | "second_exh.diagnose(display = print)\n", 85 | "\n", 86 | "# weak distributivity inferences should be entailed\n", 87 | "print(universe.entails(second_exh, Ex > a))\n", 88 | "print(universe.entails(second_exh, Ex > b))\n", 89 | "\n", 90 | "# but strong implicatures shouldn't be\n", 91 | "print(universe.entails(second_exh, Ex > ~a))\n", 92 | "print(universe.entails(second_exh, Ex > ~b))\n", 93 | "\n" 94 | ], 95 | "outputs": [], 96 | "execution_count": null 97 | }, 98 | { 99 | "cell_type": "markdown", 100 | "metadata": {}, 101 | "source": [ 102 | "# Case study 2: \"most\"\n", 103 | "\n", 104 | "This requires a version of the package 0.7.4 (not posted yet! you can get it from GitHub)\n", 105 | "\n", 106 | "We would like to run the same reasoning with \"most\"\n" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "metadata": {}, 112 | "source": [ 113 | "from exh.exts.gq import *\n", 114 | "\n", 115 | "scales = [{Existential, Most}]\n", 116 | "prejacent = Mx > a | b\n", 117 | "first_exh = Exh(prejacent, scales = scales)\n", 118 | "\n" 119 | ], 120 | "outputs": [], 121 | "execution_count": null 122 | }, 123 | { 124 | "cell_type": "markdown", 125 | "metadata": {}, 126 | "source": [ 127 | "Just as before, the first Exh is vacuous\n" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "metadata": {}, 133 | "source": [ 134 | "first_exh.diagnose(print)\n", 135 | "\n" 136 | ], 137 | "outputs": [], 138 | "execution_count": null 139 | }, 140 | { 141 | "cell_type": "markdown", 142 | "metadata": {}, 143 | "source": [ 144 | "The second Exh can exclude most of its alternatives\n" 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "metadata": {}, 150 | "source": [ 151 | "second_exh = Exh(first_exh, scales = scales)\n", 152 | "\n" 153 | ], 154 | "outputs": [], 155 | "execution_count": null 156 | }, 157 | { 158 | "cell_type": "markdown", 159 | "metadata": {}, 160 | "source": [ 161 | "Because it is unclear what these alternatives consists in, let's display and diagnose all of them\n" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "metadata": {}, 167 | "source": [ 168 | "for alt in second_exh.e.innocently_excl:\n", 169 | "\tprint(\"########## \", end = \"\")\n", 170 | "\tprint(alt)\n", 171 | "\talt.diagnose(print)\n", 172 | "\n" 173 | ], 174 | "outputs": [], 175 | "execution_count": null 176 | }, 177 | { 178 | "cell_type": "markdown", 179 | "metadata": {}, 180 | "source": [ 181 | "We can now check for entailments: weak SIs are entailed.\n" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "metadata": {}, 187 | "source": [ 188 | "print(universe.entails(second_exh, Ex > a))\n", 189 | "print(universe.entails(second_exh, Ex > b))\n", 190 | "\n", 191 | "# Is it just equivalent to the weak dist implicatures?\n", 192 | "print(universe.equivalent(second_exh, prejacent & (Ex > b) & (Ex > a)))\n", 193 | "\n" 194 | ], 195 | "outputs": [], 196 | "execution_count": null 197 | }, 198 | { 199 | "cell_type": "markdown", 200 | "metadata": {}, 201 | "source": [ 202 | "One may wonder if the same would hold if \"all\" was an alternative to most\n" 203 | ] 204 | }, 205 | { 206 | "cell_type": "code", 207 | "metadata": {}, 208 | "source": [ 209 | "scales = [{Existential, Most, Universal}]\n", 210 | "first_exh = Exh(prejacent, scales = scales) # Let's recompute \"first_exh\" with this scale\n", 211 | "first_exh.diagnose(print)\n", 212 | "\n" 213 | ], 214 | "outputs": [], 215 | "execution_count": null 216 | }, 217 | { 218 | "cell_type": "markdown", 219 | "metadata": {}, 220 | "source": [ 221 | "Now some exclusion is possible at the first Exh. What about the second Exh? \n" 222 | ] 223 | }, 224 | { 225 | "cell_type": "code", 226 | "metadata": {}, 227 | "source": [ 228 | "second_exh = Exh(first_exh, scales = scales)\n", 229 | "second_exh.diagnose(print) \n", 230 | "\n" 231 | ], 232 | "outputs": [], 233 | "execution_count": null 234 | }, 235 | { 236 | "cell_type": "markdown", 237 | "metadata": {}, 238 | "source": [ 239 | "Most of the excluded alternatives either were already exluded from the first round or were the alternatives we excluded when \"all\" wasn't alternative.\n", 240 | "This makes me confident that the generated implicatures are simply dist implicatures + \"all\" implicatures ; and indeed, it is so.\n" 241 | ] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "metadata": {}, 246 | "source": [ 247 | "print(universe.equivalent(\n", 248 | "\tsecond_exh, \n", 249 | "\tprejacent & (Ex > b) & (Ex > a) & ~(Ax > a | b)\n", 250 | "))\n", 251 | "\n", 252 | "\n", 253 | "\n", 254 | "\n", 255 | "# %%" 256 | ], 257 | "outputs": [], 258 | "execution_count": null 259 | } 260 | ], 261 | "metadata": { 262 | "anaconda-cloud": {}, 263 | "kernelspec": { 264 | "display_name": "Python 3", 265 | "language": "python", 266 | "name": "python3" 267 | }, 268 | "language_info": { 269 | "codemirror_mode": { 270 | "name": "ipython", 271 | "version": 3 272 | }, 273 | "file_extension": ".py", 274 | "mimetype": "text/x-python", 275 | "name": "python", 276 | "nbconvert_exporter": "python", 277 | "pygments_lexer": "ipython3", 278 | "version": "3.6.1" 279 | } 280 | }, 281 | "nbformat": 4, 282 | "nbformat_minor": 4 283 | } -------------------------------------------------------------------------------- /examples/focus/focus.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Setting custom domain sizes\n", 8 | "\n", 9 | "Version > 1.1 is required. It should be available on PyPI or from GitHub directly.\n", 10 | "Focus is an extension and must be activated by importing it after importing \"exh\".\n" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 1, 16 | "metadata": { 17 | "execution": { 18 | "iopub.execute_input": "2021-10-11T20:55:05.631802Z", 19 | "iopub.status.busy": "2021-10-11T20:55:05.623432Z", 20 | "iopub.status.idle": "2021-10-11T20:55:05.750082Z", 21 | "shell.execute_reply": "2021-10-11T20:55:05.750962Z" 22 | } 23 | }, 24 | "outputs": [ 25 | { 26 | "name": "stdout", 27 | "output_type": "stream", 28 | "text": [ 29 | "1.1\n" 30 | ] 31 | } 32 | ], 33 | "source": [ 34 | "import exh\n", 35 | "from exh import *\n", 36 | "from exh.exts.focus import *\n", 37 | "print(exh.__version__)\n" 38 | ] 39 | }, 40 | { 41 | "cell_type": "markdown", 42 | "metadata": {}, 43 | "source": [ 44 | "Focus allows you to give stipulated alternatives to specific constituents.\n", 45 | "Use the constructor \"Focus\" to put focus on apple.\n" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": 2, 51 | "metadata": { 52 | "execution": { 53 | "iopub.execute_input": "2021-10-11T20:55:05.763504Z", 54 | "iopub.status.busy": "2021-10-11T20:55:05.762883Z", 55 | "iopub.status.idle": "2021-10-11T20:55:05.766101Z", 56 | "shell.execute_reply": "2021-10-11T20:55:05.766479Z" 57 | } 58 | }, 59 | "outputs": [ 60 | { 61 | "data": { 62 | "text/html": [ 63 | "$apple_{F}$" 64 | ], 65 | "text/plain": [ 66 | "" 67 | ] 68 | }, 69 | "metadata": {}, 70 | "output_type": "display_data" 71 | } 72 | ], 73 | "source": [ 74 | "apple = Pred(name = \"apple\")\n", 75 | "cantaloupe = Pred(name = \"cantaloupe\")\n", 76 | "\n", 77 | "# Creating sentence where \"apple\" is focused and has \"cantaloupe\" as an alternative\n", 78 | "prejacent = Focus(apple, alts = [cantaloupe]) \n", 79 | "\n", 80 | "# A focused constituent is printed with F subscript\n", 81 | "jprint(prejacent)\n" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "metadata": {}, 87 | "source": [ 88 | "In the absence of exhaustivity operators, a focused element behaves just like its unfocused counterpart.\n" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": 3, 94 | "metadata": { 95 | "execution": { 96 | "iopub.execute_input": "2021-10-11T20:55:05.772069Z", 97 | "iopub.status.busy": "2021-10-11T20:55:05.771425Z", 98 | "iopub.status.idle": "2021-10-11T20:55:05.774497Z", 99 | "shell.execute_reply": "2021-10-11T20:55:05.774926Z" 100 | } 101 | }, 102 | "outputs": [ 103 | { 104 | "name": "stdout", 105 | "output_type": "stream", 106 | "text": [ 107 | "Are focused and unfocused formulas are equivalent? True\n" 108 | ] 109 | } 110 | ], 111 | "source": [ 112 | "focused = Focus(apple, alts = [cantaloupe]) & Focus(cantaloupe, alts = [apple])\n", 113 | "unfocused = apple & cantaloupe\n", 114 | "\n", 115 | "universe = Universe(fs = [focused, unfocused])\n", 116 | "\n", 117 | "print(\n", 118 | "\t\"Are focused and unfocused formulas are equivalent?\",\n", 119 | "\tuniverse.equivalent(focused, unfocused)\n", 120 | ")\n" 121 | ] 122 | }, 123 | { 124 | "cell_type": "markdown", 125 | "metadata": {}, 126 | "source": [ 127 | "However, focused and unfocused constituents don't give rise to the same alternatives. \n", 128 | "This can be seen when applying Exh.\n" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": 4, 134 | "metadata": { 135 | "execution": { 136 | "iopub.execute_input": "2021-10-11T20:55:05.782076Z", 137 | "iopub.status.busy": "2021-10-11T20:55:05.781341Z", 138 | "iopub.status.idle": "2021-10-11T20:55:05.787067Z", 139 | "shell.execute_reply": "2021-10-11T20:55:05.786530Z" 140 | } 141 | }, 142 | "outputs": [ 143 | { 144 | "data": { 145 | "text/html": [ 146 | "[$apple_{F} \\land cantaloupe_{F}$, $apple_{F} \\land apple$, $cantaloupe \\land cantaloupe_{F}$, $cantaloupe \\land apple$, $apple_{F} \\lor cantaloupe_{F}$, $apple_{F} \\lor apple$, $cantaloupe \\lor cantaloupe_{F}$, $cantaloupe \\lor apple$, $apple_{F}$, $cantaloupe$, $cantaloupe_{F}$, $apple$]" 147 | ], 148 | "text/plain": [ 149 | "" 150 | ] 151 | }, 152 | "metadata": {}, 153 | "output_type": "display_data" 154 | }, 155 | { 156 | "data": { 157 | "text/html": [ 158 | "[$apple \\land cantaloupe$, $apple \\lor cantaloupe$, $apple$, $cantaloupe$]" 159 | ], 160 | "text/plain": [ 161 | "" 162 | ] 163 | }, 164 | "metadata": {}, 165 | "output_type": "display_data" 166 | } 167 | ], 168 | "source": [ 169 | "exh1 = Exh(focused)\n", 170 | "exh2 = Exh(unfocused)\n", 171 | "\n", 172 | "# all alternatives obtained by replacing ans simplifying the conjunction\n", 173 | "# + replacing conjuncts by their focused alternatives\n", 174 | "jprint(exh1.alts)\n", 175 | "# all alternatives obtained by replacing ans simplifying the conjunction\n", 176 | "jprint(exh2.alts)\n" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": {}, 182 | "source": [ 183 | "**Caveat:** the same predicate can appear focused in one place with a set of alternatives S, unfocused somewhere else,\n", 184 | "focused with a different set of alternatives S' in some other place. Each occurrence behaves as it is specified: \n", 185 | "the first occurence has S as alternatives, the second nothing, the third S'.\n" 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": 5, 191 | "metadata": { 192 | "execution": { 193 | "iopub.execute_input": "2021-10-11T20:55:05.800788Z", 194 | "iopub.status.busy": "2021-10-11T20:55:05.799662Z", 195 | "iopub.status.idle": "2021-10-11T20:55:05.803265Z", 196 | "shell.execute_reply": "2021-10-11T20:55:05.803895Z" 197 | } 198 | }, 199 | "outputs": [ 200 | { 201 | "data": { 202 | "text/html": [ 203 | "[$apple_{F} \\lor apple \\lor apple_{F}$, $apple_{F} \\lor apple \\lor kiwi$, $cantaloupe \\lor apple \\lor apple_{F}$, $cantaloupe \\lor apple \\lor kiwi$, $(apple_{F} \\land apple) \\lor apple_{F}$, $(apple_{F} \\land apple) \\lor kiwi$, $(cantaloupe \\land apple) \\lor apple_{F}$, $(cantaloupe \\land apple) \\lor kiwi$, $apple_{F}$, $apple_{F} \\lor kiwi$, $cantaloupe \\lor apple_{F}$, $cantaloupe \\lor kiwi$, $apple \\lor apple_{F}$, $apple \\lor kiwi$, $(apple_{F} \\lor apple) \\land apple_{F}$, $(apple_{F} \\lor apple) \\land kiwi$, $(cantaloupe \\lor apple) \\land apple_{F}$, $(cantaloupe \\lor apple) \\land kiwi$, $apple_{F} \\land apple \\land apple_{F}$, $apple_{F} \\land apple \\land kiwi$, $cantaloupe \\land apple \\land apple_{F}$, $cantaloupe \\land apple \\land kiwi$, $apple_{F} \\land kiwi$, $cantaloupe \\land apple_{F}$, $cantaloupe \\land kiwi$, $apple \\land apple_{F}$, $apple \\land kiwi$, $cantaloupe \\lor apple$, $cantaloupe \\land apple$, $cantaloupe$, $apple$, $kiwi$]" 204 | ], 205 | "text/plain": [ 206 | "" 207 | ] 208 | }, 209 | "metadata": {}, 210 | "output_type": "display_data" 211 | } 212 | ], 213 | "source": [ 214 | "kiwi = Pred(name = \"kiwi\")\n", 215 | "f = Focus(apple, alts=[cantaloupe]) | apple | Focus(apple, alts=[kiwi])\n", 216 | "jprint(Exh(f).alts)\n", 217 | "\n" 218 | ] 219 | } 220 | ], 221 | "metadata": { 222 | "anaconda-cloud": {}, 223 | "kernelspec": { 224 | "display_name": "Python 3", 225 | "language": "python", 226 | "name": "python3" 227 | }, 228 | "language_info": { 229 | "codemirror_mode": { 230 | "name": "ipython", 231 | "version": 3 232 | }, 233 | "file_extension": ".py", 234 | "mimetype": "text/x-python", 235 | "name": "python", 236 | "nbconvert_exporter": "python", 237 | "pygments_lexer": "ipython3", 238 | "version": "3.9.5" 239 | } 240 | }, 241 | "nbformat": 4, 242 | "nbformat_minor": 4 243 | } 244 | -------------------------------------------------------------------------------- /examples/focus/focus.py: -------------------------------------------------------------------------------- 1 | # %% 2 | """ 3 | # Setting custom domain sizes 4 | 5 | Version > 1.1 is required. It should be available on PyPI or from GitHub directly. 6 | Focus is an extension and must be activated by importing it after importing "exh". 7 | """ 8 | 9 | import exh 10 | from exh import * 11 | from exh.exts.focus import * 12 | print(exh.__version__) 13 | 14 | # %% 15 | """ 16 | Focus allows you to give stipulated alternatives to specific constituents. 17 | Use the constructor "Focus" to put focus on apple. 18 | """ 19 | 20 | apple = Pred(name = "apple") 21 | cantaloupe = Pred(name = "cantaloupe") 22 | 23 | # Creating sentence where "apple" is focused and has "cantaloupe" as an alternative 24 | prejacent = Focus(apple, alts = [cantaloupe]) 25 | 26 | # A focused constituent is printed with F subscript 27 | jprint(prejacent) 28 | 29 | # %% 30 | """ 31 | In the absence of exhaustivity operators, a focused element behaves just like its unfocused counterpart. 32 | """ 33 | 34 | focused = Focus(apple, alts = [cantaloupe]) & Focus(cantaloupe, alts = [apple]) 35 | unfocused = apple & cantaloupe 36 | 37 | universe = Universe(fs = [focused, unfocused]) 38 | 39 | print( 40 | "Are focused and unfocused formulas are equivalent?", 41 | universe.equivalent(focused, unfocused) 42 | ) 43 | 44 | # %% 45 | """ 46 | However, focused and unfocused constituents don't give rise to the same alternatives. 47 | This can be seen when applying Exh. 48 | """ 49 | 50 | exh1 = Exh(focused) 51 | exh2 = Exh(unfocused) 52 | 53 | # all alternatives obtained by replacing ans simplifying the conjunction 54 | # + replacing conjuncts by their focused alternatives 55 | jprint(exh1.alts) 56 | # all alternatives obtained by replacing ans simplifying the conjunction 57 | jprint(exh2.alts) 58 | 59 | # %% 60 | """ 61 | **Caveat:** the same predicate can appear focused in one place with a set of alternatives S, unfocused somewhere else, 62 | focused with a different set of alternatives S' in some other place. Each occurrence behaves as it is specified: 63 | the first occurence has S as alternatives, the second nothing, the third S'. 64 | """ 65 | 66 | kiwi = Pred(name = "kiwi") 67 | f = Focus(apple, alts=[cantaloupe]) | apple | Focus(apple, alts=[kiwi]) 68 | jprint(Exh(f).alts) 69 | 70 | -------------------------------------------------------------------------------- /examples/runnb.py: -------------------------------------------------------------------------------- 1 | ../utils/runnb.py -------------------------------------------------------------------------------- /examples/tutorial/Tutorial.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Tutorial \n", 8 | "\n", 9 | "## Imports\n" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": { 16 | "execution": { 17 | "iopub.execute_input": "2021-01-15T08:36:43.035193Z", 18 | "iopub.status.busy": "2021-01-15T08:36:43.028122Z", 19 | "iopub.status.idle": "2021-01-15T08:36:43.191585Z", 20 | "shell.execute_reply": "2021-01-15T08:36:43.192323Z" 21 | } 22 | }, 23 | "outputs": [ 24 | { 25 | "name": "stdout", 26 | "output_type": "stream", 27 | "text": [ 28 | "1.0\n" 29 | ] 30 | } 31 | ], 32 | "source": [ 33 | "from exh import *\n", 34 | "from exh.utils import jprint # for fancy displays in Jupyter Notebook\n", 35 | "import exh # This import is just to access the version\n", 36 | "print(exh.__version__)\n" 37 | ] 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "metadata": {}, 42 | "source": [ 43 | "## Create formulas\n", 44 | " \n", 45 | "Formulas are created from propositions (0-ary predictates) and predicates.\n", 46 | "Both of these are created by class ``Pred``.\n", 47 | "Predicates have names, which govern how they are displayed.\n", 48 | "Predicates have indices : two predicates with the same index always have the same truth-value and two predicates with different indices are logically independent. Here is a code to create a predicate with index 4.\n" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 2, 54 | "metadata": { 55 | "execution": { 56 | "iopub.execute_input": "2021-01-15T08:36:43.201870Z", 57 | "iopub.status.busy": "2021-01-15T08:36:43.200232Z", 58 | "iopub.status.idle": "2021-01-15T08:36:43.203082Z", 59 | "shell.execute_reply": "2021-01-15T08:36:43.203603Z" 60 | } 61 | }, 62 | "outputs": [], 63 | "source": [ 64 | "d = Pred(index = 4, name = \"d\") \n", 65 | "# both \"name\" and \"index\" are optional, \"name\" makes prettier display with print and helps for evaluation of formulas\n", 66 | "d1 = Pred(index = 7) \n", 67 | "d2 = Pred(name = \"d2\") \n", 68 | "\n", 69 | "\n" 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "metadata": {}, 75 | "source": [ 76 | "By default, *exh.formula* creates 3 propositions a,b and c with indices 0, 1, 2 respectively. Once some propositions are defined, one can create complex formulas with & (and), | (or) and \\\\~ (not).\n" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": 3, 82 | "metadata": { 83 | "execution": { 84 | "iopub.execute_input": "2021-01-15T08:36:43.221546Z", 85 | "iopub.status.busy": "2021-01-15T08:36:43.220261Z", 86 | "iopub.status.idle": "2021-01-15T08:36:43.228408Z", 87 | "shell.execute_reply": "2021-01-15T08:36:43.228917Z" 88 | } 89 | }, 90 | "outputs": [ 91 | { 92 | "data": { 93 | "text/html": [ 94 | "$a \\lor b$" 95 | ], 96 | "text/plain": [ 97 | "" 98 | ] 99 | }, 100 | "metadata": {}, 101 | "output_type": "display_data" 102 | }, 103 | { 104 | "data": { 105 | "text/html": [ 106 | "$a \\land b$" 107 | ], 108 | "text/plain": [ 109 | "" 110 | ] 111 | }, 112 | "metadata": {}, 113 | "output_type": "display_data" 114 | }, 115 | { 116 | "data": { 117 | "text/html": [ 118 | "$a \\lor (\\neg[a] \\land b)$" 119 | ], 120 | "text/plain": [ 121 | "" 122 | ] 123 | }, 124 | "metadata": {}, 125 | "output_type": "display_data" 126 | } 127 | ], 128 | "source": [ 129 | "# f1: a or b\n", 130 | "f1 = a | b\n", 131 | "jprint(f1)\n", 132 | "\n", 133 | "# f2: a and b\n", 134 | "f2 = a & b\n", 135 | "jprint(f2)\n", 136 | "\n", 137 | "# f3: a or (not a and b)\n", 138 | "f3 = a | (~a & b)\n", 139 | "jprint(f3)\n" 140 | ] 141 | }, 142 | { 143 | "cell_type": "markdown", 144 | "metadata": {}, 145 | "source": [ 146 | "To turn a proposition into a n-ary predicate (for use in quantified formulas), one needs to specify the number of variables it depends on. Optionally, one may indicate which variable or variables the predicate depends on by default. That way, we don't need to specify the dependencies in formulas\n" 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": 4, 152 | "metadata": { 153 | "execution": { 154 | "iopub.execute_input": "2021-01-15T08:36:43.232839Z", 155 | "iopub.status.busy": "2021-01-15T08:36:43.232315Z", 156 | "iopub.status.idle": "2021-01-15T08:36:43.234261Z", 157 | "shell.execute_reply": "2021-01-15T08:36:43.234785Z" 158 | } 159 | }, 160 | "outputs": [], 161 | "source": [ 162 | "d.depends(1) # Making a unary predicate\n", 163 | "\n", 164 | "# Alternatively, making a unary predicate with a default variable name\n", 165 | "d.depends(\"x\")\n", 166 | "# For predicates with more than one variable we would write:\n", 167 | "# d.depends(\"x\", \"y\") # Default named variables\n", 168 | "# d.depends(2) # Anonymous variables\n" 169 | ] 170 | }, 171 | { 172 | "cell_type": "markdown", 173 | "metadata": {}, 174 | "source": [ 175 | "Once this is done, one can use the following syntax to create a universal statement. A(\"x\") creates a quantifier over \"x\", which combines with \">\" and a formula to form a quantified formula.\n" 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": 5, 181 | "metadata": { 182 | "execution": { 183 | "iopub.execute_input": "2021-01-15T08:36:43.239102Z", 184 | "iopub.status.busy": "2021-01-15T08:36:43.238548Z", 185 | "iopub.status.idle": "2021-01-15T08:36:43.241480Z", 186 | "shell.execute_reply": "2021-01-15T08:36:43.241980Z" 187 | } 188 | }, 189 | "outputs": [ 190 | { 191 | "data": { 192 | "text/html": [ 193 | "$\\forall x, d(x)$" 194 | ], 195 | "text/plain": [ 196 | "" 197 | ] 198 | }, 199 | "metadata": {}, 200 | "output_type": "display_data" 201 | } 202 | ], 203 | "source": [ 204 | "# f4: for all x, d(x)\n", 205 | "f4 = A(\"x\") > d(\"x\")\n", 206 | "jprint(f4)\n", 207 | "# We don't have to mark explicitly the varaibles that d depends on, if \"d\" has default name for its variable\n", 208 | "f4 = A(\"x\") > d\n" 209 | ] 210 | }, 211 | { 212 | "cell_type": "markdown", 213 | "metadata": {}, 214 | "source": [ 215 | "Two simplifying tips:\n", 216 | " - Existential and universal quantifiers over x, y, z are created by default under the names Ax, Ey, etc.\n", 217 | " - If v is propositional variable, v(\"x\") returns v and sets v to depend on x\n", 218 | "\n", 219 | "Most straigthforwardly, one can create a quantified formula as follows:\n" 220 | ] 221 | }, 222 | { 223 | "cell_type": "code", 224 | "execution_count": 6, 225 | "metadata": { 226 | "execution": { 227 | "iopub.execute_input": "2021-01-15T08:36:43.251143Z", 228 | "iopub.status.busy": "2021-01-15T08:36:43.249585Z", 229 | "iopub.status.idle": "2021-01-15T08:36:43.255483Z", 230 | "shell.execute_reply": "2021-01-15T08:36:43.256168Z" 231 | } 232 | }, 233 | "outputs": [ 234 | { 235 | "name": "stdout", 236 | "output_type": "stream", 237 | "text": [ 238 | "WARNING: More variables were provided than the predicate None depends on ; changing the arity of the predicate to 1. Universe objects will need to be recreated.\n" 239 | ] 240 | }, 241 | { 242 | "data": { 243 | "text/html": [ 244 | "$\\exists y, A7(y)$" 245 | ], 246 | "text/plain": [ 247 | "" 248 | ] 249 | }, 250 | "metadata": {}, 251 | "output_type": "display_data" 252 | } 253 | ], 254 | "source": [ 255 | "# f5: there exists y, d(y)\n", 256 | "f5 = Ey > d1(\"y\") # a warning is displayed, because we hadn't specified that \"d1\" was a predicate\n", 257 | "jprint(f5) # displays as A7 because we haven't given d1 a name\n", 258 | "\n" 259 | ] 260 | }, 261 | { 262 | "cell_type": "markdown", 263 | "metadata": {}, 264 | "source": [ 265 | "## Evaluate formulas\n", 266 | "The simplest way to evaluate a formula uses the method *evaluate* and the predicates' names.\n" 267 | ] 268 | }, 269 | { 270 | "cell_type": "code", 271 | "execution_count": 7, 272 | "metadata": { 273 | "execution": { 274 | "iopub.execute_input": "2021-01-15T08:36:43.265437Z", 275 | "iopub.status.busy": "2021-01-15T08:36:43.263654Z", 276 | "iopub.status.idle": "2021-01-15T08:36:43.271117Z", 277 | "shell.execute_reply": "2021-01-15T08:36:43.270359Z" 278 | } 279 | }, 280 | "outputs": [ 281 | { 282 | "data": { 283 | "text/html": [ 284 | "'$a \\lor b$' is True" 285 | ], 286 | "text/plain": [ 287 | "" 288 | ] 289 | }, 290 | "metadata": {}, 291 | "output_type": "display_data" 292 | } 293 | ], 294 | "source": [ 295 | "# Evaluate f1 with a True and b True.\n", 296 | "value = f1.evaluate(a = True, b = False)\n", 297 | "jprint(\"'{}' is {}\".format(f1, value))\n" 298 | ] 299 | }, 300 | { 301 | "cell_type": "markdown", 302 | "metadata": {}, 303 | "source": [ 304 | "In quantified formulas, one needs to provide as many values for a predicate as there are individuals in the domain. The number of individuals in the domain is set in the module *exh.model.options* and defaults to 3. The three values are provided as a list.\n" 305 | ] 306 | }, 307 | { 308 | "cell_type": "code", 309 | "execution_count": 8, 310 | "metadata": { 311 | "execution": { 312 | "iopub.execute_input": "2021-01-15T08:36:43.278352Z", 313 | "iopub.status.busy": "2021-01-15T08:36:43.277754Z", 314 | "iopub.status.idle": "2021-01-15T08:36:43.285945Z", 315 | "shell.execute_reply": "2021-01-15T08:36:43.286596Z" 316 | } 317 | }, 318 | "outputs": [ 319 | { 320 | "data": { 321 | "text/html": [ 322 | "$\\forall x, d(x)$ is True" 323 | ], 324 | "text/plain": [ 325 | "" 326 | ] 327 | }, 328 | "metadata": {}, 329 | "output_type": "display_data" 330 | } 331 | ], 332 | "source": [ 333 | "# Evaluate f4 with d(0) True, d(1) True and d(2) True\n", 334 | "value = f4.evaluate(d = [True, True, True])\n", 335 | "jprint(f4, \" is \", value)\n" 336 | ] 337 | }, 338 | { 339 | "cell_type": "markdown", 340 | "metadata": {}, 341 | "source": [ 342 | "One may sometimes want to evaluate a formula against all possible assignments of truth-values. To do so, one constructs a *Universe* object.\n", 343 | "This object constructs all possible assignments of truth-values to propositions and predicates within a given set of formulas, passed as an argument to the universe constructor.\n", 344 | " The *truth_table* method displays the evaluated formula in a fancy chart.\n" 345 | ] 346 | }, 347 | { 348 | "cell_type": "code", 349 | "execution_count": 9, 350 | "metadata": { 351 | "execution": { 352 | "iopub.execute_input": "2021-01-15T08:36:43.295955Z", 353 | "iopub.status.busy": "2021-01-15T08:36:43.294128Z", 354 | "iopub.status.idle": "2021-01-15T08:36:43.300906Z", 355 | "shell.execute_reply": "2021-01-15T08:36:43.300370Z" 356 | } 357 | }, 358 | "outputs": [ 359 | { 360 | "name": "stdout", 361 | "output_type": "stream", 362 | "text": [ 363 | "[[False False False]\n", 364 | " [ True False True]\n", 365 | " [ True False True]\n", 366 | " [ True True True]\n", 367 | " [False False False]\n", 368 | " [ True False True]\n", 369 | " [ True False True]\n", 370 | " [ True True True]]\n" 371 | ] 372 | }, 373 | { 374 | "data": { 375 | "text/html": [ 376 | "
abc$a \\lor b$$a \\land b$$a \\lor (\\neg[a] \\land b)$
FalseFalseFalseFalseFalseFalse
TrueFalseFalseTrueFalseTrue
FalseTrueFalseTrueFalseTrue
TrueTrueFalseTrueTrueTrue
FalseFalseTrueFalseFalseFalse
TrueFalseTrueTrueFalseTrue
FalseTrueTrueTrueFalseTrue
TrueTrueTrueTrueTrueTrue
" 381 | ], 382 | "text/plain": [ 383 | "" 384 | ] 385 | }, 386 | "metadata": {}, 387 | "output_type": "display_data" 388 | } 389 | ], 390 | "source": [ 391 | "# A Universe can be created from formulas ; \n", 392 | "# the constructor extracts all the independent predicates and propositions and creates all possible logical possibilities\n", 393 | "prop_universe = Universe(fs = [a, b, c])\n", 394 | "\n", 395 | "print(prop_universe.evaluate(f1, f2, f3))\n", 396 | "prop_universe.truth_table(f1, f2, f3)\n" 397 | ] 398 | }, 399 | { 400 | "cell_type": "markdown", 401 | "metadata": {}, 402 | "source": [ 403 | "Universes come with methods to check standard logical relations: entailment, equivalence and consistency\n" 404 | ] 405 | }, 406 | { 407 | "cell_type": "code", 408 | "execution_count": 10, 409 | "metadata": { 410 | "execution": { 411 | "iopub.execute_input": "2021-01-15T08:36:43.308096Z", 412 | "iopub.status.busy": "2021-01-15T08:36:43.307542Z", 413 | "iopub.status.idle": "2021-01-15T08:36:43.310737Z", 414 | "shell.execute_reply": "2021-01-15T08:36:43.311617Z" 415 | } 416 | }, 417 | "outputs": [ 418 | { 419 | "name": "stdout", 420 | "output_type": "stream", 421 | "text": [ 422 | "False\n", 423 | "True\n", 424 | "True\n" 425 | ] 426 | } 427 | ], 428 | "source": [ 429 | "# The first three propositions entail that \"not a\" and \"b\", contradicting the fourth. \n", 430 | "value = prop_universe.consistent(a | b,\n", 431 | " ~a | ~b,\n", 432 | " b | ~a,\n", 433 | " a | ~b)\n", 434 | "print(value)\n", 435 | "\n", 436 | "value = prop_universe.entails(a | ~ b & c,\n", 437 | " ~(b & ~a) )\n", 438 | "# NB: \"a | b & c\" is parsed as \"a | (b & c)\"\n", 439 | "\n", 440 | "print(value)\n", 441 | "\n", 442 | "# De Morgan's law\n", 443 | "value = prop_universe.equivalent(~a | ~c,\n", 444 | " ~(a & c) )\n", 445 | "print(value)\n" 446 | ] 447 | }, 448 | { 449 | "cell_type": "markdown", 450 | "metadata": {}, 451 | "source": [ 452 | "## Exhaustification\n", 453 | "\n", 454 | "Exhaustification can be computed against a set of stipulated alternatives.\n", 455 | "\n", 456 | "**NB:** Innocent exclusion is computed upon creation of the object, slowdowns will happen at this stage if the number of worlds is large.\n" 457 | ] 458 | }, 459 | { 460 | "cell_type": "code", 461 | "execution_count": 11, 462 | "metadata": { 463 | "execution": { 464 | "iopub.execute_input": "2021-01-15T08:36:43.323734Z", 465 | "iopub.status.busy": "2021-01-15T08:36:43.322766Z", 466 | "iopub.status.idle": "2021-01-15T08:36:43.325280Z", 467 | "shell.execute_reply": "2021-01-15T08:36:43.325954Z" 468 | } 469 | }, 470 | "outputs": [], 471 | "source": [ 472 | "e = Exh(Ex > d, alts = [Ax > d]) # Number of worlds: 2^3 = 8 \n", 473 | "e1 = Exh(a | b, alts = [a, b, a & b]) # Number of worlds: 2^2 = 2\n" 474 | ] 475 | }, 476 | { 477 | "cell_type": "markdown", 478 | "metadata": {}, 479 | "source": [ 480 | "To see the results of the computations, you can use a couple of methods:\n", 481 | " 1. the method ``diagnose()`` from the Exhaust object lists the innocently excludable alternatives (along with the maximal sets).\n", 482 | " 2. alternatively, the method ``unpack()`` creates an equivalent formula in the form \"prejacent and not alternative and not alternative' ...\"\n", 483 | " 3. like any formula, we can evaluate *e* and check that it behaves like \"some but not all\", our predicted meaning.\n", 484 | "\n", 485 | "**Caveat:** Neither the formula given by ``unpack()`` or the sets of alternatives in ``diagnose()`` are not simplified.\n", 486 | "Sometimes, multiple logically equivalent alternatives will be displayed. On complex examples, comparing the result obtained to a predicted result may be more helpful (i.e. method 3 above).\n" 487 | ] 488 | }, 489 | { 490 | "cell_type": "code", 491 | "execution_count": 12, 492 | "metadata": { 493 | "execution": { 494 | "iopub.execute_input": "2021-01-15T08:36:43.337284Z", 495 | "iopub.status.busy": "2021-01-15T08:36:43.336083Z", 496 | "iopub.status.idle": "2021-01-15T08:36:43.361132Z", 497 | "shell.execute_reply": "2021-01-15T08:36:43.360602Z" 498 | } 499 | }, 500 | "outputs": [ 501 | { 502 | "data": { 503 | "text/html": [ 504 | "Maximal Sets (excl):" 505 | ], 506 | "text/plain": [ 507 | "" 508 | ] 509 | }, 510 | "metadata": {}, 511 | "output_type": "display_data" 512 | }, 513 | { 514 | "data": { 515 | "text/html": [ 516 | "{$\\forall x, d(x)$}" 517 | ], 518 | "text/plain": [ 519 | "" 520 | ] 521 | }, 522 | "metadata": {}, 523 | "output_type": "display_data" 524 | }, 525 | { 526 | "data": { 527 | "text/html": [], 528 | "text/plain": [ 529 | "" 530 | ] 531 | }, 532 | "metadata": {}, 533 | "output_type": "display_data" 534 | }, 535 | { 536 | "data": { 537 | "text/html": [ 538 | "Innocently excludable: $\\forall x, d(x)$" 539 | ], 540 | "text/plain": [ 541 | "" 542 | ] 543 | }, 544 | "metadata": {}, 545 | "output_type": "display_data" 546 | }, 547 | { 548 | "data": { 549 | "text/html": [], 550 | "text/plain": [ 551 | "" 552 | ] 553 | }, 554 | "metadata": {}, 555 | "output_type": "display_data" 556 | }, 557 | { 558 | "data": { 559 | "text/html": [ 560 | "Maximal Sets (excl):" 561 | ], 562 | "text/plain": [ 563 | "" 564 | ] 565 | }, 566 | "metadata": {}, 567 | "output_type": "display_data" 568 | }, 569 | { 570 | "data": { 571 | "text/html": [ 572 | "{$b$; $a \\land b$}" 573 | ], 574 | "text/plain": [ 575 | "" 576 | ] 577 | }, 578 | "metadata": {}, 579 | "output_type": "display_data" 580 | }, 581 | { 582 | "data": { 583 | "text/html": [ 584 | "{$a$; $a \\land b$}" 585 | ], 586 | "text/plain": [ 587 | "" 588 | ] 589 | }, 590 | "metadata": {}, 591 | "output_type": "display_data" 592 | }, 593 | { 594 | "data": { 595 | "text/html": [], 596 | "text/plain": [ 597 | "" 598 | ] 599 | }, 600 | "metadata": {}, 601 | "output_type": "display_data" 602 | }, 603 | { 604 | "data": { 605 | "text/html": [ 606 | "Innocently excludable: $a \\land b$" 607 | ], 608 | "text/plain": [ 609 | "" 610 | ] 611 | }, 612 | "metadata": {}, 613 | "output_type": "display_data" 614 | }, 615 | { 616 | "data": { 617 | "text/html": [], 618 | "text/plain": [ 619 | "" 620 | ] 621 | }, 622 | "metadata": {}, 623 | "output_type": "display_data" 624 | }, 625 | { 626 | "data": { 627 | "text/html": [ 628 | "$(\\exists x, d(x)) \\land \\land[\\neg[\\forall x, d(x)]]$" 629 | ], 630 | "text/plain": [ 631 | "" 632 | ] 633 | }, 634 | "metadata": {}, 635 | "output_type": "display_data" 636 | }, 637 | { 638 | "data": { 639 | "text/html": [ 640 | "$(a \\lor b) \\land \\land[\\neg[a \\land b]]$" 641 | ], 642 | "text/plain": [ 643 | "" 644 | ] 645 | }, 646 | "metadata": {}, 647 | "output_type": "display_data" 648 | }, 649 | { 650 | "name": "stdout", 651 | "output_type": "stream", 652 | "text": [ 653 | "\n" 654 | ] 655 | }, 656 | { 657 | "data": { 658 | "text/html": [ 659 | "
d(0)d(1)d(2)$\\textbf{Exh}[\\exists x, d(x)]$$(\\exists x, d(x)) \\land \\neg[\\forall x, d(x)]$
FalseFalseFalseFalseFalse
TrueFalseFalseTrueTrue
FalseTrueFalseTrueTrue
TrueTrueFalseTrueTrue
FalseFalseTrueTrueTrue
TrueFalseTrueTrueTrue
FalseTrueTrueTrueTrue
TrueTrueTrueFalseFalse
" 664 | ], 665 | "text/plain": [ 666 | "" 667 | ] 668 | }, 669 | "metadata": {}, 670 | "output_type": "display_data" 671 | }, 672 | { 673 | "name": "stdout", 674 | "output_type": "stream", 675 | "text": [ 676 | "\n", 677 | "Is e equivalent to 'some but not all'?\n", 678 | "True\n" 679 | ] 680 | } 681 | ], 682 | "source": [ 683 | "# Method 1\n", 684 | "e.diagnose()\n", 685 | "e1.diagnose()\n", 686 | "\n", 687 | "# Method 2\n", 688 | "jprint(e.unpack())\n", 689 | "jprint(e1.unpack())\n", 690 | "\n", 691 | "# Method 3\n", 692 | "quant_universe = Universe(fs = [e])\n", 693 | "print()\n", 694 | "# Display truth table of \"e\" and compare it to truth table of \"some but not all\"\n", 695 | "quant_universe.truth_table(e, (Ex > d) & ~(Ax > d))\n", 696 | "\n", 697 | "\n", 698 | "# Or more directly check for equivalence\n", 699 | "print()\n", 700 | "print(\"Is e equivalent to 'some but not all'?\")\n", 701 | "print(\n", 702 | " quant_universe.equivalent(\n", 703 | " e,\n", 704 | " (Ex > d) & ~(Ax > d)\n", 705 | " ))\n", 706 | "\n" 707 | ] 708 | }, 709 | { 710 | "cell_type": "markdown", 711 | "metadata": {}, 712 | "source": [ 713 | "Below is a more involved example with more alternatives:\n" 714 | ] 715 | }, 716 | { 717 | "cell_type": "code", 718 | "execution_count": 13, 719 | "metadata": { 720 | "execution": { 721 | "iopub.execute_input": "2021-01-15T08:36:43.371968Z", 722 | "iopub.status.busy": "2021-01-15T08:36:43.371110Z", 723 | "iopub.status.idle": "2021-01-15T08:36:43.400843Z", 724 | "shell.execute_reply": "2021-01-15T08:36:43.401348Z" 725 | } 726 | }, 727 | "outputs": [ 728 | { 729 | "data": { 730 | "text/html": [ 731 | "Maximal Sets (excl):" 732 | ], 733 | "text/plain": [ 734 | "" 735 | ] 736 | }, 737 | "metadata": {}, 738 | "output_type": "display_data" 739 | }, 740 | { 741 | "data": { 742 | "text/html": [ 743 | "{$\\forall x, p1(x) \\land p2(x)$; $\\forall x, p2(x)$; $\\exists x, p1(x) \\land p2(x)$; $\\exists x, p2(x)$}" 744 | ], 745 | "text/plain": [ 746 | "" 747 | ] 748 | }, 749 | "metadata": {}, 750 | "output_type": "display_data" 751 | }, 752 | { 753 | "data": { 754 | "text/html": [ 755 | "{$\\forall x, p1(x) \\land p2(x)$; $\\forall x, p1(x)$; $\\forall x, p2(x)$; $\\exists x, p1(x) \\land p2(x)$}" 756 | ], 757 | "text/plain": [ 758 | "" 759 | ] 760 | }, 761 | "metadata": {}, 762 | "output_type": "display_data" 763 | }, 764 | { 765 | "data": { 766 | "text/html": [ 767 | "{$\\forall x, p1(x) \\land p2(x)$; $\\forall x, p1(x)$; $\\exists x, p1(x) \\land p2(x)$; $\\exists x, p1(x)$}" 768 | ], 769 | "text/plain": [ 770 | "" 771 | ] 772 | }, 773 | "metadata": {}, 774 | "output_type": "display_data" 775 | }, 776 | { 777 | "data": { 778 | "text/html": [], 779 | "text/plain": [ 780 | "" 781 | ] 782 | }, 783 | "metadata": {}, 784 | "output_type": "display_data" 785 | }, 786 | { 787 | "data": { 788 | "text/html": [ 789 | "Innocently excludable: $\\forall x, p1(x) \\land p2(x)$; $\\exists x, p1(x) \\land p2(x)$" 790 | ], 791 | "text/plain": [ 792 | "" 793 | ] 794 | }, 795 | "metadata": {}, 796 | "output_type": "display_data" 797 | }, 798 | { 799 | "data": { 800 | "text/html": [], 801 | "text/plain": [ 802 | "" 803 | ] 804 | }, 805 | "metadata": {}, 806 | "output_type": "display_data" 807 | }, 808 | { 809 | "data": { 810 | "text/html": [ 811 | "Maximal Sets (excl):" 812 | ], 813 | "text/plain": [ 814 | "" 815 | ] 816 | }, 817 | "metadata": {}, 818 | "output_type": "display_data" 819 | }, 820 | { 821 | "data": { 822 | "text/html": [ 823 | "{$\\forall x, p1(x) \\land p2(x)$; $\\forall x, p1(x)$; $\\forall x, p2(x)$}" 824 | ], 825 | "text/plain": [ 826 | "" 827 | ] 828 | }, 829 | "metadata": {}, 830 | "output_type": "display_data" 831 | }, 832 | { 833 | "data": { 834 | "text/html": [], 835 | "text/plain": [ 836 | "" 837 | ] 838 | }, 839 | "metadata": {}, 840 | "output_type": "display_data" 841 | }, 842 | { 843 | "data": { 844 | "text/html": [ 845 | "Innocently excludable: $\\forall x, p1(x) \\land p2(x)$; $\\forall x, p1(x)$; $\\forall x, p2(x)$" 846 | ], 847 | "text/plain": [ 848 | "" 849 | ] 850 | }, 851 | "metadata": {}, 852 | "output_type": "display_data" 853 | }, 854 | { 855 | "data": { 856 | "text/html": [], 857 | "text/plain": [ 858 | "" 859 | ] 860 | }, 861 | "metadata": {}, 862 | "output_type": "display_data" 863 | }, 864 | { 865 | "name": "stdout", 866 | "output_type": "stream", 867 | "text": [ 868 | "True\n", 869 | "True\n" 870 | ] 871 | } 872 | ], 873 | "source": [ 874 | "# constructing new predicates and immediately indicating dependency in x\n", 875 | "p1 = Pred(name = \"p1\", depends = [\"x\"]) \n", 876 | "p2 = Pred(name = \"p2\", depends = [\"x\"])\n", 877 | "\n", 878 | "prejacent = Ax > p1 | p2\n", 879 | "\n", 880 | "exh = Exh(prejacent, alts = [Ax > p1 & p2,\n", 881 | " Ax > p1,\n", 882 | " Ax > p2,\n", 883 | " Ex > p1 & p2,\n", 884 | " Ex > p1,\n", 885 | " Ex > p2])\n", 886 | "exh.diagnose()\n", 887 | "# Reads like \"none of them did both p1 and p2\" ; an embedded implicature\n", 888 | "# What if we didn't have existential alternatives?\n", 889 | "\n", 890 | "exh2 = Exh(prejacent, alts = [Ax > p1 & p2,\n", 891 | " Ax > p1,\n", 892 | " Ax > p2])\n", 893 | "exh2.diagnose()\n", 894 | "universe = Universe(fs = [exh2])\n", 895 | "print(universe.entails(exh2, Ex > p1 & ~p2))\n", 896 | "print(universe.entails(exh2, Ex > p2 & ~p1))\n", 897 | "# Two implicatures: 1) that someone did only p1, 2) that someone did only p2\n" 898 | ] 899 | }, 900 | { 901 | "cell_type": "markdown", 902 | "metadata": {}, 903 | "source": [ 904 | "### Automatic alternatives\n", 905 | "\n", 906 | "When not specified, the alternatives to the prejacent are computed in a Sauerlandian manner: all alternatives are considered that can be obtained from the prejacent by sub-constituent and scalar substitutions. Which alternatives were obtained by this process can be probed after the object is constructed.\n" 907 | ] 908 | }, 909 | { 910 | "cell_type": "code", 911 | "execution_count": 14, 912 | "metadata": { 913 | "execution": { 914 | "iopub.execute_input": "2021-01-15T08:36:43.408767Z", 915 | "iopub.status.busy": "2021-01-15T08:36:43.408054Z", 916 | "iopub.status.idle": "2021-01-15T08:36:43.411095Z", 917 | "shell.execute_reply": "2021-01-15T08:36:43.411627Z" 918 | } 919 | }, 920 | "outputs": [ 921 | { 922 | "data": { 923 | "text/html": [ 924 | "Computed alternatives [$a \\lor b \\lor c$, $(a \\land b) \\lor c$, $a \\lor c$, $b \\lor c$, $(a \\lor b) \\land c$, $a \\land b \\land c$, $a \\land c$, $b \\land c$, $a \\lor b$, $a \\land b$, $a$, $b$, $c$]" 925 | ], 926 | "text/plain": [ 927 | "" 928 | ] 929 | }, 930 | "metadata": {}, 931 | "output_type": "display_data" 932 | } 933 | ], 934 | "source": [ 935 | "h2 = Exh (a | b | c)\n", 936 | "jprint(\"Computed alternatives\", h2.alts)\n" 937 | ] 938 | }, 939 | { 940 | "cell_type": "markdown", 941 | "metadata": {}, 942 | "source": [ 943 | "It is possible to specify the scales and to decide whether to allow substitution by sub-consituent.\n" 944 | ] 945 | }, 946 | { 947 | "cell_type": "code", 948 | "execution_count": 15, 949 | "metadata": { 950 | "execution": { 951 | "iopub.execute_input": "2021-01-15T08:36:43.418394Z", 952 | "iopub.status.busy": "2021-01-15T08:36:43.417855Z", 953 | "iopub.status.idle": "2021-01-15T08:36:43.424409Z", 954 | "shell.execute_reply": "2021-01-15T08:36:43.424932Z" 955 | } 956 | }, 957 | "outputs": [ 958 | { 959 | "data": { 960 | "text/html": [ 961 | "[$a \\lor b \\lor c$, $(a \\land b) \\lor c$, $(a \\lor b) \\land c$, $a \\land b \\land c$]" 962 | ], 963 | "text/plain": [ 964 | "" 965 | ] 966 | }, 967 | "metadata": {}, 968 | "output_type": "display_data" 969 | }, 970 | { 971 | "data": { 972 | "text/html": [ 973 | "[$\\exists x, p1(x) \\lor p2(x)$, $\\exists x, p1(x) \\land p2(x)$, $\\exists x, p1(x)$, $\\exists x, p2(x)$]" 974 | ], 975 | "text/plain": [ 976 | "" 977 | ] 978 | }, 979 | "metadata": {}, 980 | "output_type": "display_data" 981 | } 982 | ], 983 | "source": [ 984 | "h3 = Exh(a | b | c, subst = False) # no replacement by sub-constituent allowed (only scalar alternatives)\n", 985 | "jprint(h3.alts)\n", 986 | "\n", 987 | "h4 = Exh(Ex > p1 | p2, scales = [{Or, And}]) # no \"some\", \"all\" scale\n", 988 | "jprint(h4.alts)\n", 989 | "# NB: to avoid unbound variables, the quantifier's scope is not considered a sub-consituent of the quantifier\n", 990 | "# NB2: The \"scales\" argument is a list of sets of types. You can find out the type of any formula by running:\n", 991 | "# print(formula_of_unknown_type.__class__.__name__) \n" 992 | ] 993 | }, 994 | { 995 | "cell_type": "code", 996 | "execution_count": 16, 997 | "metadata": { 998 | "execution": { 999 | "iopub.execute_input": "2021-01-15T08:36:43.428962Z", 1000 | "iopub.status.busy": "2021-01-15T08:36:43.428480Z", 1001 | "iopub.status.idle": "2021-01-15T08:36:43.445212Z", 1002 | "shell.execute_reply": "2021-01-15T08:36:43.445719Z" 1003 | } 1004 | }, 1005 | "outputs": [ 1006 | { 1007 | "data": { 1008 | "text/html": [ 1009 | "Maximal Sets (excl):" 1010 | ], 1011 | "text/plain": [ 1012 | "" 1013 | ] 1014 | }, 1015 | "metadata": {}, 1016 | "output_type": "display_data" 1017 | }, 1018 | { 1019 | "data": { 1020 | "text/html": [ 1021 | "{$(a \\land b) \\lor c$; $(a \\lor b) \\land c$; $a \\land b \\land c$}" 1022 | ], 1023 | "text/plain": [ 1024 | "" 1025 | ] 1026 | }, 1027 | "metadata": {}, 1028 | "output_type": "display_data" 1029 | }, 1030 | { 1031 | "data": { 1032 | "text/html": [], 1033 | "text/plain": [ 1034 | "" 1035 | ] 1036 | }, 1037 | "metadata": {}, 1038 | "output_type": "display_data" 1039 | }, 1040 | { 1041 | "data": { 1042 | "text/html": [ 1043 | "Innocently excludable: $(a \\land b) \\lor c$; $(a \\lor b) \\land c$; $a \\land b \\land c$" 1044 | ], 1045 | "text/plain": [ 1046 | "" 1047 | ] 1048 | }, 1049 | "metadata": {}, 1050 | "output_type": "display_data" 1051 | }, 1052 | { 1053 | "data": { 1054 | "text/html": [], 1055 | "text/plain": [ 1056 | "" 1057 | ] 1058 | }, 1059 | "metadata": {}, 1060 | "output_type": "display_data" 1061 | }, 1062 | { 1063 | "data": { 1064 | "text/html": [ 1065 | "Maximal Sets (excl):" 1066 | ], 1067 | "text/plain": [ 1068 | "" 1069 | ] 1070 | }, 1071 | "metadata": {}, 1072 | "output_type": "display_data" 1073 | }, 1074 | { 1075 | "data": { 1076 | "text/html": [ 1077 | "{$\\exists x, p1(x) \\land p2(x)$; $\\exists x, p2(x)$}" 1078 | ], 1079 | "text/plain": [ 1080 | "" 1081 | ] 1082 | }, 1083 | "metadata": {}, 1084 | "output_type": "display_data" 1085 | }, 1086 | { 1087 | "data": { 1088 | "text/html": [ 1089 | "{$\\exists x, p1(x) \\land p2(x)$; $\\exists x, p1(x)$}" 1090 | ], 1091 | "text/plain": [ 1092 | "" 1093 | ] 1094 | }, 1095 | "metadata": {}, 1096 | "output_type": "display_data" 1097 | }, 1098 | { 1099 | "data": { 1100 | "text/html": [], 1101 | "text/plain": [ 1102 | "" 1103 | ] 1104 | }, 1105 | "metadata": {}, 1106 | "output_type": "display_data" 1107 | }, 1108 | { 1109 | "data": { 1110 | "text/html": [ 1111 | "Innocently excludable: $\\exists x, p1(x) \\land p2(x)$" 1112 | ], 1113 | "text/plain": [ 1114 | "" 1115 | ] 1116 | }, 1117 | "metadata": {}, 1118 | "output_type": "display_data" 1119 | }, 1120 | { 1121 | "data": { 1122 | "text/html": [], 1123 | "text/plain": [ 1124 | "" 1125 | ] 1126 | }, 1127 | "metadata": {}, 1128 | "output_type": "display_data" 1129 | } 1130 | ], 1131 | "source": [ 1132 | "\n", 1133 | "\n", 1134 | "\n", 1135 | "h3.diagnose()\n", 1136 | "h4.diagnose()\n" 1137 | ] 1138 | }, 1139 | { 1140 | "cell_type": "markdown", 1141 | "metadata": {}, 1142 | "source": [ 1143 | "## Advanced usage\n", 1144 | "\n", 1145 | "### Formulas with multiple quantifiers\n", 1146 | "\n", 1147 | "One can create multiply quantified sentences ; the number of worlds grows exponentially. One predicate that depends on two variables will give rise to 9 independent variables ; we get 2^9 = 512 worlds.\n" 1148 | ] 1149 | }, 1150 | { 1151 | "cell_type": "code", 1152 | "execution_count": 17, 1153 | "metadata": { 1154 | "execution": { 1155 | "iopub.execute_input": "2021-01-15T08:36:43.451798Z", 1156 | "iopub.status.busy": "2021-01-15T08:36:43.451278Z", 1157 | "iopub.status.idle": "2021-01-15T08:36:43.463593Z", 1158 | "shell.execute_reply": "2021-01-15T08:36:43.464101Z" 1159 | } 1160 | }, 1161 | "outputs": [ 1162 | { 1163 | "name": "stdout", 1164 | "output_type": "stream", 1165 | "text": [ 1166 | "WARNING: More variables were provided than the predicate p3 depends on ; changing the arity of the predicate to 2. Universe objects will need to be recreated.\n" 1167 | ] 1168 | }, 1169 | { 1170 | "data": { 1171 | "text/html": [ 1172 | "Maximal Sets (excl):" 1173 | ], 1174 | "text/plain": [ 1175 | "" 1176 | ] 1177 | }, 1178 | "metadata": {}, 1179 | "output_type": "display_data" 1180 | }, 1181 | { 1182 | "data": { 1183 | "text/html": [ 1184 | "{$\\forall x, \\exists y, p3(x,y)$; $\\exists y, \\forall x, p3(x,y)$}" 1185 | ], 1186 | "text/plain": [ 1187 | "" 1188 | ] 1189 | }, 1190 | "metadata": {}, 1191 | "output_type": "display_data" 1192 | }, 1193 | { 1194 | "data": { 1195 | "text/html": [], 1196 | "text/plain": [ 1197 | "" 1198 | ] 1199 | }, 1200 | "metadata": {}, 1201 | "output_type": "display_data" 1202 | }, 1203 | { 1204 | "data": { 1205 | "text/html": [ 1206 | "Innocently excludable: $\\forall x, \\exists y, p3(x,y)$; $\\exists y, \\forall x, p3(x,y)$" 1207 | ], 1208 | "text/plain": [ 1209 | "" 1210 | ] 1211 | }, 1212 | "metadata": {}, 1213 | "output_type": "display_data" 1214 | }, 1215 | { 1216 | "data": { 1217 | "text/html": [], 1218 | "text/plain": [ 1219 | "" 1220 | ] 1221 | }, 1222 | "metadata": {}, 1223 | "output_type": "display_data" 1224 | } 1225 | ], 1226 | "source": [ 1227 | "p3 = Pred(13, name = \"p3\")\n", 1228 | "prejacent = Ex > Ay > p3(\"x\", \"y\") # Number of worlds 2^(3^2) = 512 (still quite reasonable)\n", 1229 | "\n", 1230 | "e = Exh(prejacent, alts = [Ay > Ex > p3, Ax > Ey > p3, Ey > Ax > p3])\n", 1231 | "e.diagnose()\n" 1232 | ] 1233 | }, 1234 | { 1235 | "cell_type": "markdown", 1236 | "metadata": {}, 1237 | "source": [ 1238 | "### Recursive exhaustification\n", 1239 | "\n", 1240 | "The object *Exh* is just like any other formula. It can be embedded, yielding recursive exhaustification. Here is for instance a replication of free choice:\n" 1241 | ] 1242 | }, 1243 | { 1244 | "cell_type": "code", 1245 | "execution_count": 18, 1246 | "metadata": { 1247 | "execution": { 1248 | "iopub.execute_input": "2021-01-15T08:36:43.485339Z", 1249 | "iopub.status.busy": "2021-01-15T08:36:43.477147Z", 1250 | "iopub.status.idle": "2021-01-15T08:36:43.546633Z", 1251 | "shell.execute_reply": "2021-01-15T08:36:43.547167Z" 1252 | } 1253 | }, 1254 | "outputs": [ 1255 | { 1256 | "name": "stdout", 1257 | "output_type": "stream", 1258 | "text": [ 1259 | "Alternatives:\n" 1260 | ] 1261 | }, 1262 | { 1263 | "data": { 1264 | "text/html": [ 1265 | "
  • $\\textbf{Exh}[\\exists x, p1(x) \\lor p2(x)]$
  • $\\textbf{Exh}[\\exists x, p1(x)]$
  • $\\textbf{Exh}[\\exists x, p2(x)]$
" 1266 | ], 1267 | "text/plain": [ 1268 | "" 1269 | ] 1270 | }, 1271 | "metadata": {}, 1272 | "output_type": "display_data" 1273 | }, 1274 | { 1275 | "data": { 1276 | "text/html": [ 1277 | "Maximal Sets (excl):" 1278 | ], 1279 | "text/plain": [ 1280 | "" 1281 | ] 1282 | }, 1283 | "metadata": {}, 1284 | "output_type": "display_data" 1285 | }, 1286 | { 1287 | "data": { 1288 | "text/html": [ 1289 | "{$\\textbf{Exh}[\\exists x, p1(x)]$; $\\textbf{Exh}[\\exists x, p2(x)]$}" 1290 | ], 1291 | "text/plain": [ 1292 | "" 1293 | ] 1294 | }, 1295 | "metadata": {}, 1296 | "output_type": "display_data" 1297 | }, 1298 | { 1299 | "data": { 1300 | "text/html": [], 1301 | "text/plain": [ 1302 | "" 1303 | ] 1304 | }, 1305 | "metadata": {}, 1306 | "output_type": "display_data" 1307 | }, 1308 | { 1309 | "data": { 1310 | "text/html": [ 1311 | "Innocently excludable: $\\textbf{Exh}[\\exists x, p1(x)]$; $\\textbf{Exh}[\\exists x, p2(x)]$" 1312 | ], 1313 | "text/plain": [ 1314 | "" 1315 | ] 1316 | }, 1317 | "metadata": {}, 1318 | "output_type": "display_data" 1319 | }, 1320 | { 1321 | "data": { 1322 | "text/html": [], 1323 | "text/plain": [ 1324 | "" 1325 | ] 1326 | }, 1327 | "metadata": {}, 1328 | "output_type": "display_data" 1329 | }, 1330 | { 1331 | "name": "stdout", 1332 | "output_type": "stream", 1333 | "text": [ 1334 | "Am I allowed to do p1?\n", 1335 | "True" 1336 | ] 1337 | }, 1338 | { 1339 | "name": "stdout", 1340 | "output_type": "stream", 1341 | "text": [ 1342 | "\n", 1343 | "Am I allowed to do p2?\n", 1344 | "True\n", 1345 | "Does the sentence say that I can do p1 without doing p2?\n", 1346 | "False\n", 1347 | "Is the sentence compatible with a requirement to do both p1 and p2?\n", 1348 | "True\n" 1349 | ] 1350 | }, 1351 | { 1352 | "data": { 1353 | "text/html": [ 1354 | "
  • $\\textbf{Exh}[\\exists x, p1(x) \\lor p2(x)]$
  • $\\textbf{Exh}[\\exists x, p1(x)]$
  • $\\textbf{Exh}[\\exists x, p2(x)]$
  • $\\textbf{Exh}[\\forall x, p1(x) \\lor p2(x)]$
  • $\\textbf{Exh}[\\forall x, p1(x)]$
  • $\\textbf{Exh}[\\forall x, p2(x)]$
" 1355 | ], 1356 | "text/plain": [ 1357 | "" 1358 | ] 1359 | }, 1360 | "metadata": {}, 1361 | "output_type": "display_data" 1362 | }, 1363 | { 1364 | "data": { 1365 | "text/html": [ 1366 | "Maximal Sets (excl):" 1367 | ], 1368 | "text/plain": [ 1369 | "" 1370 | ] 1371 | }, 1372 | "metadata": {}, 1373 | "output_type": "display_data" 1374 | }, 1375 | { 1376 | "data": { 1377 | "text/html": [ 1378 | "{$\\textbf{Exh}[\\exists x, p1(x)]$; $\\textbf{Exh}[\\exists x, p2(x)]$; $\\textbf{Exh}[\\forall x, p1(x) \\lor p2(x)]$; $\\textbf{Exh}[\\forall x, p1(x)]$; $\\textbf{Exh}[\\forall x, p2(x)]$}" 1379 | ], 1380 | "text/plain": [ 1381 | "" 1382 | ] 1383 | }, 1384 | "metadata": {}, 1385 | "output_type": "display_data" 1386 | }, 1387 | { 1388 | "data": { 1389 | "text/html": [], 1390 | "text/plain": [ 1391 | "" 1392 | ] 1393 | }, 1394 | "metadata": {}, 1395 | "output_type": "display_data" 1396 | }, 1397 | { 1398 | "data": { 1399 | "text/html": [ 1400 | "Innocently excludable: $\\textbf{Exh}[\\exists x, p1(x)]$; $\\textbf{Exh}[\\exists x, p2(x)]$; $\\textbf{Exh}[\\forall x, p1(x) \\lor p2(x)]$; $\\textbf{Exh}[\\forall x, p1(x)]$; $\\textbf{Exh}[\\forall x, p2(x)]$" 1401 | ], 1402 | "text/plain": [ 1403 | "" 1404 | ] 1405 | }, 1406 | "metadata": {}, 1407 | "output_type": "display_data" 1408 | }, 1409 | { 1410 | "data": { 1411 | "text/html": [], 1412 | "text/plain": [ 1413 | "" 1414 | ] 1415 | }, 1416 | "metadata": {}, 1417 | "output_type": "display_data" 1418 | }, 1419 | { 1420 | "name": "stdout", 1421 | "output_type": "stream", 1422 | "text": [ 1423 | "Does the sentence say that I can do p1 without doing p2?\n", 1424 | "False\n", 1425 | "Is the sentence compatible with a requirement to do both p1 and p2?\n", 1426 | "False\n" 1427 | ] 1428 | } 1429 | ], 1430 | "source": [ 1431 | "# For fancy html display\n", 1432 | "from IPython.core.display import display, HTML\n", 1433 | "\n", 1434 | "prejacent = Ex > p1 | p2 # The existential quantifier can be thought of as an existential modal\n", 1435 | "\n", 1436 | "free_choice = Exh(Exh(prejacent, scales = []), scales = []) \n", 1437 | "# no scalar alternatives for the moment\n", 1438 | "\n", 1439 | "# Let's see what has been computed\n", 1440 | "# First, the alternatives\n", 1441 | "print(\"Alternatives:\")\n", 1442 | "\n", 1443 | "to_display = \"
    \"\n", 1444 | "for alt in free_choice.alts:\n", 1445 | " to_display += \"
  • {}
  • \".format(alt)\n", 1446 | "to_display += \"
\"\n", 1447 | "display(HTML(to_display))\n", 1448 | "\n", 1449 | "# Second, the innocently excludable alternatives\n", 1450 | "free_choice.diagnose()\n", 1451 | "# The result seems right ; let's check entailments\n", 1452 | "\n", 1453 | "fc_universe = Universe(f = free_choice) # one can use f you only have one formula\n", 1454 | "print(\"Am I allowed to do p1?\")\n", 1455 | "print(fc_universe.entails(free_choice, Ex > p1))\n", 1456 | "print(\"Am I allowed to do p2?\")\n", 1457 | "print(fc_universe.entails(free_choice, Ex > p2))\n", 1458 | "\n", 1459 | "# We have weak FC ; what about strong free-choice?\n", 1460 | "print(\"Does the sentence say that I can do p1 without doing p2?\")\n", 1461 | "print(fc_universe.entails(free_choice, Ex > p1 & ~p2)) # We don't have strong FC\n", 1462 | "print(\"Is the sentence compatible with a requirement to do both p1 and p2?\")\n", 1463 | "print(fc_universe.consistent(free_choice, Ax > p1 & p2))\n", 1464 | "\n", 1465 | "# Can we derive strong FC with universal scalar alternatives?\n", 1466 | "someall = [{Existential, Universal}] # we only allow some/all scale, not or/and scale\n", 1467 | "fc_2 = Exh(Exh(prejacent, scales = someall), scales = someall) \n", 1468 | "\n", 1469 | "to_display = \"
    \"\n", 1470 | "for alt in fc_2.alts:\n", 1471 | " to_display += \"
  • {}
  • \".format(alt)\n", 1472 | "to_display += \"
\"\n", 1473 | "display(HTML(to_display))\n", 1474 | "\n", 1475 | "fc_2.diagnose()\n", 1476 | "\n", 1477 | "print(\"Does the sentence say that I can do p1 without doing p2?\")\n", 1478 | "print(fc_universe.entails(fc_2, Ex > p1 & ~p2)) # We don't have strong FC\n", 1479 | "print(\"Is the sentence compatible with a requirement to do both p1 and p2?\")\n", 1480 | "print(fc_universe.consistent(fc_2, Ax > p1 & p2))\n" 1481 | ] 1482 | }, 1483 | { 1484 | "cell_type": "markdown", 1485 | "metadata": {}, 1486 | "source": [ 1487 | "One may wonder why by sub-constituent replacement, \"Ex, p1(x)\" is not an alternative to \"Exh(Ex, p1(x) or p2(x))\"\n", 1488 | "If this were so, note that free choice wouldn't be derived.\n", 1489 | "\"exh\" is set to not be removable by sub-constituent replacement (you can modify this in *options.py*)\n", 1490 | "\n", 1491 | "\n", 1492 | "### Innocent inclusion\n", 1493 | "\n", 1494 | "*Exh* can also compute innocent inclusion. \n" 1495 | ] 1496 | }, 1497 | { 1498 | "cell_type": "code", 1499 | "execution_count": 19, 1500 | "metadata": { 1501 | "execution": { 1502 | "iopub.execute_input": "2021-01-15T08:36:43.555034Z", 1503 | "iopub.status.busy": "2021-01-15T08:36:43.553996Z", 1504 | "iopub.status.idle": "2021-01-15T08:36:43.604597Z", 1505 | "shell.execute_reply": "2021-01-15T08:36:43.605114Z" 1506 | } 1507 | }, 1508 | "outputs": [ 1509 | { 1510 | "name": "stdout", 1511 | "output_type": "stream", 1512 | "text": [ 1513 | "### DISJUNCTION ###\n" 1514 | ] 1515 | }, 1516 | { 1517 | "data": { 1518 | "text/html": [ 1519 | "Maximal Sets (excl):" 1520 | ], 1521 | "text/plain": [ 1522 | "" 1523 | ] 1524 | }, 1525 | "metadata": {}, 1526 | "output_type": "display_data" 1527 | }, 1528 | { 1529 | "data": { 1530 | "text/html": [ 1531 | "{$b$}" 1532 | ], 1533 | "text/plain": [ 1534 | "" 1535 | ] 1536 | }, 1537 | "metadata": {}, 1538 | "output_type": "display_data" 1539 | }, 1540 | { 1541 | "data": { 1542 | "text/html": [ 1543 | "{$a$}" 1544 | ], 1545 | "text/plain": [ 1546 | "" 1547 | ] 1548 | }, 1549 | "metadata": {}, 1550 | "output_type": "display_data" 1551 | }, 1552 | { 1553 | "data": { 1554 | "text/html": [], 1555 | "text/plain": [ 1556 | "" 1557 | ] 1558 | }, 1559 | "metadata": {}, 1560 | "output_type": "display_data" 1561 | }, 1562 | { 1563 | "data": { 1564 | "text/html": [ 1565 | "Innocently excludable: nothing" 1566 | ], 1567 | "text/plain": [ 1568 | "" 1569 | ] 1570 | }, 1571 | "metadata": {}, 1572 | "output_type": "display_data" 1573 | }, 1574 | { 1575 | "data": { 1576 | "text/html": [], 1577 | "text/plain": [ 1578 | "" 1579 | ] 1580 | }, 1581 | "metadata": {}, 1582 | "output_type": "display_data" 1583 | }, 1584 | { 1585 | "data": { 1586 | "text/html": [], 1587 | "text/plain": [ 1588 | "" 1589 | ] 1590 | }, 1591 | "metadata": {}, 1592 | "output_type": "display_data" 1593 | }, 1594 | { 1595 | "data": { 1596 | "text/html": [ 1597 | "Maximal Sets (incl):" 1598 | ], 1599 | "text/plain": [ 1600 | "" 1601 | ] 1602 | }, 1603 | "metadata": {}, 1604 | "output_type": "display_data" 1605 | }, 1606 | { 1607 | "data": { 1608 | "text/html": [ 1609 | "{$a \\lor b$; $a$; $b$}" 1610 | ], 1611 | "text/plain": [ 1612 | "" 1613 | ] 1614 | }, 1615 | "metadata": {}, 1616 | "output_type": "display_data" 1617 | }, 1618 | { 1619 | "data": { 1620 | "text/html": [], 1621 | "text/plain": [ 1622 | "" 1623 | ] 1624 | }, 1625 | "metadata": {}, 1626 | "output_type": "display_data" 1627 | }, 1628 | { 1629 | "data": { 1630 | "text/html": [ 1631 | "Innocently includable: $a \\lor b$; $a$; $b$" 1632 | ], 1633 | "text/plain": [ 1634 | "" 1635 | ] 1636 | }, 1637 | "metadata": {}, 1638 | "output_type": "display_data" 1639 | }, 1640 | { 1641 | "data": { 1642 | "text/html": [], 1643 | "text/plain": [ 1644 | "" 1645 | ] 1646 | }, 1647 | "metadata": {}, 1648 | "output_type": "display_data" 1649 | }, 1650 | { 1651 | "name": "stdout", 1652 | "output_type": "stream", 1653 | "text": [ 1654 | "### FREE CHOICE ###\n" 1655 | ] 1656 | }, 1657 | { 1658 | "data": { 1659 | "text/html": [ 1660 | "Maximal Sets (excl):" 1661 | ], 1662 | "text/plain": [ 1663 | "" 1664 | ] 1665 | }, 1666 | "metadata": {}, 1667 | "output_type": "display_data" 1668 | }, 1669 | { 1670 | "data": { 1671 | "text/html": [ 1672 | "{$\\exists x, p1(x) \\land p2(x)$; $\\exists x, p2(x)$; $\\forall x, p1(x) \\lor p2(x)$; $\\forall x, p1(x) \\land p2(x)$; $\\forall x, p1(x)$; $\\forall x, p2(x)$}" 1673 | ], 1674 | "text/plain": [ 1675 | "" 1676 | ] 1677 | }, 1678 | "metadata": {}, 1679 | "output_type": "display_data" 1680 | }, 1681 | { 1682 | "data": { 1683 | "text/html": [ 1684 | "{$\\exists x, p1(x) \\land p2(x)$; $\\exists x, p1(x)$; $\\forall x, p1(x) \\lor p2(x)$; $\\forall x, p1(x) \\land p2(x)$; $\\forall x, p1(x)$; $\\forall x, p2(x)$}" 1685 | ], 1686 | "text/plain": [ 1687 | "" 1688 | ] 1689 | }, 1690 | "metadata": {}, 1691 | "output_type": "display_data" 1692 | }, 1693 | { 1694 | "data": { 1695 | "text/html": [], 1696 | "text/plain": [ 1697 | "" 1698 | ] 1699 | }, 1700 | "metadata": {}, 1701 | "output_type": "display_data" 1702 | }, 1703 | { 1704 | "data": { 1705 | "text/html": [ 1706 | "Innocently excludable: $\\exists x, p1(x) \\land p2(x)$; $\\forall x, p1(x) \\lor p2(x)$; $\\forall x, p1(x) \\land p2(x)$; $\\forall x, p1(x)$; $\\forall x, p2(x)$" 1707 | ], 1708 | "text/plain": [ 1709 | "" 1710 | ] 1711 | }, 1712 | "metadata": {}, 1713 | "output_type": "display_data" 1714 | }, 1715 | { 1716 | "data": { 1717 | "text/html": [], 1718 | "text/plain": [ 1719 | "" 1720 | ] 1721 | }, 1722 | "metadata": {}, 1723 | "output_type": "display_data" 1724 | }, 1725 | { 1726 | "data": { 1727 | "text/html": [], 1728 | "text/plain": [ 1729 | "" 1730 | ] 1731 | }, 1732 | "metadata": {}, 1733 | "output_type": "display_data" 1734 | }, 1735 | { 1736 | "data": { 1737 | "text/html": [ 1738 | "Maximal Sets (incl):" 1739 | ], 1740 | "text/plain": [ 1741 | "" 1742 | ] 1743 | }, 1744 | "metadata": {}, 1745 | "output_type": "display_data" 1746 | }, 1747 | { 1748 | "data": { 1749 | "text/html": [ 1750 | "{$\\exists x, p1(x) \\lor p2(x)$; $\\exists x, p1(x)$; $\\exists x, p2(x)$}" 1751 | ], 1752 | "text/plain": [ 1753 | "" 1754 | ] 1755 | }, 1756 | "metadata": {}, 1757 | "output_type": "display_data" 1758 | }, 1759 | { 1760 | "data": { 1761 | "text/html": [], 1762 | "text/plain": [ 1763 | "" 1764 | ] 1765 | }, 1766 | "metadata": {}, 1767 | "output_type": "display_data" 1768 | }, 1769 | { 1770 | "data": { 1771 | "text/html": [ 1772 | "Innocently includable: $\\exists x, p1(x) \\lor p2(x)$; $\\exists x, p1(x)$; $\\exists x, p2(x)$" 1773 | ], 1774 | "text/plain": [ 1775 | "" 1776 | ] 1777 | }, 1778 | "metadata": {}, 1779 | "output_type": "display_data" 1780 | }, 1781 | { 1782 | "data": { 1783 | "text/html": [], 1784 | "text/plain": [ 1785 | "" 1786 | ] 1787 | }, 1788 | "metadata": {}, 1789 | "output_type": "display_data" 1790 | }, 1791 | { 1792 | "name": "stdout", 1793 | "output_type": "stream", 1794 | "text": [ 1795 | "Allowed to do p1 not p2: True\n", 1796 | "Allowed to do p2 not p1: True\n", 1797 | "Allowed to do both: False\n" 1798 | ] 1799 | } 1800 | ], 1801 | "source": [ 1802 | "# Strengthening to conjunction, when scalar alternatives are absent\n", 1803 | "print(\"### DISJUNCTION ###\")\n", 1804 | "conj = Exh(a | b, scales = [], ii = True) # ii parameter for innocent inclusion\n", 1805 | "conj.diagnose()\n", 1806 | "\n", 1807 | "print(\"### FREE CHOICE ###\")\n", 1808 | "fc_ii = Exh(Ex > p1 | p2, ii = True) # Automatic alternatives\n", 1809 | "fc_ii.diagnose()\n", 1810 | "\n", 1811 | "print(\"Allowed to do p1 not p2:\", fc_universe.entails(fc_ii, Ex > p1 & ~p2))\n", 1812 | "print(\"Allowed to do p2 not p1:\", fc_universe.entails(fc_ii, Ex > p2 & ~p1))\n", 1813 | "print(\"Allowed to do both:\", fc_universe.consistent(fc_ii, Ex > p2 & p1))\n", 1814 | "\n", 1815 | "\n", 1816 | "\n", 1817 | "\n", 1818 | "\n", 1819 | "\n" 1820 | ] 1821 | } 1822 | ], 1823 | "metadata": { 1824 | "anaconda-cloud": {}, 1825 | "kernelspec": { 1826 | "display_name": "Python 3", 1827 | "language": "python", 1828 | "name": "python3" 1829 | }, 1830 | "language_info": { 1831 | "codemirror_mode": { 1832 | "name": "ipython", 1833 | "version": 3 1834 | }, 1835 | "file_extension": ".py", 1836 | "mimetype": "text/x-python", 1837 | "name": "python", 1838 | "nbconvert_exporter": "python", 1839 | "pygments_lexer": "ipython3", 1840 | "version": "3.6.11" 1841 | } 1842 | }, 1843 | "nbformat": 4, 1844 | "nbformat_minor": 4 1845 | } 1846 | -------------------------------------------------------------------------------- /examples/tutorial/Tutorial.py: -------------------------------------------------------------------------------- 1 | # %% 2 | """ 3 | # Tutorial 4 | 5 | ## Imports 6 | """ 7 | 8 | 9 | 10 | from exh import * 11 | from exh.utils import jprint # for fancy displays in Jupyter Notebook 12 | import exh # This import is just to access the version 13 | print(exh.__version__) 14 | 15 | # %% 16 | """ 17 | ## Create formulas 18 | 19 | Formulas are created from propositions (0-ary predictates) and predicates. 20 | Both of these are created by class ``Pred``. 21 | Predicates have names, which govern how they are displayed. 22 | Predicates have indices : two predicates with the same index always have the same truth-value and two predicates with different indices are logically independent. Here is a code to create a predicate with index 4. 23 | """ 24 | 25 | d = Pred(index = 4, name = "d") 26 | # both "name" and "index" are optional, "name" makes prettier display with print and helps for evaluation of formulas 27 | d1 = Pred(index = 7) 28 | d2 = Pred(name = "d2") 29 | 30 | 31 | 32 | # %% 33 | """ 34 | By default, *exh.formula* creates 3 propositions a,b and c with indices 0, 1, 2 respectively. Once some propositions are defined, one can create complex formulas with & (and), | (or) and \\~ (not). 35 | """ 36 | 37 | # f1: a or b 38 | f1 = a | b 39 | jprint(f1) 40 | 41 | # f2: a and b 42 | f2 = a & b 43 | jprint(f2) 44 | 45 | # f3: a or (not a and b) 46 | f3 = a | (~a & b) 47 | jprint(f3) 48 | 49 | # %% 50 | """ 51 | To turn a proposition into a n-ary predicate (for use in quantified formulas), one needs to specify the number of variables it depends on. Optionally, one may indicate which variable or variables the predicate depends on by default. That way, we don't need to specify the dependencies in formulas 52 | """ 53 | 54 | 55 | d.depends(1) # Making a unary predicate 56 | 57 | # Alternatively, making a unary predicate with a default variable name 58 | d.depends("x") 59 | # For predicates with more than one variable we would write: 60 | # d.depends("x", "y") # Default named variables 61 | # d.depends(2) # Anonymous variables 62 | 63 | # %% 64 | """ 65 | Once this is done, one can use the following syntax to create a universal statement. A("x") creates a quantifier over "x", which combines with ">" and a formula to form a quantified formula. 66 | """ 67 | 68 | # f4: for all x, d(x) 69 | f4 = A("x") > d("x") 70 | jprint(f4) 71 | # We don't have to mark explicitly the varaibles that d depends on, if "d" has default name for its variable 72 | f4 = A("x") > d 73 | 74 | # %% 75 | """ 76 | Two simplifying tips: 77 | - Existential and universal quantifiers over x, y, z are created by default under the names Ax, Ey, etc. 78 | - If v is propositional variable, v("x") returns v and sets v to depend on x 79 | 80 | Most straigthforwardly, one can create a quantified formula as follows: 81 | """ 82 | 83 | 84 | # f5: there exists y, d(y) 85 | f5 = Ey > d1("y") # a warning is displayed, because we hadn't specified that "d1" was a predicate 86 | jprint(f5) # displays as A7 because we haven't given d1 a name 87 | 88 | 89 | # %% 90 | """ 91 | ## Evaluate formulas 92 | The simplest way to evaluate a formula uses the method *evaluate* and the predicates' names. 93 | """ 94 | 95 | # Evaluate f1 with a True and b True. 96 | value = f1.evaluate(a = True, b = False) 97 | jprint("'{}' is {}".format(f1, value)) 98 | 99 | # %% 100 | """ 101 | In quantified formulas, one needs to provide as many values for a predicate as there are individuals in the domain. The number of individuals in the domain is set in the module *exh.model.options* and defaults to 3. The three values are provided as a list. 102 | """ 103 | 104 | # Evaluate f4 with d(0) True, d(1) True and d(2) True 105 | value = f4.evaluate(d = [True, True, True]) 106 | jprint(f4, " is ", value) 107 | 108 | # %% 109 | """ 110 | One may sometimes want to evaluate a formula against all possible assignments of truth-values. To do so, one constructs a *Universe* object. 111 | This object constructs all possible assignments of truth-values to propositions and predicates within a given set of formulas, passed as an argument to the universe constructor. 112 | The *truth_table* method displays the evaluated formula in a fancy chart. 113 | """ 114 | 115 | # A Universe can be created from formulas ; 116 | # the constructor extracts all the independent predicates and propositions and creates all possible logical possibilities 117 | prop_universe = Universe(fs = [a, b, c]) 118 | 119 | print(prop_universe.evaluate(f1, f2, f3)) 120 | prop_universe.truth_table(f1, f2, f3) 121 | 122 | # %% 123 | """ 124 | Universes come with methods to check standard logical relations: entailment, equivalence and consistency 125 | """ 126 | 127 | # The first three propositions entail that "not a" and "b", contradicting the fourth. 128 | value = prop_universe.consistent(a | b, 129 | ~a | ~b, 130 | b | ~a, 131 | a | ~b) 132 | print(value) 133 | 134 | value = prop_universe.entails(a | ~ b & c, 135 | ~(b & ~a) ) 136 | # NB: "a | b & c" is parsed as "a | (b & c)" 137 | 138 | print(value) 139 | 140 | # De Morgan's law 141 | value = prop_universe.equivalent(~a | ~c, 142 | ~(a & c) ) 143 | print(value) 144 | 145 | # %% 146 | """ 147 | ## Exhaustification 148 | 149 | Exhaustification can be computed against a set of stipulated alternatives. 150 | 151 | **NB:** Innocent exclusion is computed upon creation of the object, slowdowns will happen at this stage if the number of worlds is large. 152 | """ 153 | 154 | 155 | e = Exh(Ex > d, alts = [Ax > d]) # Number of worlds: 2^3 = 8 156 | e1 = Exh(a | b, alts = [a, b, a & b]) # Number of worlds: 2^2 = 2 157 | 158 | # %% 159 | """ 160 | To see the results of the computations, you can use a couple of methods: 161 | 1. the method ``diagnose()`` from the Exhaust object lists the innocently excludable alternatives (along with the maximal sets). 162 | 2. alternatively, the method ``unpack()`` creates an equivalent formula in the form "prejacent and not alternative and not alternative' ..." 163 | 3. like any formula, we can evaluate *e* and check that it behaves like "some but not all", our predicted meaning. 164 | 165 | **Caveat:** Neither the formula given by ``unpack()`` or the sets of alternatives in ``diagnose()`` are not simplified. 166 | Sometimes, multiple logically equivalent alternatives will be displayed. On complex examples, comparing the result obtained to a predicted result may be more helpful (i.e. method 3 above). 167 | """ 168 | 169 | # Method 1 170 | e.diagnose() 171 | e1.diagnose() 172 | 173 | # Method 2 174 | jprint(e.unpack()) 175 | jprint(e1.unpack()) 176 | 177 | # Method 3 178 | quant_universe = Universe(fs = [e]) 179 | print() 180 | # Display truth table of "e" and compare it to truth table of "some but not all" 181 | quant_universe.truth_table(e, (Ex > d) & ~(Ax > d)) 182 | 183 | 184 | # Or more directly check for equivalence 185 | print() 186 | print("Is e equivalent to 'some but not all'?") 187 | print( 188 | quant_universe.equivalent( 189 | e, 190 | (Ex > d) & ~(Ax > d) 191 | )) 192 | 193 | 194 | # %% 195 | """ 196 | Below is a more involved example with more alternatives: 197 | """ 198 | 199 | 200 | # constructing new predicates and immediately indicating dependency in x 201 | p1 = Pred(name = "p1", depends = ["x"]) 202 | p2 = Pred(name = "p2", depends = ["x"]) 203 | 204 | prejacent = Ax > p1 | p2 205 | 206 | exh = Exh(prejacent, alts = [Ax > p1 & p2, 207 | Ax > p1, 208 | Ax > p2, 209 | Ex > p1 & p2, 210 | Ex > p1, 211 | Ex > p2]) 212 | exh.diagnose() 213 | # Reads like "none of them did both p1 and p2" ; an embedded implicature 214 | # What if we didn't have existential alternatives? 215 | 216 | exh2 = Exh(prejacent, alts = [Ax > p1 & p2, 217 | Ax > p1, 218 | Ax > p2]) 219 | exh2.diagnose() 220 | universe = Universe(fs = [exh2]) 221 | print(universe.entails(exh2, Ex > p1 & ~p2)) 222 | print(universe.entails(exh2, Ex > p2 & ~p1)) 223 | # Two implicatures: 1) that someone did only p1, 2) that someone did only p2 224 | 225 | # %% 226 | """ 227 | ### Automatic alternatives 228 | 229 | When not specified, the alternatives to the prejacent are computed in a Sauerlandian manner: all alternatives are considered that can be obtained from the prejacent by sub-constituent and scalar substitutions. Which alternatives were obtained by this process can be probed after the object is constructed. 230 | """ 231 | 232 | 233 | h2 = Exh (a | b | c) 234 | jprint("Computed alternatives", h2.alts) 235 | 236 | # %% 237 | """ 238 | It is possible to specify the scales and to decide whether to allow substitution by sub-consituent. 239 | """ 240 | 241 | 242 | h3 = Exh(a | b | c, subst = False) # no replacement by sub-constituent allowed (only scalar alternatives) 243 | jprint(h3.alts) 244 | 245 | h4 = Exh(Ex > p1 | p2, scales = [{Or, And}]) # no "some", "all" scale 246 | jprint(h4.alts) 247 | # NB: to avoid unbound variables, the quantifier's scope is not considered a sub-consituent of the quantifier 248 | # NB2: The "scales" argument is a list of sets of types. You can find out the type of any formula by running: 249 | # print(formula_of_unknown_type.__class__.__name__) 250 | 251 | # %% 252 | 253 | 254 | h3.diagnose() 255 | h4.diagnose() 256 | 257 | # %% 258 | """ 259 | ## Advanced usage 260 | 261 | ### Formulas with multiple quantifiers 262 | 263 | One can create multiply quantified sentences ; the number of worlds grows exponentially. One predicate that depends on two variables will give rise to 9 independent variables ; we get 2^9 = 512 worlds. 264 | """ 265 | 266 | 267 | p3 = Pred(13, name = "p3") 268 | prejacent = Ex > Ay > p3("x", "y") # Number of worlds 2^(3^2) = 512 (still quite reasonable) 269 | 270 | e = Exh(prejacent, alts = [Ay > Ex > p3, Ax > Ey > p3, Ey > Ax > p3]) 271 | e.diagnose() 272 | 273 | # %% 274 | """ 275 | ### Recursive exhaustification 276 | 277 | The object *Exh* is just like any other formula. It can be embedded, yielding recursive exhaustification. Here is for instance a replication of free choice: 278 | """ 279 | 280 | 281 | # For fancy html display 282 | from IPython.core.display import display, HTML 283 | 284 | prejacent = Ex > p1 | p2 # The existential quantifier can be thought of as an existential modal 285 | 286 | free_choice = Exh(Exh(prejacent, scales = []), scales = []) 287 | # no scalar alternatives for the moment 288 | 289 | # Let's see what has been computed 290 | # First, the alternatives 291 | print("Alternatives:") 292 | 293 | to_display = "
    " 294 | for alt in free_choice.alts: 295 | to_display += "
  • {}
  • ".format(alt) 296 | to_display += "
" 297 | display(HTML(to_display)) 298 | 299 | # Second, the innocently excludable alternatives 300 | free_choice.diagnose() 301 | # The result seems right ; let's check entailments 302 | 303 | fc_universe = Universe(f = free_choice) # one can use f you only have one formula 304 | print("Am I allowed to do p1?") 305 | print(fc_universe.entails(free_choice, Ex > p1)) 306 | print("Am I allowed to do p2?") 307 | print(fc_universe.entails(free_choice, Ex > p2)) 308 | 309 | # We have weak FC ; what about strong free-choice? 310 | print("Does the sentence say that I can do p1 without doing p2?") 311 | print(fc_universe.entails(free_choice, Ex > p1 & ~p2)) # We don't have strong FC 312 | print("Is the sentence compatible with a requirement to do both p1 and p2?") 313 | print(fc_universe.consistent(free_choice, Ax > p1 & p2)) 314 | 315 | # Can we derive strong FC with universal scalar alternatives? 316 | someall = [{Existential, Universal}] # we only allow some/all scale, not or/and scale 317 | fc_2 = Exh(Exh(prejacent, scales = someall), scales = someall) 318 | 319 | to_display = "
    " 320 | for alt in fc_2.alts: 321 | to_display += "
  • {}
  • ".format(alt) 322 | to_display += "
" 323 | display(HTML(to_display)) 324 | 325 | fc_2.diagnose() 326 | 327 | print("Does the sentence say that I can do p1 without doing p2?") 328 | print(fc_universe.entails(fc_2, Ex > p1 & ~p2)) # We don't have strong FC 329 | print("Is the sentence compatible with a requirement to do both p1 and p2?") 330 | print(fc_universe.consistent(fc_2, Ax > p1 & p2)) 331 | 332 | # %% 333 | """ 334 | One may wonder why by sub-constituent replacement, "Ex, p1(x)" is not an alternative to "Exh(Ex, p1(x) or p2(x))" 335 | If this were so, note that free choice wouldn't be derived. 336 | "exh" is set to not be removable by sub-constituent replacement (you can modify this in *options.py*) 337 | 338 | 339 | ### Innocent inclusion 340 | 341 | *Exh* can also compute innocent inclusion. 342 | """ 343 | 344 | 345 | # Strengthening to conjunction, when scalar alternatives are absent 346 | print("### DISJUNCTION ###") 347 | conj = Exh(a | b, scales = [], ii = True) # ii parameter for innocent inclusion 348 | conj.diagnose() 349 | 350 | print("### FREE CHOICE ###") 351 | fc_ii = Exh(Ex > p1 | p2, ii = True) # Automatic alternatives 352 | fc_ii.diagnose() 353 | 354 | print("Allowed to do p1 not p2:", fc_universe.entails(fc_ii, Ex > p1 & ~p2)) 355 | print("Allowed to do p2 not p1:", fc_universe.entails(fc_ii, Ex > p2 & ~p1)) 356 | print("Allowed to do both:", fc_universe.consistent(fc_ii, Ex > p2 & p1)) 357 | 358 | 359 | 360 | 361 | 362 | 363 | -------------------------------------------------------------------------------- /examples/tutorial/runnb.py: -------------------------------------------------------------------------------- 1 | ../../utils/runnb.py -------------------------------------------------------------------------------- /exh/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.1.2" 2 | 3 | from .prop import * 4 | from .model import * 5 | from .fol import * 6 | from .exhaust import * 7 | from .scales import * 8 | # from .worlds import * 9 | 10 | # Defining the names that are exported with from exh import * 11 | # __all__ = ["function1", "function2"] 12 | -------------------------------------------------------------------------------- /exh/alternatives.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module offers a collection of tools to work with alternatives: 3 | compute alternatives of a formula, find maximal consistent sets, etc. 4 | """ 5 | import numpy as np 6 | from itertools import product 7 | import copy 8 | 9 | from . import exhaust 10 | import exh.options as options 11 | from exh.prop import Pred, Or, And 12 | from exh.utils import entails, remove_doubles 13 | 14 | 15 | 16 | 17 | 18 | 19 | ############################ MAXIMAL SETS ############################################## 20 | 21 | 22 | def find_maximal_sets(universe, props, variables = None): 23 | """ 24 | Given a set of worlds and a set of propositions, this method returns the maximal sets of propositions that are consistent with one another 25 | 26 | Algorithm: 27 | focus on the sets S of proposition which are all the propositions true in some world 28 | return the maximal sets of S 29 | 30 | Arguments: 31 | universe (Universe) 32 | props (list[Formula]) -- set of propositions to compute the maximal sets of 33 | 34 | Returns: 35 | np.array[bool] -- returned_value[i, j] is True iff i-th maximal set contains j-th proposition 36 | 37 | """ 38 | kwargs = {} if variables is None else {"variables" : variables} 39 | truth_table = universe.evaluate(*props, no_flattening = True, **kwargs) 40 | maximal_sets = [] 41 | 42 | # for every world, 43 | for s in truth_table: 44 | 45 | # test if the set of true proposition in that world is smaller than any of the current maximal sets 46 | # if yes, go on to the next world 47 | # if no, remove any smaller set from maximal set and insert 48 | if any(entails(s, m) for m in maximal_sets): 49 | continue 50 | else: 51 | maximal_sets = [m for m in maximal_sets if not entails(m, s)] 52 | maximal_sets.append(s) 53 | 54 | return np.stack(maximal_sets) #if maximal_sets else np.full((0, 0), True, dtype = "bool") 55 | 56 | 57 | 58 | 59 | ############################ ALTERNATIVE GENERATION ############################################## 60 | """ 61 | *alts_aux* performs the automatic generation of alternatives 62 | *alts* adds on top of this simplification of the set of alternatives using braindead heuristics 63 | """ 64 | 65 | 66 | 67 | 68 | # @profile 69 | def alt_aux(p, scales, subst): 70 | """ 71 | Return alternatives to a formula following a Sauerland-esque algorithm. 72 | Specifically, an alternative is anything which can be obtained from the prejacent by sub-constituent replacement ("A" is an alternative to "A or B"), scale replacement ("a or b" is an alternative to "a and b") 73 | 74 | Arguments: 75 | p (Formula) -- prejacent 76 | scales (list[tuple[class]])-- the list of scales in the lexicon 77 | subst (bool) -- whether to take subconstituent alternatives 78 | 79 | Returns: 80 | list[Formula] -- the alternatives 81 | 82 | """ 83 | 84 | # A predicate has no alternatives 85 | if isinstance(p, Pred): 86 | return [p] 87 | 88 | # in case the prejacent is an Exh, its alternative are either stipulated or already computed as "p.alts" 89 | # So the returned set of alternatives is just {Exh(alt) | alt \in p.alts} (+alt themselved if subst is active) 90 | if isinstance(p, exhaust.Exh): 91 | all_alternatives = [p.prejacent] 92 | all_alternatives.extend(p.alts) 93 | 94 | 95 | exh_alternatives = [p] 96 | exh_alternatives.extend( 97 | [exhaust.Exh(alt, alts = all_alternatives[:i] + all_alternatives[i + 1:]) 98 | for i, alt in enumerate(all_alternatives) if i != 0 # <--- trick: we don't recompute exhaustification of the prejacent 99 | ] 100 | ) 101 | 102 | # Somehow, sub-constituent alternative must not be disallowed to derive FC -- something to investigate 103 | if subst and options.prejacent_alternative_to_exh: 104 | exh_alternatives.extend(p.alts) 105 | 106 | # exh_alternatives = [exhaust.Exh(alt, alts = all_alternatives[:i] + all_alternatives[i + 1:]) 107 | # for i, alt in enumerate(all_alternatives)] 108 | return exh_alternatives 109 | 110 | # GENERAL CASE: 111 | 112 | 113 | 114 | # Recursively obtain the alternatives of the children nodes of the prejacent 115 | children_alternative = [alt_aux(child, scales, subst) for child in p.children] 116 | 117 | 118 | root_fixed_alts = [] # alternatives which have the same root operator as the prejacent (ie. a | (b & c) as an alternative to a | (b | c), preserves root or) 119 | # For every choice of an alternative to a child (C1 x C2 x ... x Cn where Ci are the children's alternatives) 120 | for t in product(*children_alternative): 121 | # To avoid problems, the prejacent is copied 122 | to_append = copy.copy(p) 123 | to_append.children = t 124 | 125 | # Because exhaust will need to perform computation at initialization, we need to reinitialize. (<= Not sure if this is necessary given that the case Exh is already dealt with) 126 | to_append.reinitialize()#constructors[p.type](to_append) 127 | root_fixed_alts.append(to_append) 128 | # "root_fixed_alts" now contains all alternatives to the current formula that keep the root node the same 129 | # e.g. p = a | (b & c) => root_fixed_alts = [a | b, a | c, a | (b | c), a | (b & c)] 130 | 131 | 132 | scalar_alts = scales.alternatives_to(root_fixed_alts) 133 | # # Find the scalemates of the prejacent 134 | # rel_scale = set(type_f for s in scales if any(isinstance(p, type_f) for type_f in s) 135 | # for type_f in s if not isinstance(p, type_f)) 136 | 137 | # # we now need to include scalar replacements 138 | # scalar_alts = [] 139 | # for scale_mate in rel_scale: 140 | # for alt_root_fixed in root_fixed_alts: 141 | # scalar_alts.append(scale_mate.alternative_to(alt_root_fixed)) 142 | 143 | 144 | 145 | if subst and p.subst: # if sub-constituent are alternatives, add every element of child_alts 146 | return root_fixed_alts + scalar_alts + [alt for child_alts in children_alternative for alt in child_alts] 147 | else: 148 | return root_fixed_alts + scalar_alts 149 | 150 | # @profile 151 | def alt(p, scales = [], subst = False): 152 | """ 153 | Simplifies the result of alt_aux for efficiency 154 | 155 | 1) Simplify trivial alternatives: A or A -> A, B and B -> B 156 | 2) Remove duplicate alternatives: {A, B, B, A or B} -> {A, B, A or B} 157 | """ 158 | return remove_doubles(simplify_alts(alt_aux(p, scales, subst))) 159 | # return alt_aux(p, scales, subst) 160 | 161 | 162 | 163 | 164 | def simplify_alt(alt): 165 | """Performs simple heuristics to simplify a formula, namely "A or A" is "A" ; "A and A" is "A" """ 166 | if isinstance(alt, Or) or isinstance(alt, And): 167 | if len(alt.children) == 2 and alt.children[0] == alt.children[1]: 168 | return alt.children[0] 169 | return alt 170 | 171 | def simplify_alts(alts): 172 | """Applies "simplify_alt" to a list""" 173 | return list(map(simplify_alt, alts)) 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /exh/exhaust.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | # This is a circular import ; better import it at runtime 4 | import exh.alternatives as alternatives 5 | import exh.model as model 6 | import exh.prop as prop 7 | import exh.scales as scale 8 | 9 | from exh.utils import jprint 10 | import exh.options as options 11 | 12 | 13 | 14 | class Exhaust: 15 | """ 16 | This class orchestrates the computation of exhaustification. 17 | It computes the alternatives, if they are not provided ; it performs IE and II on them 18 | 19 | Attributes: 20 | p (Formula) -- the prejacent 21 | alts (list[Formula]) -- the alternatives 22 | incl (bool) -- whether IE exhaustification has been computed yet 23 | incl (bool) -- whether II exhaustification has been computed yet 24 | vm (VariableManager) -- variable manager for prejacent & alternatives 25 | u (Universe) -- universe with all corresponding logical possibilities 26 | """ 27 | 28 | 29 | def __init__(self, prejacent, alts = None, scales = None, subst = None, extra_alts = []): 30 | """ 31 | Arguments 32 | - prejacent (Formula) -- the prejacent 33 | - alts (list[Formula]) -- stipulated alternatives. If None, alternatives are computed automatically 34 | - scales -- the scales used to compute automatic alternatives 35 | - subst (bool) -- whether subconstituent alternatives should be used 36 | - extra_alts(list[Formula]) -- if alternatives are computed automatically, add to the already computed alternatives some stipulated ones. 37 | """ 38 | # Defining default options dynamically so that users can change options on the fly 39 | if scales is None: 40 | scales = options.scales 41 | elif isinstance(scales, list): 42 | scales = scale.SimpleScales(scales) 43 | self.scales = scales 44 | 45 | if subst is None: 46 | subst = options.sub 47 | 48 | if alts is None: # if no alternative is given, compute them automatically 49 | self.alts = alternatives.alt(prejacent, scales = scales, subst = subst) 50 | else: 51 | self.alts = alts 52 | self.alts += extra_alts 53 | 54 | self.p = prejacent 55 | 56 | # If there are free variables in the prejacent or the alternatives, they must be saturated with dummy values 57 | self.free_vars = set(self.p.free_vars) 58 | for alt in self.alts: 59 | self.free_vars.union(set(alt.free_vars)) 60 | 61 | self.dummy_vals = {var: 0 for var in self.free_vars} 62 | 63 | self.incl = False 64 | self.excl = False 65 | 66 | self.vm = model.VarManager.merge(prejacent.vm, *(alt.vm for alt in self.alts)) 67 | self.u = model.Universe(vm = self.vm) 68 | 69 | 70 | def innocently_excludable(self): 71 | 72 | evalSet = [~f for f in self.alts] 73 | worldsPrejacent = np.squeeze(self.u.evaluate(self.p, no_flattening = True, 74 | variables = self.dummy_vals), # give free variables dummy values 75 | axis = 1) 76 | # We restrict ourselves to the worlds where the prejacent is True 77 | uPrejacent = self.u.restrict(worldsPrejacent) 78 | 79 | if evalSet and uPrejacent.n_worlds != 0: 80 | self.maximalExclSets = alternatives.find_maximal_sets(uPrejacent, evalSet, variables = self.dummy_vals) 81 | self.innocently_excl_indices = np.prod(self.maximalExclSets, axis = 0, dtype = "bool") # innocently_excl_indices[i] is true iff the i-th proposition belongs to every maximal set 82 | else: 83 | self.maximalExclSets = [] 84 | self.innocently_excl_indices = [] 85 | 86 | self.excl = True 87 | return self.innocently_excl_indices 88 | 89 | def innocently_includable(self): 90 | 91 | if not self.excl: 92 | raise ValueError("Exclusion has not been applied yet.") 93 | 94 | evalNegSet = [~f for f, excludable in zip(self.alts, self.innocently_excl_indices) if excludable] + [self.p] 95 | evalPosSet = [ f for f, excludable in zip(self.alts, self.innocently_excl_indices) if not excludable] 96 | 97 | worldsStengthenedPrejacent = np.prod(self.u.evaluate(*evalNegSet, no_flattening = True, 98 | variables = self.dummy_vals), 99 | axis = 1, 100 | dtype = "bool") 101 | 102 | # Restricting ourselves to the worlds where the prejacent is true and the negatable alternatives are false. 103 | uSPrejacent = self.u.restrict(worldsStengthenedPrejacent) 104 | 105 | if evalPosSet and uSPrejacent.n_worlds != 0: 106 | maximalSets = alternatives.find_maximal_sets(uSPrejacent, evalPosSet, variables = self.dummy_vals) 107 | 108 | # The maximal sets refer to positions in the set of non-excludable alternatives ; we must convert this to position in the whole set of alternatives 109 | self.maximalInclSets = np.full((len(maximalSets), len(self.alts)), False, dtype = "bool") 110 | self.maximalInclSets[:, np.logical_not(self.innocently_excl_indices)] = maximalSets 111 | 112 | self.innocently_incl_indices = np.prod(self.maximalInclSets, axis = 0, dtype = "bool") 113 | else: 114 | self.maximalInclSets = [] 115 | self.innocently_incl_indices = [] 116 | 117 | self.incl = True 118 | return self.innocently_incl_indices 119 | 120 | def diagnose(self, display = jprint): 121 | """Diplay pertinent information regarding the results of the computation such as maximal sets, IE alternatives, II alternatives""" 122 | inline_sep = "; " 123 | list_sep = "\n - " 124 | 125 | def sep_fs(fs): 126 | str_fs = [str(f) for f in fs] 127 | n_fs = len(str_fs) 128 | 129 | if n_fs > options.cutoff_inline_to_list: 130 | return list_sep + list_sep.join(str_fs) 131 | elif n_fs > 0: 132 | return inline_sep.join(str_fs) 133 | else: 134 | return "nothing" 135 | 136 | if self.excl: 137 | display("Maximal Sets (excl):") 138 | for excl in self.maximalExclSets: 139 | display("{" + inline_sep.join(list(map(str, self.extract_alts(excl)))) + "}") 140 | display() 141 | display("Innocently excludable: ", sep_fs(self.innocently_excl)) 142 | 143 | if self.incl: 144 | display() 145 | display() 146 | display("Maximal Sets (incl):") 147 | for incl in self.maximalInclSets: 148 | display("{" + inline_sep.join(list(map(str, self.extract_alts(incl)))) + "}") 149 | display() 150 | display("Innocently includable: ", sep_fs(self.innocently_incl)) 151 | display() 152 | 153 | @property 154 | def innocently_excl(self): 155 | return self.extract_alts(self.innocently_excl_indices) 156 | 157 | @property 158 | def innocently_incl(self): 159 | return self.extract_alts(self.innocently_incl_indices) 160 | 161 | 162 | def extract_alts(self, mask): 163 | return [alt for alt, included in zip(self.alts, mask) if included] 164 | 165 | class Exh(prop.Operator): 166 | """ 167 | This class wraps the class Exhaust into a Formula object, so that it can be evaluated like any Formula object 168 | 169 | Attributes: 170 | e (Exhaust) -- the object Exhaust that performs the actual computation 171 | """ 172 | 173 | plain_symbol = "Exh" 174 | latex_symbol = r"\textbf{Exh}" 175 | 176 | substitutable = False 177 | 178 | def __init__(self, child, alts = None, scales = None, subst = None, ii = None, extra_alts = []): 179 | self.e = Exhaust(child, alts, scales, subst, extra_alts) 180 | super(Exh, self).__init__(None, child) 181 | 182 | if ii is None: 183 | self.ii = options.ii_on 184 | else: 185 | self.ii = ii 186 | self.reinitialize() 187 | 188 | def reinitialize(self): 189 | self.e.p = self.prejacent 190 | self.ieSet = self.e.innocently_excludable() 191 | 192 | if self.ii: 193 | self.iiSet = self.e.innocently_includable() 194 | else: 195 | self.iiSet = [] 196 | 197 | 198 | self.evalSet = [~f for f, excludable in zip(self.alts, self.ieSet) if excludable] + [f for f, includable in zip(self.alts, self.iiSet) if includable] 199 | 200 | 201 | 202 | def evaluate_aux(self, assignment, vm, variables = dict(), free_vars = list()): 203 | 204 | evaluanda = [self.children[0]] + self.evalSet 205 | values = [f.evaluate_aux(assignment, vm, variables) for f in evaluanda] 206 | 207 | return np.min(np.stack(values), axis = 0) 208 | 209 | def unpack(self): 210 | return self.prejacent & prop.And(*self.evalSet) 211 | 212 | @property 213 | def alts(self): 214 | return self.e.alts 215 | 216 | 217 | def vars(self): 218 | self.vm = model.VarManager.merge(self.children[0].vm, *[alt.vm for alt in self.alts]) 219 | return self.vm 220 | 221 | def diagnose(self, *args, **kwargs): 222 | self.e.diagnose(*args, **kwargs) 223 | 224 | def __eq__(self, other): 225 | return isinstance(other, Exh) and (self.children[0] == other.children[0]) 226 | 227 | @property 228 | def prejacent(self): 229 | return self.children[0] 230 | 231 | 232 | def copy(self): 233 | return Exh(self.prejacent, alts = self.alts) 234 | 235 | @classmethod 236 | def alternative_to(cls, other): 237 | return cls(other.children[0], alts = other.alts) 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | -------------------------------------------------------------------------------- /exh/exts/focus/__init__.py: -------------------------------------------------------------------------------- 1 | from .formula import * 2 | from .scales import * 3 | 4 | import exh.options as options 5 | 6 | options.scales = options.scales + FocusScales() -------------------------------------------------------------------------------- /exh/exts/focus/formula.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import exh.model as var 4 | import exh.utils as utils 5 | import exh.model.options as options 6 | 7 | from exh.prop.formula import Formula 8 | 9 | 10 | ############### PREDICATE CLASS ################ 11 | 12 | class Focus(Formula): 13 | """ 14 | Class for focused item 15 | - child : formula corresponding to focused element 16 | - alts : alternatives 17 | """ 18 | 19 | no_parenthesis = True 20 | substitutable = False 21 | 22 | def __init__(self, child, alts): 23 | self.alts = alts 24 | super(Focus, self).__init__(child) 25 | 26 | 27 | 28 | # @profile 29 | def evaluate_aux(self, assignment, vm, variables = dict(), free_vars = list()): 30 | # Not necessary to split by "free_vars", the empty case is just a special case 31 | # The split avoids generalizing to the worst case. 32 | return self.children[0].evaluate_aux(assignment, vm, variables, free_vars) 33 | 34 | 35 | 36 | 37 | 38 | 39 | def __eq__(self, other): 40 | return super(Focus, self).__eq__(other) and self.children[0] == other.children[0] 41 | 42 | 43 | def display_aux(self, latex): 44 | child = self.children[0] 45 | formula = ( 46 | "{}" if child.no_parenthesis 47 | else "({})" 48 | ).format(child.display_aux(latex)) 49 | 50 | 51 | if latex: 52 | return "{}_{{F}}".format(formula) 53 | else: 54 | return "{}_F".format(formula) 55 | 56 | def vars(self): 57 | """Returns a VariableManager object for all the variables that occur in the formula""" 58 | 59 | self.vm = var.VarManager.merge(self.children[0].vars(), *(alt.vars() for alt in self.alts)) 60 | self.vm.linearize() 61 | return self.vm -------------------------------------------------------------------------------- /exh/exts/focus/scales.py: -------------------------------------------------------------------------------- 1 | from exh.exts.focus.formula import Focus 2 | from exh.scales import Scales 3 | 4 | class FocusScales(Scales): 5 | """docstring for FocusScales""" 6 | def __init__(self): 7 | super(FocusScales, self).__init__() 8 | 9 | def alternatives_to(self, prejacents): 10 | """ 11 | From a list of prejacents assumed to have the same root operator Op 12 | """ 13 | return [ 14 | alt 15 | for prejacent in prejacents 16 | if isinstance(prejacent, Focus) 17 | for alt in prejacent.alts 18 | ] 19 | 20 | -------------------------------------------------------------------------------- /exh/exts/gq/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | A module for a variety of Generalized Quantifiers and associated scales, including 3 | 4 | - most/more than half 5 | - modified numerals 6 | """ 7 | 8 | import numpy as np 9 | import exh.fol as q 10 | 11 | 12 | 13 | class Most(q.Quantifier): 14 | plain_symbol = "Most " 15 | latex_symbol = "\\text{Most }" 16 | 17 | def __init__(self, *args, **kwargs): 18 | super(Most, self).__init__(*args, **kwargs) 19 | self.symbol = "Most" 20 | self.type = "most" 21 | 22 | def fun(self, results): 23 | return np.mean(results, axis = 0) > 0.5 24 | 25 | 26 | 27 | 28 | class NumeralQuantifier(q.Quantifier): 29 | 30 | def fun(self, results): 31 | return self.numerosity(np.sum(results, axis = 0)) 32 | 33 | def numerosity(self, counts): 34 | raise Exception("Abstract class NumeralQuantifier can't be instantiated") 35 | 36 | 37 | class ExactlyQuantifier(NumeralQuantifier): 38 | plain_symbol = "Exactly " 39 | latex_symbol = "\\text{{Exactly }}" 40 | 41 | def __init__(self, n, *args, **kwargs): 42 | self.n = n 43 | super(ExactlyQuantifier, self).__init__(*args, **kwargs) 44 | 45 | self.plain_symbol += str(self.n) 46 | self.latex_symbol += str(self.n) 47 | 48 | def numerosity(self, counts): 49 | return counts == self.n 50 | 51 | 52 | class MoreThanQuantifier(NumeralQuantifier): 53 | plain_symbol = "More than " 54 | latex_symbol = "\\text{{More than }}" 55 | 56 | def __init__(self, n, *args, **kwargs): 57 | self.n = n 58 | super(MoreThanQuantifier, self).__init__(*args, **kwargs) 59 | 60 | self.plain_symbol += str(self.n) 61 | self.latex_symbol += str(self.n) 62 | 63 | def numerosity(self, counts): 64 | return counts > self.n 65 | 66 | class LessThanQuantifier(NumeralQuantifier): 67 | plain_symbol = "Less than " 68 | latex_symbol = "\\text{{Less than }}" 69 | 70 | def __init__(self, n, *args, **kwargs): 71 | self.n = n 72 | super(LessThanQuantifier, self).__init__(*args, **kwargs) 73 | 74 | self.plain_symbol += str(self.n) 75 | self.latex_symbol += str(self.n) 76 | 77 | def numerosity(self, counts): 78 | return counts < self.n 79 | 80 | 81 | M = q.quantifier_cons(Most) 82 | 83 | Mx = M("x") 84 | My = M("y") 85 | Mz = M("z") 86 | 87 | def Exactly(n, var): 88 | return q.C(lambda formula: ExactlyQuantifier(n, var, formula)) 89 | 90 | def LessThan(n, var): 91 | return q.C(lambda formula: LessThanQuantifier(n, var, formula)) 92 | 93 | def MoreThan(n, var): 94 | return q.C(lambda formula: MoreThanQuantifier(n, var, formula)) -------------------------------------------------------------------------------- /exh/exts/subdomain/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module deals with making subdomain alternatives for existentials 3 | """ 4 | import numpy as np 5 | import exh.fol as q 6 | import exh.utils as utils 7 | 8 | class SubdomainExistential(q.Quantifier): 9 | """docstring for SubdomainExistential""" 10 | verbose = True 11 | plain_symbol = "\u2203c" 12 | latex_symbol = r"\exists_C" 13 | 14 | def __init__(self, quant_var, scope, domain = None, mask = None): 15 | super(SubdomainExistential, self).__init__(quant_var = quant_var, scope = scope, domain = domain) 16 | if mask is None: 17 | mask = np.full(self.domain.n, True) 18 | self.mask = mask 19 | 20 | if self.verbose: 21 | self.plain_symbol = "\u2203" + "".join("\u02da" if bit else "\u02d9" for bit in self.mask) 22 | self.latex_symbol = "\u2203_{{{}}}".format("".join(r"\circ" if bit else "." for bit in self.mask)) 23 | 24 | def fun(self, results): 25 | return np.max(results[self.mask], axis = 0) 26 | 27 | def __eq__(self, other): 28 | if self.__class__ is other.__class__: 29 | return self.qvar == other.qvar and np.all(self.mask == other.mask) and self.children[0] == other.children[0] 30 | return False 31 | 32 | def lower_alternatives(self): 33 | n_true_mask = np.sum(self.mask) 34 | for line in utils.getAssignment(n_true_mask)[1:-1]: 35 | mask = self.mask.copy() 36 | mask[self.mask] = line 37 | yield SubdomainExistential(self.qvar, self.scope, self.domain, mask) 38 | 39 | def all_alternatives(self): 40 | for line in utils.getAssignment(len(self.mask))[1:]: 41 | if np.all(line == self.mask): 42 | pass 43 | else: 44 | yield SubdomainExistential(self.qvar, self.scope, self.domain, line) 45 | 46 | 47 | class SubdomainScale: 48 | """docstring for SubdomainScale""" 49 | def __init__(self, sub_only = True): 50 | super(SubdomainScale, self).__init__() 51 | self.sub_only = sub_only 52 | 53 | def alternatives_to(self, prejacents): 54 | original = prejacents[0] 55 | if isinstance(original, SubdomainExistential): 56 | alternatives = SubdomainExistential.lower_alternatives if self.sub_only else SubdomainExistential.all_alternatives 57 | return [alt for prejacent in prejacents for alt in alternatives(prejacent)] 58 | else: 59 | return [] 60 | 61 | sub_scale = SubdomainScale(True) 62 | dom_scale = SubdomainScale(False) 63 | 64 | def Ec_(var, domain = None, mask = None): 65 | return q.C(lambda scope: SubdomainExistential(var, scope, domain = domain, mask = mask)) 66 | 67 | Ec_x = Ec_("x") 68 | Ec_y = Ec_("y") 69 | Ec_z = Ec_("z") -------------------------------------------------------------------------------- /exh/fol/__init__.py: -------------------------------------------------------------------------------- 1 | from .quantifier import * 2 | 3 | A = quantifier_cons(Universal) 4 | E = quantifier_cons(Existential) 5 | 6 | 7 | Ax = A("x") 8 | Ex = E("x") 9 | 10 | Ay = A("y") 11 | Ey = E("y") 12 | 13 | Az = A("z") 14 | Ez = E("z") 15 | 16 | 17 | 18 | 19 | def Ax_in_(domain): 20 | return A("x", domain = domain) 21 | 22 | def Ay_in_(domain): 23 | return A("y", domain = domain) 24 | 25 | def Az_in_(domain): 26 | return A("z", domain = domain) 27 | 28 | def Ex_in_(domain): 29 | return E("x", domain = domain) 30 | 31 | def Ey_in_(domain): 32 | return E("y", domain = domain) 33 | 34 | def Ez_in_(domain): 35 | return E("z", domain = domain) -------------------------------------------------------------------------------- /exh/fol/quantifier.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from collections import defaultdict 3 | 4 | from exh.model import vars as var 5 | from exh.model import options 6 | import exh.prop as prop 7 | 8 | class Quantifier(prop.Formula): 9 | """ 10 | Abstract class for quantified formula 11 | 12 | Attributes: 13 | symbol -- display symbol (obsolete) 14 | qvar -- string name of the individual variable of quantification 15 | domain -- something specifying the number of individuals in the domain 16 | """ 17 | 18 | substitutable = False 19 | plain_symbol = "Q" 20 | latex_symbol = "Q" 21 | 22 | def __init__(self, quant_var, scope, domain = None): 23 | super(Quantifier, self).__init__(scope) 24 | self.qvar = quant_var 25 | 26 | try: 27 | self.free_vars.remove(self.qvar) 28 | except ValueError: 29 | pass 30 | 31 | if domain is None: 32 | domain = var.default_domain 33 | self.domain = domain 34 | 35 | def display_aux(self, latex): 36 | return "{symb} {var}, {scope}".format( 37 | symb = self.latex_symbol if latex else self.plain_symbol, 38 | var = self.qvar, 39 | scope = self.children[0].display_aux(latex) 40 | ) 41 | 42 | def evaluate_aux(self, assignment, vm, variables = dict(), free_vars = list()): 43 | return self.fun(np.stack([self.children[0].evaluate_aux(assignment, vm, 44 | dict(variables, **{self.qvar: i}), 45 | free_vars) 46 | for i in range(self.domain.n)], 47 | axis = 0)) 48 | 49 | def fun(self, results): 50 | raise Exception("Evaluation of abstract class Quantifier ; use Universal or Existential class") 51 | 52 | def __eq__(self, other): 53 | if self.__class__ is other.__class__: 54 | if self.qvar == other.qvar: 55 | return self.children[0] == other.children[0] 56 | return False 57 | 58 | @property 59 | def scope(self): 60 | return self.children[0] 61 | 62 | 63 | def copy(self): 64 | return self.__class__(self.qvar, self.scope) 65 | 66 | @classmethod 67 | def alternative_to(cls, other): 68 | return cls(other.qvar, other.children[0], domain = other.domain) 69 | 70 | 71 | class Universal(Quantifier): 72 | plain_symbol = "\u2200" 73 | latex_symbol = r"\forall" 74 | 75 | def __init__(self, *args, **kwargs): 76 | super(Universal, self).__init__(*args, **kwargs) 77 | 78 | def fun(self, results): 79 | return np.min(results, axis = 0) 80 | 81 | class Existential(Quantifier): 82 | plain_symbol = "\u2203" 83 | latex_symbol = r"\exists" 84 | 85 | def __init__(self, *args, **kwargs): 86 | super(Existential, self).__init__(*args, **kwargs) 87 | 88 | def fun(self, results): 89 | return np.max(results, axis = 0) 90 | 91 | class C: 92 | """ 93 | The following baroque construction allows us to write quantifierd formula in parenthesis-free way: 94 | Ax > Ey > a | b 95 | To do this, we twist Python in ways that are not recommendable for other purposes. 96 | Reasonable usage of the library should not incur any problems. 97 | 98 | The class C is such that "C() > formula37" will return a formula built from "formula37" using function cons 99 | """ 100 | 101 | def __init__(self, function): 102 | self.cons = function 103 | self.bup = function 104 | 105 | def __gt__(self, other): 106 | """ 107 | In python, A > B > C is evaluated as (A > B) and (B > C) ; 108 | this is not the semantics we want so we allow A>B to change the value of B before evaluation in conjunct (B>C) 109 | This is highly unorthodox ; don't try this at home 110 | """ 111 | return_val = True 112 | if isinstance(other, prop.Formula): 113 | return_val = self.cons(other) 114 | else: 115 | f = other.cons # We store a reference to the original function so that the lambda term is not interpreted recursively 116 | other.cons = lambda formula: self.cons(f(formula)) 117 | 118 | self.cons = self.bup # We restore the original function in case chaining has happened 119 | return return_val 120 | 121 | 122 | 123 | def quantifier_cons(constructor): 124 | """ Returns a C class from a formula constructor """ 125 | def f(var, **kwargs): 126 | return C(lambda formula: constructor(var, formula, **kwargs)) 127 | 128 | return f 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /exh/model/__init__.py: -------------------------------------------------------------------------------- 1 | from .vars import * 2 | from .model import * -------------------------------------------------------------------------------- /exh/model/exceptions.py: -------------------------------------------------------------------------------- 1 | class UnknownPred(Exception): 2 | def __init__(self, var, vm): 3 | self.var = var 4 | self.vm = vm 5 | super(UnknownPred, self).__init__("Unknown predicate with index {pred}. Known indices: {vmvars}".format(pred = var, 6 | vmvars = list(vm.names.keys()))) 7 | 8 | -------------------------------------------------------------------------------- /exh/model/model.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import itertools 3 | 4 | from . import options 5 | import exh.utils as utils 6 | from .vars import VarManager 7 | from exh.utils.table import Table 8 | # from formula import Var 9 | 10 | class Universe: 11 | """ 12 | Universe generates a set of assignments of truth-values for every predicate (i.e. a world). 13 | The assigments either cover all the logical space.or just some logical possibilities 14 | 15 | Attributes: 16 | n -- number of bits that specify the world (example: propositional varaible a requires 1 bit, unary predicates a(x) as many bits as there are individuaks) 17 | worlds -- numpy boolean array worlds[i, j] specifies the truth-value of the j-th bit at the i-th world 18 | vm -- variable manager ; specifies a mapping from predicates to bit position (example: predicate variable "a" is mapped to "x") 19 | 20 | Properties: 21 | n_worlds -- number of worlds in universe 22 | """ 23 | 24 | def __init__(self, **kwargs): 25 | """ 26 | Keyword arguments: 27 | f -- one formula from which to extract the predicates 28 | vm -- a variable manager object 29 | fs -- a list of formulas from which to ex 30 | """ 31 | 32 | if "f" in kwargs: 33 | self.vm = kwargs["f"].vm 34 | elif "vm" in kwargs: 35 | self.vm = kwargs["vm"] 36 | elif "fs" in kwargs: 37 | self.vm = VarManager.merge(*[f.vm for f in kwargs["fs"]]) 38 | 39 | self.n = self.vm.n 40 | 41 | if "worlds" not in kwargs: 42 | self.worlds = utils.getAssignment(self.n) 43 | else: 44 | self.worlds = kwargs["worlds"] 45 | 46 | @property 47 | def n_worlds(self): 48 | return self.worlds.shape[0] 49 | 50 | 51 | def consistent(self, *fs): 52 | 53 | output = self.evaluate(*fs) 54 | 55 | return np.any(np.min(output, axis = 1)) 56 | 57 | # def set(pred, value, **variables): 58 | # if isinstance(pred, Var): 59 | # idx = pred.idx 60 | # else: 61 | # idx = pred 62 | 63 | # deps = self.vm.preds[idx] 64 | 65 | # # Variables for which no value has been provided 66 | # no_val_vars = list(set(deps.keys()) - set(variables.keys())) 67 | 68 | # def all_vars_assignment(): 69 | # for vals in product(range(options.dom_quant), repeat = len(no_val_vars)): 70 | # d = {var: val for var, val in zip(no_val_vars, vals)} 71 | # d.update(variables) 72 | # yield d 73 | 74 | 75 | # ko_cols = [self.vm.index(idx, **d) for d in iterator()] 76 | 77 | # # We remove all the lines where the values of column does not match value 78 | # reduced_worlds = self.u.worlds[:, ko_cols] 79 | # goal = np.full_like(reduced_worlds, value) 80 | 81 | # indices_keep = np.max((goal == reduced_worlds), axis = 1) 82 | 83 | # self.worlds = self.worlds[indices_keep, :] 84 | 85 | 86 | def entails(self, f1, f2): 87 | """Checks if f1 entails f2 in universe""" 88 | return not self.consistent(f1 & ~f2) 89 | 90 | def equivalent(self, f1, f2): 91 | output = self.evaluate(f1, f2) 92 | return np.all(output[:, 0] == output[:, 1]) 93 | 94 | def evaluate(self, *fs, **kwargs): 95 | """ 96 | Evaluate formules against every world in universe 97 | """ 98 | 99 | return np.transpose(np.stack([f.evaluate(assignment = self.worlds, vm = self.vm, **kwargs) for f in fs])) 100 | 101 | 102 | def name_worlds(self): 103 | """ 104 | Returns list of meaningful names to bits, depending on which predicates they set true 105 | 106 | e.g. if Universe is for propositional variable "a", and unary predicate "b" will return 107 | ["a", "b(0)", "b(1)", "b(2)"] 108 | """ 109 | 110 | def str_tuple(tuple): 111 | return "({})".format(",".join(list(map(str, t)))) 112 | 113 | nvars = self.worlds.shape[1] 114 | names = [i for i in range(nvars)] 115 | name_vars = ["A{}".format(key) for key in self.vm.preds.keys()] 116 | 117 | for name, var_idx in self.vm.names.items(): 118 | vm_index = self.vm.pred_to_vm_index[var_idx] 119 | name_vars[vm_index] = name 120 | 121 | vm_idx_to_deps = list(self.vm.preds.values()) 122 | 123 | for i, offset in enumerate(self.vm.offset): 124 | deps = vm_idx_to_deps[i] 125 | 126 | if deps: 127 | 128 | for t in np.ndindex(*deps): 129 | i_col = offset + np.ravel_multi_index(t, deps, order = "F") 130 | names[i_col] = name_vars[i] + str_tuple(t) 131 | 132 | else: 133 | names[offset] = name_vars[i] 134 | 135 | return names 136 | 137 | 138 | 139 | 140 | 141 | 142 | def restrict(self, indices): 143 | """Returns Universe object restricted to the worlds with indices in "indices" argument""" 144 | 145 | return Universe(vm = self.vm, worlds = self.worlds[indices]) 146 | 147 | 148 | def update(self, var): 149 | self.vm = VarManager.merge(self.vm, var.vm) 150 | self.n = self.vm.n 151 | self.worlds = utils.getAssignment(n) 152 | 153 | def truth_table(self, *fs, **kwargs): 154 | """Display a truth-table for formulas fs. Keyword arguments are passed to table (cf exh.utils.table)""" 155 | 156 | output = self.evaluate(*fs) 157 | 158 | table = Table(**kwargs) 159 | nvars = self.worlds.shape[1] 160 | nworlds = self.worlds.shape[0] 161 | 162 | # We find the names for the columns 163 | name_cols = self.name_worlds() + [str(f) for f in fs] 164 | table.set_header(name_cols) 165 | 166 | # self.worlds: nworlds x nvars 167 | # output : nworlds x nfs 168 | combined = np.concatenate([self.worlds, output], axis = 1) 169 | 170 | for row in combined: 171 | table.add_row(row) 172 | 173 | table.set_strong_col(nvars) 174 | table.print() 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /exh/model/options.py: -------------------------------------------------------------------------------- 1 | # Whether automatic alternatives use subconstituents alternatives by default 2 | sub = True 3 | 4 | # Default size of the domain of quantification of quantifiers 5 | dom_quant = 3 6 | 7 | # Whether display is in Latex by default 8 | latex_display = True 9 | 10 | -------------------------------------------------------------------------------- /exh/model/vars.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from . import options 3 | from . import exceptions 4 | 5 | class Domain: 6 | """ 7 | Represents some domain of quantification. 8 | 9 | Attributes: 10 | - n (int) -- the size of the domain of quantification 11 | """ 12 | def __init__(self, size): 13 | self._n = size 14 | 15 | @property 16 | def n(self): 17 | return self._n 18 | 19 | 20 | 21 | class DefaultDomain(Domain): 22 | """ 23 | A domain whose size is rigidly bound to "options.dom_quant" 24 | """ 25 | def __init__(self): 26 | super(DefaultDomain, self).__init__(options.dom_quant) 27 | 28 | @property 29 | def n(self): 30 | return options.dom_quant 31 | 32 | default_domain = DefaultDomain() 33 | D3 = Domain(3) 34 | D4 = Domain(4) 35 | D5 = Domain(5) 36 | 37 | class VarManager: 38 | """ 39 | VarManager keeps track of all independent variables in a given system. 40 | Maps propositional variables and fully saturated predicate variables to indices 41 | 42 | Example: 43 | System: unary predicate p and proposition q; domain of individuals = 3 44 | Values: p(0) p(1) p(2) q 45 | are mapped to 46 | Indices: 0 1 2 3 47 | 48 | Attributes: 49 | preds -- a dictionary mapping predicate indices to the sizes of the domains they depend on 50 | pred_to_vm_index -- a dictionary mapping predicate indices to a position (e.g. if a has index 1 and b index 4, a is mapped to position 0 and b to position 1) 51 | names -- a dictionary mapping predicate names to their indices 52 | memory -- a list mapping predicate positions to how many bits are required to define this predicate 53 | offset -- a list mapping predicate positions to the bit index offset at which they are defined. 54 | """ 55 | def __init__(self, preds, names = dict()): 56 | self.preds = preds 57 | self.names = names 58 | 59 | # pred_to_vm_index : dictionary mapping Formula's predicate indices to VarManager's predicate indices 60 | self.pred_to_vm_index = {pred_idx: vm_idx for vm_idx, pred_idx in enumerate(self.preds.keys())} 61 | self.linearize() 62 | 63 | def linearize(self): 64 | """ 65 | How many independent boolean values to specify the propositional variables. 66 | 67 | propositions: 1 i 68 | unary predicate: dom_quant 69 | etc 70 | """ 71 | 72 | self.memory = [grand_product(deps) for _, deps in self.preds.items()] 73 | 74 | # position in memory of bits devoted to a parcitular predicate 75 | self.offset = [0] 76 | for mem in self.memory[:-1]: 77 | self.offset.append(self.offset[-1] + mem) 78 | 79 | def merge(*vms): 80 | return VarManager(preds = {k: v for vm in vms for k, v in vm.preds.items()}, 81 | names = {k: v for vm in vms for k, v in vm.names.items()}) 82 | 83 | @property 84 | def n(self): 85 | """ 86 | Number of bits required to specify an assignment 87 | """ 88 | return sum(self.memory) 89 | 90 | def index(self, pred, value_slots): 91 | if pred in self.preds: 92 | deps = self.preds[pred] 93 | else: 94 | raise exceptions.UnknownPred(pred, self) 95 | 96 | pred_idx = self.pred_to_vm_index[pred] 97 | offset = self.offset[pred_idx] 98 | 99 | # to_return = offset 100 | # multiplier = 1 101 | # for slot, dep in zip(value_slots, deps): 102 | # to_return += slot * multiplier 103 | # multiplier *= dep 104 | # print(value_slots, deps) 105 | try: 106 | return offset + (np.ravel_multi_index(value_slots, deps, order = "F") if deps else 0) 107 | except ValueError: 108 | raise Exception("Can't access {} in predicate with domain sizes {}".format(value_slots, deps)) 109 | 110 | 111 | def grand_product(list_numbers): 112 | """ 113 | Computes grand product of a list. 114 | [1, 2, 3] -> 6 (= 2 * 3) 115 | """ 116 | to_return = 1 117 | for nb in list_numbers: 118 | to_return *= nb 119 | return to_return 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /exh/options.py: -------------------------------------------------------------------------------- 1 | from exh.prop import Or, And 2 | from exh.fol import Existential, Universal 3 | from exh.scales import SimpleScales 4 | 5 | # Default scalar scales 6 | scales = SimpleScales([{Or, And}, {Existential, Universal}]) 7 | 8 | # Whether Exh computes innocent inclusion by default 9 | ii_on = False 10 | 11 | # Whether automatic alternatives use subconstituents alternatives by default 12 | sub = True 13 | 14 | # Whether the prejacent of Exh is an alternative to Exh when "sub" is True ; we don't derive Free Choice if this is set to True 15 | prejacent_alternative_to_exh = False 16 | 17 | # The minimmal number of alternatives beyond which the "diagnose" method starts displaying them as bullet point list 18 | cutoff_inline_to_list = 5 -------------------------------------------------------------------------------- /exh/prop/__init__.py: -------------------------------------------------------------------------------- 1 | from .formula import * 2 | from .predicate import * 3 | 4 | true = Truth() 5 | false = Falsity() 6 | 7 | 8 | a = Pred(0, name = "a") 9 | b = Pred(1, name = "b") 10 | c = Pred(2, name = "c") 11 | 12 | 13 | if __name__ == "__main__": 14 | f1 = a & b & ~c 15 | f2 = a | b & c 16 | -------------------------------------------------------------------------------- /exh/prop/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenyC/Exh/2c48806462c7d502e36b17b55547eb11738891a2/exh/prop/__init__.pyc -------------------------------------------------------------------------------- /exh/prop/display.py: -------------------------------------------------------------------------------- 1 | import exh.model.options as options 2 | 3 | ### DISPLAY METHODS #### 4 | 5 | 6 | class Display: 7 | """ 8 | Returns string representation of the object, in plain text or Latex 9 | """ 10 | def display(self, latex = None): 11 | if latex is None: 12 | latex = options.latex_display 13 | 14 | if latex: 15 | return "${}$".format(self.display_aux(latex)) 16 | else: 17 | return self.display_aux(latex) 18 | 19 | def display_aux(self, latex): 20 | raise Exception("Base class Formula cannot be displayed") 21 | # if latex: 22 | # display_dict = self.__class__.latex_dict 23 | # else: 24 | # display_dict = self.__class__.plain_dict 25 | 26 | # def paren(typeF, child): 27 | # if (typeF == child.type) or (child.type == "pred") or (child.type == "not"): 28 | # return child.display_aux(display_dict) 29 | # else: 30 | # return "({})".format(child.display_aux(display_dict)) 31 | 32 | # if self.type == "not" or self.type == "exh": 33 | # return "{}[{}]".format(display_dict[self.type], self.children[0].display_aux(latex)) 34 | # else: 35 | # return " {type} ".format(type = display_dict[self.type]).join([paren(self.type, child) for child in self.children]) 36 | 37 | """ 38 | Display object, in plain text or LateX 39 | """ 40 | def show(self, latex = None): 41 | if latex is None: # we cannot use "options.latex_display" as default lest we lose dynamic binding 42 | latex = options.latex_display 43 | 44 | if latex: 45 | display(Math(self.display(latex))) 46 | else: 47 | print(self.display(latex)) 48 | 49 | -------------------------------------------------------------------------------- /exh/prop/evaluate.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import itertools 3 | 4 | import exh.utils as utils 5 | import exh.model.options as options 6 | 7 | ### EVALUATION METHODS ### 8 | 9 | class Evaluate: 10 | 11 | def evaluate(self, **kwargs): 12 | """ 13 | Evaluate the formula with respect to an assignment of values to the propositional and predicate variable 14 | 15 | Arguments: 16 | vm (VarManager, default = self.vm) -- a variable manager for the variables in the formula 17 | either 18 | assignment (np.ndarray[bool]) -- at index i, is the value for the independent variable with index i according to vm 19 | or 20 | kwargs (dict) -- for every key, provides the value of the propositional or predicate variable with that name 21 | if proposition, the value must be boolean 22 | if n-ary predicate, value must be a boolean numpy array with size (options.dom_quant)^n 23 | no_flattening (bool, default = False) -- prevent automatic flattening of result if the result is one-dimensional 24 | 25 | Returns: 26 | np.ndarray[bool] -- Boolean array of shape (n_assignment, dom_quant, ..., dom_quant) specifying for each assignment and values given to free variables 27 | <---number of free vars--> 28 | or 29 | bool -- if the latter result is single dimensional 30 | """ 31 | 32 | if "vm" in kwargs: 33 | vm = kwargs["vm"] 34 | else: 35 | vm = self.vm 36 | 37 | # If assignment is provided, use it ; otherwise, construct one from keyword arguments 38 | if "assignment" in kwargs: 39 | assignment = kwargs["assignment"] 40 | else: 41 | assignment = np.full(vm.n, True) 42 | 43 | for var, val in kwargs.items(): 44 | if var in vm.names: 45 | idx = vm.names[var] 46 | else: 47 | continue 48 | 49 | size_domains = vm.preds[idx] 50 | for t in np.ndindex(*size_domains): 51 | i = vm.index(idx, t) 52 | assignment[i] = utils.get(val, t) 53 | 54 | assignment = assignment[np.newaxis, :] 55 | 56 | variables = kwargs.get("variables", dict()) 57 | free_vars = [var for var in kwargs.get("free_vars", self.free_vars) if var not in variables] 58 | to_return = self.evaluate_aux(assignment, vm, variables = variables, free_vars = free_vars) 59 | 60 | if all(dim == 1 for dim in to_return.shape) and not ("no_flattening" in kwargs and kwargs["no_flattening"]): 61 | return to_return.item() 62 | else: 63 | return to_return 64 | 65 | 66 | def evaluate_aux(self, assignment, vm, variables = dict(), free_vars = list()): 67 | """ 68 | Auxiliary method for recursion ; evaluates sub-formula (to be overridden by children classes) 69 | 70 | Arguments: 71 | assignment (numpy.ndarray[bool]) -- each line specifies a different assignmen of bit positions to truth values # TODO: rename to worlds 72 | vm (VariableManager) -- variable manager mapping predicate and variable names to bit positions 73 | variables (dict[str, int]) -- local assignment of values to variables 74 | free_vars (list[str]) -- variables left free in the matrix formula (the formula on which "evaluate" was called) 75 | 76 | Returns: 77 | np.ndarray[bool] -- Boolean array of shape (n_assignment, dom_quant, ..., dom_quant) specifying for each assignment and values given to free variables 78 | <---number of free vars--> 79 | """ 80 | raise Exception("evaluate_aux is not been implemented for class {}".format(self.__class__.__name__)) 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /exh/prop/formula.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from collections import defaultdict 3 | import itertools 4 | 5 | from IPython.display import Math, display, HTML 6 | 7 | import exh.utils as utils 8 | import exh.model.options as options 9 | import exh.model.vars as var 10 | 11 | from .simplify import IteratorType 12 | from .display import Display 13 | from .evaluate import Evaluate 14 | 15 | 16 | class Formula(IteratorType, Display, Evaluate): # Using sub-classing to spread code over multiple files 17 | """ 18 | Base class for fomulas 19 | 20 | Class attributes: 21 | no_parenthesis (bool) -- whether to display the formula with parenthesis around it in conjunctions, coordinations, etc. 22 | substitutable (bool) -- Whether sub-formulas of this formulas count as alternatives to it 23 | 24 | Attributes: 25 | children (list(Formula)) -- sub-formulas 26 | vm (VariableManager) -- organizes mapping from predicate and variables name to concrete bit position 27 | """ 28 | 29 | no_parenthesis = False 30 | substitutable = True 31 | 32 | def __init__(self, *children): 33 | self.subst = self.__class__.substitutable 34 | self.children = children 35 | self.vars() 36 | 37 | # Free vars are lexically ordered 38 | self.free_vars = list(set(var for child in self.children for var in child.free_vars)) 39 | self.free_vars.sort() 40 | 41 | def reinitialize(self): #only used for Exh, which performs computation at initialization 42 | pass 43 | 44 | def __and__(self, other): 45 | return And(self, other) 46 | 47 | def __or__(self, other): 48 | return Or(self, other) 49 | 50 | def __invert__(self): 51 | return Not(self) 52 | 53 | def __str__(self): 54 | return self.display() 55 | 56 | def __repr__(self): 57 | return self.display() 58 | 59 | def copy(self): 60 | """Creates copy of the object (overridden by children's classes""" 61 | return Formula(*self.children) 62 | 63 | def __eq__(self, other): 64 | """Returns true if two formulas are syntactically the same, up to constituent reordering (overridden by children classes)""" 65 | return self.__class__ is other.__class__ 66 | 67 | 68 | 69 | ### FORMULA MANIPULATION METHODS ### 70 | def flatten(self): 71 | """ 72 | Turns embedded "or" and "and" in to generalized "or" and "and" 73 | Ex: a or ((b or c) or d) becomes a or b or c or d 74 | """ 75 | raise Exception("Not implemented yet!") 76 | 77 | if self.type in ["and", "or"]: 78 | new_children = list(self.iterator_type()) 79 | else: 80 | new_children = self.children 81 | 82 | return Formula(self.type, *map(lambda c: c.flatten(), new_children)) 83 | 84 | 85 | def simplify(self): 86 | """Turns a formula into a quantifier-first, disjunctions of conjunctions formula""" 87 | raise Exception("Not implemented yet!") 88 | 89 | # Returns all indexes of variables in a conjunctive formula 90 | def idx_vars(f): 91 | return [child.idx if "idx" in dir(child) else -1 for child in f.iterator_type("and")] 92 | 93 | if self.type == "or" or self.type == "and": 94 | simplified_children = [child.simplify() for child in self.iterator_type()] 95 | 96 | if self.type == "or": 97 | all_children = [grandchild for child in simplified_children for grandchild in child.iterator_type("or")] 98 | all_children.sort(key = idx_vars) 99 | 100 | return Formula("or", *all_children) 101 | 102 | else: 103 | all_children = [list(child.iterator_type("or")) for child in simplified_children] 104 | individual_conjuncts = TODO 105 | 106 | return self 107 | elif self.type == "neg": 108 | child = self.children[0] 109 | 110 | if child.type == "or": 111 | pass 112 | else: 113 | return self 114 | 115 | def vars(self): 116 | """Returns a VariableManager object for all the variables that occur in the formula""" 117 | 118 | self.vm = var.VarManager.merge(*[c.vars() for c in self.children]) 119 | self.vm.linearize() 120 | return self.vm 121 | 122 | @classmethod 123 | def alternative_to(cls, other): 124 | """ 125 | Returns an formulat which is an alternative to other with the same children ; meant to be overriden by subclasses 126 | Example: Or.alternative_to(a & b) -> a | b 127 | """ 128 | raise Exception("alternative_to is not been implemented for class {}".format(cls.__name__)) 129 | 130 | 131 | 132 | ############### OPERATORS ############## 133 | 134 | class Operator(Formula): 135 | """ 136 | Base class for associative operators 137 | 138 | Class attributes: 139 | plain_symbol (str) -- symbol to display in plain text mode (to be overridden by children classes) 140 | latex_symbol (str) -- symbol to display in LateX mode (to be overridden by children classes) 141 | 142 | Attributes: 143 | fun (function) -- function to call on subformulas' result to get parent result 144 | """ 145 | 146 | 147 | plain_symbol = "op" 148 | latex_symbol = "\text{op}" 149 | 150 | def __init__(self, fun, *children): 151 | super(Operator, self).__init__(*children) 152 | self.fun = fun 153 | 154 | def evaluate_aux(self, assignment, vm, variables = dict(), free_vars = list()): 155 | """Stacks subformulas' results and applies fun to it""" 156 | return self.fun(np.stack([child.evaluate_aux(assignment, vm, variables, free_vars) for child in self.children])) 157 | 158 | 159 | def display_aux(self, latex): 160 | 161 | if latex: 162 | symbol = self.__class__.latex_symbol 163 | else: 164 | symbol = self.__class__.plain_symbol 165 | 166 | def paren(child): 167 | if (self.__class__ is child.__class__) or child.__class__.no_parenthesis: 168 | return child.display_aux(latex) 169 | else: 170 | return "({})".format(child.display_aux(latex)) 171 | 172 | if len(self.children) == 1: 173 | return "{}[{}]".format(symbol, self.children[0].display_aux(latex)) 174 | else: 175 | return " {type} ".format(type = symbol).join([paren(child) for child in self.children]) 176 | 177 | def __eq__(self, other): 178 | if self.__class__ is other.__class__: 179 | other_children = list(other.children) 180 | 181 | for child1 in self.children: 182 | 183 | matches = [i for i, child2 in enumerate(other_children) if child1 == child2] 184 | 185 | if matches: 186 | other_children.pop(matches[0]) 187 | else: 188 | return False 189 | 190 | return True 191 | else: 192 | return False 193 | 194 | @classmethod 195 | def alternative_to(cls, other): 196 | return cls(*other.children) 197 | 198 | 199 | class And(Operator): 200 | plain_symbol = "and" 201 | latex_symbol = r"\land" 202 | 203 | fun_ = lambda array: np.min(array, axis = 0) 204 | 205 | """docstring for And""" 206 | def __init__(self, *children): 207 | super(And, self).__init__(And.fun_, *children) 208 | 209 | 210 | class Or(Operator): 211 | plain_symbol = "or" 212 | latex_symbol = r"\lor" 213 | 214 | fun_ = lambda array: np.max(array, axis = 0) 215 | 216 | """docstring for Or""" 217 | def __init__(self, *children): 218 | super(Or, self).__init__(Or.fun_, *children) 219 | 220 | class Not(Operator): 221 | no_parenthesis = True 222 | 223 | plain_symbol = "not" 224 | latex_symbol = r"\neg" 225 | 226 | fun_ = lambda x: np.squeeze(np.logical_not(x), axis = 0) 227 | 228 | """docstring for Not""" 229 | def __init__(self, child): 230 | super(Not, self).__init__(Not.fun_, child) 231 | 232 | 233 | 234 | 235 | ############### TAUTOLOGIES AND ANTILOGIES ######## 236 | 237 | class Truth(Formula): 238 | """docstring for Truth""" 239 | def __init__(self): 240 | super(Truth, self).__init__() 241 | 242 | def evaluate_aux(self, assignment, vm, variables = dict(), free_vars = list()): 243 | return np.ones(assignment.shape[0], dtype = "bool") 244 | 245 | def display_aux(self, latex): 246 | if latex: 247 | return r"\textsf{true}" 248 | else: 249 | return "true" 250 | 251 | 252 | class Falsity(Formula): 253 | """docstring for Falsity""" 254 | def __init__(self): 255 | super(Falsity, self).__init__() 256 | 257 | def evaluate_aux(self, assignment, vm, variables = dict(), free_vars = list()): 258 | return np.zeros(assignment.shape[0], dtype = "bool") 259 | 260 | def display_aux(self, latex): 261 | if latex: 262 | return r"\textsf{true}" 263 | else: 264 | return "true" 265 | 266 | class Named(Formula): 267 | def __init__(self, name, child, latex_name = None): 268 | super(Named, self).__init__(child) 269 | self.name = name 270 | self.latex_name = latex_name if latex_name is not None else self.name 271 | 272 | def evaluate_aux(self, *args, **kwargs): 273 | return self.children[0].evaluate_aux(*args, **kwargs) 274 | 275 | def display_aux(self, latex): 276 | if latex: 277 | return self.latex_name 278 | else: 279 | return self.name 280 | -------------------------------------------------------------------------------- /exh/prop/formula.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenyC/Exh/2c48806462c7d502e36b17b55547eb11738891a2/exh/prop/formula.pyc -------------------------------------------------------------------------------- /exh/prop/predicate.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import exh.model.vars as var 4 | import exh.utils as utils 5 | import exh.model.options as options 6 | 7 | from .formula import Formula 8 | 9 | 10 | ############### PREDICATE CLASS ################ 11 | 12 | class Pred(Formula): 13 | """ 14 | Class for atomic proposition and predicate variable 15 | Attributes: 16 | - name : name for display and evaluation 17 | - arity : for n-ary predicates, the number of variables that the predicate depends on 18 | - deps : the name of the default variables that the predicate depends 19 | (i.e. when no vars are specified, as in Ax > a, this is what the predicate depends on) 20 | - idx : an integer that uniquely identifies the predicate 21 | """ 22 | 23 | no_parenthesis = True 24 | last_index = 100 # leaving some offset 25 | 26 | def __init__(self, index = None, name = None, depends = None, domains = None): 27 | self.name = name 28 | 29 | if index is None: 30 | index = Pred.last_index 31 | self.idx = index 32 | 33 | if self.idx >= Pred.last_index: 34 | Pred.last_index = self.idx + 1 35 | 36 | if depends is None: 37 | depends = [] 38 | elif isinstance(depends, str): 39 | depends = [depends] 40 | elif isinstance(depends, int): 41 | depends = [depends] 42 | self.depends(*depends, domains = domains) 43 | 44 | 45 | super(Pred, self).__init__() 46 | 47 | self.free_vars = self.free_vars_ 48 | 49 | @property 50 | def free_vars_(self): 51 | return sorted(set(self.deps)) 52 | 53 | 54 | def flatten(self): 55 | return self 56 | 57 | def simplify(self): 58 | return self 59 | 60 | def display_aux(self, latex): 61 | if self.deps: 62 | dep_string = "({})".format(",".join(list(self.deps))) 63 | else: 64 | dep_string = "" 65 | 66 | if self.name is None: 67 | return "A{}".format(self.idx) + dep_string 68 | else: 69 | return self.name + dep_string 70 | 71 | # @profile 72 | def evaluate_aux(self, assignment, vm, variables = dict(), free_vars = list()): 73 | # Not necessary to split by "free_vars", the empty case is just a special case 74 | # The split avoids generalizing to the worst case. 75 | if not free_vars: 76 | try: 77 | value_slots = [variables[dep] for dep in self.deps] 78 | except KeyError as e: 79 | raise Exception("Predicate {} cannot be evaluated b/c no value for free variable {} was provided".format(self.name, e)) 80 | 81 | return assignment[:, vm.index(self.idx, value_slots)] 82 | else: 83 | """ 84 | P(x, y, z) 85 | The value of some of these variables are provided by assignment, others are left free 86 | """ 87 | raise NotImplementedError 88 | shape_output = tuple(options.dom_quant for _ in free_vars) 89 | vars_not_in_assignment = set(self.deps).difference(set(variables.keys())) 90 | 91 | position_free_vars = np.full(len(free_vars), False) 92 | for dep in vars_not_in_assignment: 93 | position_free_vars[free_vars.index(dep)] = True #Potential for exception if free_vars is misconfigured 94 | 95 | value_slots = np.full(len(self.deps), 0, dtype = "int") 96 | mask_free_vars = np.full(len(self.deps), False, dtype = "bool") 97 | 98 | for i, dep in enumerate(self.deps): 99 | if dep in variables: 100 | value_slots[i] = variables[dep] 101 | else: 102 | mask_free_vars[i] = True 103 | 104 | output = np.full((len(assignment), *shape_output), True, dtype = "bool") 105 | 106 | for indices in np.ndindex(shape_output): 107 | value_slots[mask_free_vars] = np.array(indices)[position_free_vars] 108 | indexing = slice(len(assignment)), *indices 109 | output[indexing] = assignment[:, vm.index(self.idx, value_slots)] 110 | 111 | return output 112 | 113 | 114 | 115 | 116 | 117 | 118 | def __call__(self, *variables): 119 | if len(variables) == self.arity: 120 | return Pred(self.idx, self.name, variables, domains = self.domains) 121 | else: 122 | print("""WARNING: {} variables were provided than the predicate {} depends on ; changing the arity of the predicate to {}. Universe objects will need to be recreated.""" 123 | .format("More" if len(variables) > self.arity else "Less", self.name, len(variables))) 124 | self.depends(*variables) 125 | self.vars() # Recompute variable manager 126 | self.free_vars = self.free_vars_ 127 | return self 128 | 129 | def __eq__(self, other): 130 | return super(Pred, self).__eq__(other) and self.idx == other.idx 131 | 132 | def vars(self): 133 | size_domains = [domain.n for domain in self.domains] 134 | 135 | if self.name is None: 136 | self.vm = var.VarManager({self.idx: size_domains}) 137 | else: 138 | self.vm = var.VarManager({self.idx: size_domains}, names = {self.name: self.idx}) 139 | 140 | return self.vm 141 | 142 | 143 | 144 | 145 | def depends(self, *depends_on, domains = None): 146 | if depends_on and isinstance(depends_on[0], int): 147 | self.arity = depends_on[0] 148 | self.deps = [var_name for _, var_name in zip(range(self.arity), utils.automatic_var_names())] 149 | else: 150 | self.arity = len(depends_on) 151 | self.deps = depends_on 152 | 153 | if domains is None: 154 | domains = [var.default_domain for _ in self.deps] 155 | self.domains = domains 156 | -------------------------------------------------------------------------------- /exh/prop/simplify.py: -------------------------------------------------------------------------------- 1 | """ 2 | Not implemented yet! 3 | """ 4 | 5 | # from formula import Formula 6 | 7 | 8 | class IteratorType: 9 | """ 10 | Iterator over all smallest nodes in the formula that are not of type "type_f" 11 | 12 | Example: in the formula ((a and b) or c) or (d or e), upon input type "or", the iterator returns: 13 | a and b, c, d, e 14 | """ 15 | def iterator_type(self, type_f = None): 16 | 17 | if type_f is None: 18 | type_f = self.__class__ 19 | 20 | if isinstance(self, type_f): 21 | for child in self.children: 22 | for value in child.iterator_type(type_f): 23 | yield value 24 | else: 25 | yield self 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /exh/scales.py: -------------------------------------------------------------------------------- 1 | """ 2 | The classes below find alternatives replacing the root operator by a scalemate ; they preserve the overall structure of the formula 3 | """ 4 | 5 | class Scales: 6 | def __add__(self, other): 7 | if isinstance(other, ListScales): 8 | return other + self 9 | else: 10 | return ListScales([self, other]) 11 | 12 | class SimpleScales(Scales): 13 | """ 14 | This class uses the method of "alternative_to" to generate scalemates. It only looks at the type of the prejacent to find its scalemates. 15 | As such, it cannot do content-based alternative subsitution (e.g. "more than 6" to "more than 7", since both have the same type) 16 | But it is easy to define since it only demands its scales passed as simple list 17 | """ 18 | 19 | def __init__(self, scales): 20 | """ 21 | Arguments: 22 | - scales (list[set[type]]) : a list of scales (seen as sets of types) 23 | """ 24 | self.scales = scales 25 | 26 | 27 | def alternatives_to(self, prejacents): 28 | """ 29 | From a list of prejacents assumed to have the same root operator Op, compute a set of alternatives replacing Op with scalemates 30 | 31 | Arguments: 32 | - prejacents (list[Formula]) : a list of formulas 33 | """ 34 | if prejacents: 35 | # Find the scalemates of the prejacent 36 | example = prejacents[0] # take the first element to find the type of the formulas 37 | rel_scale = set(type_f for s in self.scales if any(isinstance(example, type_f) for type_f in s) 38 | for type_f in s if not isinstance(example, type_f)) 39 | 40 | # we now need to include scalar replacements 41 | scalar_alts = [] 42 | for scale_mate in rel_scale: 43 | for alt_root_fixed in prejacents: 44 | scalar_alts.append(scale_mate.alternative_to(alt_root_fixed)) 45 | return scalar_alts 46 | else: 47 | return [] 48 | 49 | def __repr__(self): 50 | return "SimpleScales({scales})".format(self.scales.__repr__()) 51 | 52 | class ListScales(Scales): 53 | """ 54 | This class takes a list of scales and apply all of them 55 | """ 56 | def __init__(self, scales): 57 | self.scales = scales 58 | 59 | def alternatives_to(self, prejacents): 60 | return [alt for scale in self.scales for alt in scale.alternatives_to(prejacents)] 61 | 62 | def __add__(self, other): 63 | if isinstance(other, ListScales): 64 | return ListScales(self.scales + other.scales) 65 | else: 66 | return ListScales(self.scales + [other]) -------------------------------------------------------------------------------- /exh/utils/__init__.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from IPython.display import Math, display, HTML 3 | import itertools 4 | 5 | def getAssignment(n): 6 | """Returns all possible assignment of values to n independent boolean variables""" 7 | iterator = [np.array(2**(n-i-1)*(2**(i) * [False] + 2**(i) * [True]), dtype = "bool") for i in range(n)] 8 | return np.transpose(np.stack(iterator)) 9 | 10 | def entails(a, b): 11 | return np.all(np.logical_or(np.logical_not(a), b)) 12 | 13 | def remove_doubles(fs): 14 | """Returns a list of elements from iterable fs, without double values""" 15 | 16 | toReturn = [] 17 | 18 | for f in fs: 19 | if all(f != g for g in toReturn): 20 | toReturn.append(f) 21 | 22 | return toReturn 23 | 24 | def get(array, index_tuple): 25 | """Get value from multi-dimensional array "array" at indices specified by tuple "index_tuple" """ 26 | to_return = array 27 | 28 | for index in index_tuple: 29 | to_return = to_return[index] 30 | 31 | return to_return 32 | 33 | def add_functions_as_methods(fs): 34 | def decorator(Class): 35 | for f in fs: 36 | setattr(Class, f.__name__, f) 37 | return Class 38 | return decorator 39 | 40 | def jprint(*args): 41 | """Replacement for print in IPython""" 42 | display(HTML(" ".join(list(map(str, args))))) 43 | 44 | 45 | def automatic_var_names(): 46 | """Generator of default variable names""" 47 | typical_names = ["x{}", "y{}", "z{}"] 48 | 49 | for x in itertools.chain([""], itertools.count()): 50 | for var in typical_names: 51 | yield var.format(x) 52 | -------------------------------------------------------------------------------- /exh/utils/table.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from IPython.core.display import display, HTML 3 | 4 | 5 | def to_str_list(list_vars): 6 | return list(map(str, list_vars)) 7 | 8 | class Table: 9 | """ 10 | Helper class to draw tables in HTML and plain text 11 | 12 | Attributes: 13 | options (dict) 14 | html (bool) -- whether to use HTML display or not 15 | char_col -- in plain display, character for plain column 16 | char_bold_col -- in plain display, character for emphasized column 17 | char_line -- in plain display, character for line 18 | row_lines -- in plain display, whether to insert lines between every row 19 | 20 | header (list[str]) -- list of cells in header 21 | rows (list[list]) -- list of rows ; each row is either a list of strings of the constant HLINE (for horizontal lines) 22 | strong_col (list[int]) -- list of indices of vertical lines to display bold (0 means before first column, n_cols means after last_column) 23 | """ 24 | 25 | HLINE = 0 # Constant to reprensent line in 26 | style =""" 27 | border: 1px solid black; 28 | border-collapse: collapse; 29 | font-weight: normal; 30 | """ 31 | 32 | def __init__(self, **kwargs): 33 | """kwargs update class options""" 34 | 35 | self.options = {"html": True, "char_col": "|", "char_bold_col": "#", "char_line": "-", "row_lines": False} 36 | self.options.update(kwargs) 37 | 38 | self.rows = [] 39 | self.strong_cols = [] 40 | self.header = None 41 | 42 | 43 | 44 | 45 | def set_header(self, list_header): 46 | """ Set table header to "list_header" argument """ 47 | self.header = to_str_list(list_header) 48 | 49 | def add_row(self, row): 50 | self.rows.append(to_str_list(row)) 51 | if self.options["row_lines"]: 52 | self.insert_hline() 53 | 54 | def set_strong_col(self, i): 55 | # 0 means leftmost border ; ncols + 1 rightmost border 56 | self.strong_cols.append(i) 57 | 58 | def insert_hline(self): 59 | self.rows.append(Table.HLINE) 60 | 61 | 62 | 63 | 64 | def print(self, html = None): 65 | if html is None: 66 | html = self.options["html"] 67 | 68 | if html: 69 | self.print_html() 70 | else: 71 | self.print_plain() 72 | 73 | def print_html(self): 74 | 75 | def cell(i, content): 76 | style = "" 77 | if i in self.strong_cols: 78 | style += "border-left-style: double !important;" 79 | if i+1 in self.strong_cols: 80 | style += "border-right-style: double !important;" 81 | 82 | self.add(''.format(style)) 83 | self.add(content) 84 | self.add("") 85 | 86 | self.cache = "" 87 | 88 | self.add(''.format(Table.style)) 89 | 90 | if self.header is not None: 91 | 92 | self.add("") 93 | self.add("") 94 | for t in enumerate(self.header): 95 | cell(*t) 96 | self.add("") 97 | self.add("") 98 | 99 | 100 | for row in self.rows: 101 | if isinstance(row, list): 102 | self.add("") 103 | for t in enumerate(row): 104 | cell(*t) 105 | self.add("") 106 | 107 | self.add("
") 108 | 109 | display(HTML(self.cache)) 110 | 111 | def print_plain(self): 112 | self.cache = "" 113 | 114 | def pad(string, width): 115 | l = width - len(string) 116 | return (l//2 + l % 2) * " " + string + (l//2) * " " 117 | 118 | def print_row(strings, widths): 119 | for i, (string, width) in enumerate(zip(strings, widths)): 120 | if i in self.strong_cols: 121 | self.add(self.options["char_bold_col"]) 122 | else: 123 | self.add(self.options["char_col"]) 124 | 125 | self.add(pad(string, width)) 126 | 127 | self.add(self.options["char_col"]) 128 | self.add("\n") 129 | 130 | def print_line(widths): 131 | self.add((sum(widths) + len(widths) + 1) * self.options["char_line"] + "\n") 132 | 133 | rows_and_headers = ([self.header] if self.header is not None else []) + [row for row in self.rows if isinstance(row, list)] 134 | widths = [max(len(row[i]) for row in rows_and_headers) + 2 for i in range(len(rows_and_headers[0]))] 135 | 136 | print_row(self.header, widths) 137 | print_line(widths) 138 | 139 | for row in self.rows: 140 | if isinstance(row, list): 141 | print_row(row, widths) 142 | 143 | print(self.cache) 144 | #self.rows_and_headers 145 | 146 | def add(self, string): 147 | self.cache += string -------------------------------------------------------------------------------- /meta.yaml: -------------------------------------------------------------------------------- 1 | {% set name = "exh" %} 2 | {% set version = "0.1" %} 3 | 4 | package: 5 | name: "{{ Exh|exh }}" 6 | version: "{{ 0.1 }}" 7 | 8 | source: 9 | git_url: https://github.com/KenyC/Exh 10 | git_rev: v0.1 11 | 12 | build: 13 | number: 0 14 | script: "{{ PYTHON }} -m pip install . --no-deps --ignore-installed -vv " 15 | 16 | requirements: 17 | build: 18 | - python 19 | - setuptools 20 | 21 | run: 22 | - python 23 | - numpy x.x 24 | - IPython 25 | 26 | 27 | 28 | about: 29 | home: https://github.com/KenyC/Exh 30 | license: MIT 31 | license_family: MIT 32 | summary: Computes innocent exclusion/inclusions exhaustivity using minimal worlds 33 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # Inside of setup.cfg 2 | [metadata] 3 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import exh 2 | from setuptools import setup 3 | 4 | long_desc = """\ 5 | Exh 6 | =========================== 7 | 8 | ``Exh`` is a package for computing the exhaustification of logical formulas. 9 | You can write formulas in a convenient legible syntax. You can compute alternatives automatically. 10 | You can compute innocently excludable and includable alternatives. 11 | 12 | A introductory tutorial can be found [here](https://github.com/KenyC/Exh/blob/master/examples/tutorial/Tutorial.ipynb). 13 | Other tutorials covering more advanced features and more complicated examples are available in [the `examples` folder of the GitHub repository](https://github.com/KenyC/Exh/tree/master/examples). 14 | """ 15 | 16 | setup( 17 | name = "Exh", 18 | version = exh.__version__, 19 | description = "Computes innocent exclusion/inclusions exhaustivity", 20 | long_description = long_desc, 21 | long_description_content_type = "text/markdown", 22 | url = "http://github.com/KenyC/Exh", 23 | author = "Keny Chatain", 24 | author_email = "kchatain@mit.edu", 25 | license = "MIT", 26 | packages = [ 27 | "exh", 28 | "exh.model", 29 | "exh.utils", 30 | "exh.prop", 31 | "exh.exts.gq", 32 | "exh.exts.focus", 33 | "exh.fol" 34 | ], 35 | install_requires = [ 36 | "numpy", 37 | "IPython" 38 | ], 39 | zip_safe = True 40 | ) -------------------------------------------------------------------------------- /tests/evaluate.py: -------------------------------------------------------------------------------- 1 | # %% 2 | import sys 3 | # insert at 1, 0 is the script path (or '' in REPL) 4 | sys.path.insert(1, '../') 5 | 6 | # %% 7 | from exh import * 8 | 9 | d = Pred(name = "d", depends = "x", domains = [D5]) 10 | e = Pred(name = "e", depends = ["x", "y"], domains = [Domain(4), D3]) 11 | 12 | assert(d.evaluate_aux( 13 | vm = d.vars(), 14 | assignment = np.array([[True, False, True, False, True]]), 15 | variables = {"x" : 4} 16 | )) 17 | assert(not d.evaluate_aux( 18 | vm = d.vars(), 19 | assignment = np.array([[True, False, True, False, True]]), 20 | variables = {"x" : 1} 21 | )) 22 | 23 | # d = [True, False, True, False, True]) 24 | 25 | # %% 26 | assert((Ex_in_(D5) > d).evaluate( 27 | d = [True, False, True, False, True] 28 | )) 29 | assert(not (Ex_in_(D4) > Ay_in_(D3) > e).evaluate( 30 | e = [ 31 | [False, True, True], 32 | [True, False, True], 33 | [True, False, True], 34 | [True, False, True] 35 | ] 36 | )) 37 | 38 | 39 | 40 | 41 | # %% 42 | 43 | -------------------------------------------------------------------------------- /tests/focus.py: -------------------------------------------------------------------------------- 1 | # %% 2 | import sys 3 | # insert at 1, 0 is the script path (or '' in REPL) 4 | sys.path.insert(1, '../') 5 | 6 | # %% 7 | from exh import * 8 | from exh.exts.focus import * 9 | 10 | 11 | # %% 12 | """ 13 | # Construction and evaluation 14 | """ 15 | 16 | f = Focus(a | b, [b]) 17 | 18 | assignment = np.array([ 19 | [True, True], 20 | [True, False], 21 | [False, True], 22 | [False, False] 23 | ]) 24 | 25 | assert((f.evaluate_aux( 26 | vm = f.vars(), 27 | assignment = assignment, 28 | variables = {} 29 | ) == 30 | (a | b).evaluate_aux( 31 | vm = f.vars(), 32 | assignment = assignment, 33 | variables = {} 34 | )).all() 35 | ) 36 | 37 | assert(not (f.evaluate_aux( 38 | vm = f.vars(), 39 | assignment = assignment, 40 | variables = {} 41 | ) == 42 | (a & b).evaluate_aux( 43 | vm = f.vars(), 44 | assignment = assignment, 45 | variables = {} 46 | )).all() 47 | ) 48 | 49 | # %% 50 | """ 51 | # Alternative calculation 52 | """ 53 | 54 | scale = FocusScales() 55 | 56 | 57 | assert( 58 | scale.alternatives_to([f]) 59 | == [b] 60 | ) 61 | 62 | assert( 63 | scale.alternatives_to([f]) 64 | != [a | b] 65 | ) 66 | 67 | # %% 68 | """ 69 | # Exhaustification 70 | ## Simple cases 71 | """ 72 | 73 | # Test if FocusScales is now default (importing the extention should make it default) 74 | assert(any(isinstance(s, FocusScales) for s in Exh(a).e.scales.scales)) 75 | 76 | # %% 77 | 78 | g = Focus(a, alts = [b, c]) 79 | exhg = Exh(g, scales = FocusScales()) 80 | 81 | assert( 82 | exhg.alts == [ 83 | g, 84 | b, 85 | c 86 | ] 87 | ) 88 | 89 | universe = Universe(f = exhg) 90 | assert(universe.equivalent(exhg, a & ~b & ~c)) 91 | 92 | # %% 93 | 94 | g = Focus(a | b, alts = [a & b]) 95 | exhg = Exh(g, scales = FocusScales()) 96 | 97 | assert( 98 | exhg.alts == [ 99 | g, 100 | Focus(a, alts=[a & b]), 101 | Focus(b, alts=[a & b]), 102 | a & b 103 | ] 104 | ) 105 | 106 | universe = Universe(f = exhg) 107 | assert(universe.equivalent(exhg, (a | b) & ~(a & b))) 108 | 109 | 110 | # %% 111 | """ 112 | ## Exhaustification across operators 113 | """ 114 | 115 | apple = Pred(name = "A", depends = ["x"]) 116 | cantaloupe = Pred(name = "C", depends = ["x"]) 117 | 118 | h = Ex > Focus(apple, alts = [cantaloupe]) 119 | 120 | exhh = Exh(h, scales = FocusScales()) 121 | 122 | complex_universe = Universe(f = h) 123 | 124 | assert(complex_universe.equivalent( 125 | exhh, 126 | (Ex > apple) & ~(Ex > cantaloupe) 127 | )) 128 | 129 | assert(not complex_universe.equivalent( 130 | exhh, 131 | Ex > apple 132 | )) 133 | 134 | # %% 135 | """ 136 | Not A 137 | """ 138 | 139 | prop_universe = Universe(fs = [a, b, c]) 140 | 141 | exhf = Exh(~Focus(a, alts = [c]), scales = FocusScales(), subst = False) 142 | 143 | assert(exhf.alts == [~Focus(a, alts = [c]), ~c]) 144 | 145 | assert(prop_universe.equivalent( 146 | exhf, 147 | ~a & c 148 | )) 149 | 150 | # %% 151 | """ 152 | Recursive exh 153 | """ 154 | 155 | prej = Focus(a, alts = [c]) 156 | fst_exh = Exh(prej, scales = FocusScales()) 157 | snd_exh = Exh(fst_exh, scales = FocusScales ()) 158 | 159 | assert( 160 | fst_exh.alts == 161 | [prej, c] 162 | ) 163 | assert( 164 | snd_exh.alts == 165 | [Exh(prej), Exh(c)] 166 | ) 167 | 168 | assert(prop_universe.equivalent( 169 | snd_exh, 170 | a & ~c 171 | )) 172 | 173 | 174 | # %% 175 | -------------------------------------------------------------------------------- /tests/main.py: -------------------------------------------------------------------------------- 1 | # This file must run fully without exceptions 2 | # %% 3 | import sys 4 | # insert at 1, 0 is the script path (or '' in REPL) 5 | sys.path.insert(1, '../') 6 | 7 | # %% 8 | hashes = "#####################################################" 9 | def header(title): 10 | title = " {} ".format(title) 11 | padding_size = len(hashes) - len(title) 12 | print(hashes[:padding_size // 2] + title + hashes[-padding_size // 2:]) 13 | 14 | 15 | # %% 16 | header("IMPORTS") 17 | from exh import * 18 | from exh.model import options # for fancy displays 19 | 20 | options.latex_display = False 21 | 22 | 23 | # %% 24 | header("FORMULA CONSTRUCTION AND DISPLAY") 25 | 26 | # Index-less formulas, testing increment index 27 | last_index = Pred().idx 28 | assert(Pred(name = "george").idx == last_index + 1) 29 | 30 | # Index formula 31 | Pred(4, name = "d") 32 | Pred(7) # "name" is optional, "name" makes prettier display with print and helps for evaluation of formulas 33 | Pred(2, name = "er", depends = 1) 34 | Pred(2, name = "er", depends = ["x", "y"]) 35 | Pred(2, name = "er", depends = "x") 36 | 37 | 38 | a | b 39 | a & b 40 | print(a | (~a & b)) 41 | 42 | 43 | d = Pred(4, name = "d") 44 | e = Pred(5, name = "e") 45 | 46 | d.depends(1) # Making a unary predicate 47 | d.depends("x") 48 | e.depends("x", "y") 49 | 50 | 51 | f4 = A("x") > d 52 | print(f4) 53 | A("x") > Ey > e 54 | f5 = Ax > Ey > e("y", "x") 55 | 56 | 57 | # names for nameless predicates 58 | assert(Pred(7).display_aux(False) == "A7") 59 | assert(a.display_aux(False) == "a") 60 | 61 | # Display test 62 | options.latex_display = True 63 | assert(f4.display() == r"$\forall x, d(x)$") 64 | options.latex_display = False 65 | assert(f4.display() == "\u2200 x, d(x)") 66 | 67 | # %% 68 | header("FORMULA METHODS") 69 | 70 | # Simple prop logic formul 71 | assert(not (a == b)) 72 | assert((a | b) == (b | a)) 73 | assert(not ((a | b) == (b | c))) 74 | assert((a | b) != (a & b)) 75 | 76 | # FOL formulas 77 | assert((Ex > Pred(4, "d1", depends = "x")) == (Ex > d)) 78 | 79 | 80 | # %% 81 | header("EVALUATION") 82 | # Propositional logic 83 | 84 | assert((a | b).evaluate(a = True, b = False)) 85 | assert((a | (b & ~a)).evaluate(a = True, b = False)) 86 | 87 | # %% 88 | # FOL 89 | 90 | assert((Ax > d("x")).evaluate(d = [True, True, True])) 91 | 92 | assert((Ax > Ey > e("y", "x")).evaluate(e = [ 93 | [True, True, False], 94 | [False, False, False], 95 | [False, False, True] 96 | ])) 97 | 98 | 99 | 100 | 101 | 102 | # %% 103 | header("UNIVERSE") 104 | 105 | prop_universe = Universe(fs = [a, b, c]) 106 | assert(prop_universe.n == 3) 107 | 108 | prop_universe = Universe(fs = [a & b, b | ~ a]) 109 | assert(prop_universe.n == 2) 110 | 111 | import exh.model.options as options 112 | dom_quant = options.dom_quant 113 | 114 | prop_universe = Universe(fs = [Ax > d("x"), Ax > Ey > e("y", "x")]) 115 | assert(prop_universe.n == dom_quant + dom_quant**2) 116 | 117 | 118 | prop_universe = Universe(fs = [a, b, c]) 119 | result = np.array( 120 | [[False, False, True], 121 | [False, True, False], 122 | [False, True, True], 123 | [ True, True, False], 124 | [False, False, True], 125 | [False, True, True], 126 | [False, True, True], 127 | [ True, True, True]] 128 | ) 129 | assert(np.all(prop_universe.evaluate(a & b, a | b, ~a | c ) == result)) 130 | 131 | 132 | 133 | value = prop_universe.consistent(a | b, 134 | ~a | ~b, 135 | b | ~a, 136 | a | ~b) 137 | assert(not value) 138 | 139 | value = prop_universe.entails(a | ~ b & c, 140 | ~(b & ~a) ) 141 | # NB: "a | b & c" is parsed as "a | (b & c)" 142 | 143 | assert(value) 144 | 145 | # De Morgan's law 146 | value = prop_universe.equivalent(~a | ~c, 147 | ~(a & c) ) 148 | assert(value) 149 | 150 | quant_universe = Universe(fs = [Ex > d]) 151 | quant_universe.truth_table((Ex > d) & ~(Ax > d), html = False) 152 | 153 | 154 | # %% 155 | header("EXHAUSTIFICATION") 156 | 157 | 158 | e = Exh(Ex > d, alts = [Ax > d]) 159 | e1 = Exh(a | b, alts = [a, b, a & b]) 160 | 161 | # Test syntactic equality 162 | assert(e1 == Exh(b | a, alts = [])) 163 | assert(e1 != Exh(b | c, alts = [])) 164 | 165 | 166 | assert( 167 | prop_universe.equivalent( 168 | e1, 169 | (a | b) & ~(a & b) 170 | )) 171 | 172 | 173 | 174 | assert( 175 | quant_universe.equivalent( 176 | e, 177 | (Ex > d) & ~(Ax > d) 178 | )) 179 | 180 | 181 | 182 | 183 | p1 = Pred(5, name = "p1", depends = ["x"]) 184 | p2 = Pred(6, name = "p2", depends = ["x"]) 185 | 186 | prejacent = Ax > p1 | p2 187 | 188 | exh = Exh(prejacent, alts = [Ax > p1 & p2, 189 | Ax > p1, 190 | Ax > p2, 191 | Ex > p1 & p2, 192 | Ex > p1, 193 | Ex > p2]) 194 | 195 | quant_universe = Universe(f = prejacent) 196 | 197 | assert(quant_universe.equivalent(exh, Ax > (p1 | p2) & ~ (p1 & p2))) 198 | 199 | 200 | exh2 = Exh(prejacent, alts = [Ax > p1 & p2, 201 | Ax > p1, 202 | Ax > p2]) 203 | 204 | assert(quant_universe.equivalent(exh2, prejacent & (Ex > ~p1) & (Ex > ~p2))) 205 | assert(not quant_universe.equivalent(exh2, prejacent & (Ex > p1) & (Ex > p2))) 206 | 207 | 208 | # Two implicatures: 1) that someone did only p1, 2) that someone did only p2 209 | 210 | # Dealing with free_vars 211 | Exh(p1, alts = [p1, p2]) 212 | Ex > Exh(p1, alts = [p1, p2]) 213 | 214 | 215 | # ### Automatic alternatives 216 | 217 | # When not specified, the alternatives to the prejacent are computed in a Katzirian manner: all alternatives are considered that can be obtained from the prejacent by sub-constituent and scalar substitutions. Which alternatives were obtained by this process can be probed after the object is constructed. 218 | 219 | 220 | 221 | h2 = Exh (a | b | c) 222 | options.latex_display = False 223 | print("Computed alternatives", h2.alts) 224 | 225 | assert(len(h2.alts) == 4 + # subtree1 226 | 1 + # subtree2 227 | 2 * 4 * 1) # alts for each subtree 228 | 229 | # It is possible to specify the scales, to decide whether to allow substitution by sub-consituent. 230 | 231 | 232 | 233 | h3 = Exh(a | b | c, subst = False) # no replacement by sub-constituent allowed (only scalar alternatives) 234 | assert(len(h3.alts) == 2 * 2) # alts for each subtree 235 | 236 | 237 | h4 = Exh(Ex > p1 | p2, scales = [{Or, And}]) # no "some", "all" scale 238 | assert(len(h4.alts) == 2 + 2) 239 | 240 | # Dealing with free_vars without specified alternatives 241 | Exh(p1 | p2) 242 | 243 | # Dealing with recursive exh when the embedded exh has stipulated alternatives 244 | h5 = Exh(Exh(a, alts = [a, b])) 245 | assert(Exh(b, alts = [a]) in h5.alts) 246 | assert(prop_universe.equivalent(h5, a & ~b)) 247 | 248 | 249 | 250 | # %% 251 | header("FREE CHOICE REPLICATION") 252 | 253 | 254 | 255 | prejacent = Ex > p1 | p2 # The existential quantifier can be thought of as an existential modal 256 | 257 | free_choice = Exh(Exh(prejacent, scales = []), scales = []) 258 | # The result seems right ; let's check entailments 259 | 260 | fc_universe = Universe(f = free_choice) # one can use f you only have one formula 261 | assert(fc_universe.entails(free_choice, Ex > p1)) 262 | assert(fc_universe.entails(free_choice, Ex > p2)) 263 | 264 | 265 | # We have weak FC ; what about strong free-choice? 266 | assert(not fc_universe.entails(free_choice, Ex > p1 & ~p2)) # We don't have strong FC 267 | assert(fc_universe.consistent(free_choice, Ax > p1 & p2)) 268 | 269 | # Can we derive strong FC with universal scalar alternatives? 270 | someall = [{Existential, Universal}] # we only allow some/all scale, not or/and scale 271 | fc_2 = Exh(Exh(prejacent, scales = someall), scales = someall) 272 | 273 | 274 | assert(not fc_universe.entails(fc_2, Ex > p1 & ~p2)) # We don't have strong FC 275 | assert(not fc_universe.consistent(fc_2, Ax > p1 & p2)) 276 | 277 | # %% 278 | header("INNOCENT INCLUSION") 279 | 280 | 281 | conj = Exh(a | b, scales = [], ii = True) # ii parameter for innocent inclusion 282 | assert(prop_universe.equivalent(conj, a & b)) 283 | 284 | fc_ii = Exh(Ex > p1 | p2, ii = True) # Automatic alternatives 285 | 286 | assert(fc_universe.entails(fc_ii, Ex > p1 & ~p2)) 287 | assert(fc_universe.entails(fc_ii, Ex > p2 & ~p1)) 288 | assert(not fc_universe.consistent(fc_ii, Ex > p2 & p1)) 289 | 290 | 291 | # %% 292 | 293 | header("EXTENSION : GENERALIZED QUANTIFIER") 294 | 295 | # Import and check import 296 | from exh.exts.gq import * 297 | Mx 298 | 299 | 300 | 301 | # Construct formula 302 | f = Mx > p1 303 | 304 | # Check if the options set for the main module also apply to the extension 305 | options.latex_display = True 306 | assert(f.display() == r"$\text{Most } x, p1(x)$") 307 | options.latex_display = False 308 | assert(f.display() == "Most x, p1(x)") 309 | 310 | # Test formula construction 311 | Mx > p1 312 | Exactly(3, "x") > p1 313 | MoreThan(4, "x") > p1 | p2 314 | 315 | 316 | 317 | 318 | # %% 319 | 320 | 321 | -------------------------------------------------------------------------------- /tests/subdomain.py: -------------------------------------------------------------------------------- 1 | # %% 2 | import sys 3 | # insert at 1, 0 is the script path (or '' in REPL) 4 | sys.path.insert(1, '../') 5 | 6 | # %% 7 | from exh import * 8 | from exh.exts.subdomain import * 9 | from exh.model import options # for fancy displays 10 | 11 | options.latex_display = False 12 | 13 | 14 | d = Pred(name = "d", depends = "x") 15 | mask = np.array([True, False, True]) 16 | f = SubdomainExistential("x", d, mask = mask) 17 | 18 | assert(f.evaluate(d = [True, False, False])) 19 | assert(not f.evaluate(d = [False, True, False])) 20 | 21 | f = Ec_x > d 22 | assert(f.evaluate(d = [True, False, False])) 23 | assert(not f.evaluate(d = [False, False, False])) 24 | 25 | # %% 26 | assert(f.display_aux(latex = False) == "\u2203\u02da\u02da\u02da x, d(x)") 27 | 28 | # %% Test alternative formation 29 | 30 | masks_alt = np.stack([alt.mask for alt in f.all_alternatives()]) 31 | # print(masks_alt) 32 | assert(np.all(masks_alt == [ 33 | [ True, False, False], 34 | [False, True, False], 35 | [ True, True, False], 36 | [False, False, True], 37 | [ True, False, True], 38 | [False, True, True] 39 | ])) 40 | 41 | f_ = SubdomainExistential("x", d, mask = np.array([True, False, True])) 42 | masks_alt = np.stack([alt.mask for alt in f_.lower_alternatives()]) 43 | # print(masks_alt) 44 | assert(np.all(masks_alt == [ 45 | [ True, False, False], 46 | [False, False, True] 47 | ])) 48 | 49 | # %% 50 | g = Exh(f, alts = list(f.lower_alternatives()), ii = True) 51 | h = Exh(f, alts = list(f.lower_alternatives()), ii = False) 52 | universe = Universe(f = g) 53 | assert(universe.equivalent(g, Ax > d)) 54 | assert(universe.equivalent(h, f)) 55 | 56 | 57 | g = Exh(f, scales = sub_scale, ii = True) 58 | h = Exh(f, scales = dom_scale, ii = False) 59 | g.diagnose(print) 60 | universe = Universe(f = g) 61 | assert(universe.equivalent(g, Ax > d)) 62 | assert(universe.equivalent(h, f)) 63 | 64 | 65 | 66 | # %% 67 | 68 | -------------------------------------------------------------------------------- /tests/test: -------------------------------------------------------------------------------- 1 | for pyfile in *.py; do 2 | echo -n $pyfile ........... 3 | if python "$pyfile" >/dev/null; then 4 | echo Pass! 5 | else 6 | echo ">>>>>>>>>>>>>>" FAIL ON $pyfile "<<<<<<<<<<<<<<<<<<<" 7 | exit 1 8 | fi 9 | done -------------------------------------------------------------------------------- /tests/var_managers.py: -------------------------------------------------------------------------------- 1 | # %% 2 | import sys 3 | # insert at 1, 0 is the script path (or '' in REPL) 4 | sys.path.insert(1, '../') 5 | 6 | # %% 7 | from exh.model.vars import * 8 | 9 | vm = VarManager( 10 | {2: [3, 4], 5: [6, 3]}, 11 | {2: "a", 5: "b"} 12 | ) 13 | 14 | assert(vm.n == 30) 15 | assert(vm.memory == [12, 18]) 16 | assert(vm.pred_to_vm_index == {2: 0, 5: 1}) 17 | assert(vm.offset == [0, 12]) 18 | assert(vm.index(5, (2, 1)) == 3 * 4 + 2 + 6 * 1) 19 | 20 | 21 | 22 | 23 | # %% 24 | 25 | -------------------------------------------------------------------------------- /utils/py2jp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | """ 3 | From https://github.com/kiwi0fruit/ipynb-py-convert/blob/master/ipynb_py_convert/__main__.py 4 | 5 | The MIT License (MIT) 6 | 7 | Copyright (c) 2018 Noj Vek nojvek@gmail.com 8 | (c) 2020 Keny Chatain keny.chatain@gmail.com 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | """ 28 | import json 29 | import sys 30 | import argparse 31 | import re 32 | from os import path 33 | 34 | header_comment = "# %%" 35 | md_content = re.compile(r'^\s*\n"""\s*\n((.|\n)+)"""\s*(\n)+') 36 | # md_content = re.compile(r'"""\n(.+)"""\n.+') 37 | 38 | 39 | def nb2py(notebook): 40 | result = [] 41 | cells = notebook['cells'] 42 | 43 | for cell in cells: 44 | cell_type = cell['cell_type'] 45 | 46 | if cell_type == 'markdown': 47 | result.append('%s"""\n%s\n"""'% 48 | (header_comment, ''.join(cell['source']))) 49 | 50 | if cell_type == 'code': 51 | result.append("%s%s" % (header_comment, ''.join(cell['source']))) 52 | 53 | return '\n\n'.join(result) 54 | 55 | 56 | def py2nb(py_str): 57 | # remove leading header comment 58 | if py_str.startswith(header_comment): 59 | py_str = py_str[len(header_comment):] 60 | 61 | cells = [] 62 | chunks = py_str.split('\n' + header_comment) 63 | 64 | for chunk in chunks: 65 | 66 | match = md_content.search(chunk) 67 | 68 | if match: 69 | cells.append({ 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": match.group(1).splitlines(True) 73 | }) 74 | chunk = chunk[len(match.group(0)):] 75 | 76 | 77 | cells.append({ 78 | 'cell_type': "code", 79 | 'metadata': {}, 80 | 'source': chunk.splitlines(True), 81 | 'outputs': [], 82 | 'execution_count': None 83 | }) 84 | 85 | 86 | 87 | notebook = { 88 | 'cells': cells, 89 | 'metadata': { 90 | 'anaconda-cloud': {}, 91 | 'kernelspec': { 92 | 'display_name': 'Python 3', 93 | 'language': 'python', 94 | 'name': 'python3'}, 95 | 'language_info': { 96 | 'codemirror_mode': {'name': 'ipython', 'version': 3}, 97 | 'file_extension': '.py', 98 | 'mimetype': 'text/x-python', 99 | 'name': 'python', 100 | 'nbconvert_exporter': 'python', 101 | 'pygments_lexer': 'ipython3', 102 | 'version': '3.6.1'}}, 103 | 'nbformat': 4, 104 | 'nbformat_minor': 4 105 | } 106 | 107 | return notebook 108 | 109 | 110 | def convert(in_file, out_file, py_in = True): 111 | if py_in: 112 | with open(in_file, 'r', encoding = 'utf-8') as f: 113 | py_str = f.read() 114 | 115 | notebook = py2nb(py_str) 116 | 117 | with open(out_file, 'w', encoding = 'utf-8') as f: 118 | json.dump(notebook, f, indent = 2) 119 | else: 120 | with open(in_file, 'r', encoding = 'utf-8') as f: 121 | notebook = json.load(f) 122 | 123 | py_str = nb2py(notebook) 124 | 125 | with open(out_file, 'w', encoding = 'utf-8') as f: 126 | f.write(py_str) 127 | 128 | 129 | 130 | if __name__ == '__main__': 131 | parser = argparse.ArgumentParser( 132 | description = 'Convert from .py to .ipynb and vice-versa. Desired conversion is inferred from extension.' 133 | ) 134 | 135 | parser.add_argument('filename', help = 'File name of input') 136 | parser.add_argument('--out', help = "File name of output (default: blabla.py <-> blabla.ipynb)") 137 | 138 | args = parser.parse_args() 139 | 140 | basename, extension = path.splitext(args.filename) 141 | pyfile = extension != ".ipynb" 142 | 143 | if args.out is not None: 144 | output_name = args.out 145 | elif pyfile: 146 | output_name = basename + ".ipynb" 147 | else: 148 | output_name = basename + ".py" 149 | 150 | convert ( 151 | in_file = args.filename, 152 | out_file = output_name, 153 | py_in = pyfile 154 | ) 155 | 156 | print("=> {}".format(output_name)) 157 | -------------------------------------------------------------------------------- /utils/runnb.py: -------------------------------------------------------------------------------- 1 | # ! python 2 | # coding: utf-8 3 | """ 4 | Adapted from: https://ogden.eu/run-notebooks 5 | """ 6 | import os 7 | import argparse 8 | import glob 9 | 10 | import nbformat 11 | from nbconvert.preprocessors import ExecutePreprocessor 12 | from nbconvert.preprocessors.execute import CellExecutionError 13 | 14 | # Parse args 15 | parser = argparse.ArgumentParser( 16 | description = "Runs a set of Jupyter notebooks." 17 | ) 18 | 19 | file_text = """ Notebook file(s) to be run, e.g. '*.ipynb' (default), 20 | 'my_nb1.ipynb', 'my_nb1.ipynb my_nb2.ipynb', 'my_dir/*.ipynb' 21 | """ 22 | 23 | parser.add_argument('file_list', metavar = 'F', type = str, nargs = '*', help = file_text) 24 | parser.add_argument( 25 | '-t', '--timeout', 26 | help = 'Length of time (in secs) a cell can run before raising TimeoutError (default 600).', 27 | default = 600, 28 | required= False 29 | ) 30 | parser.add_argument( 31 | '-p', '--run-path', 32 | help = "The path the notebook will be run from (default pwd).", 33 | default = '.', 34 | required = False 35 | ) 36 | parser.add_argument( 37 | '--kernel', 38 | help = "Which IPython kernel to run (default python3).", 39 | default = 'python3', 40 | required = False 41 | ) 42 | parser.add_argument( 43 | "-i", '--inplace', 44 | help = "Whether existing notebooks, must be owerwritten (default False).", 45 | action = "store_true" 46 | ) 47 | args = parser.parse_args() 48 | print('Args:', args) 49 | if not args.file_list: # Default file_list 50 | args.file_list = glob.glob('*.ipynb') 51 | 52 | # Check list of notebooks 53 | notebooks = [] 54 | print('Notebooks to run:') 55 | for f in args.file_list: 56 | # Find notebooks but not notebooks previously output from this script 57 | if f.endswith('.ipynb') and not f.endswith('_out.ipynb'): 58 | print(" - ", f) 59 | notebooks.append(f[:-6]) # Want the filename without '.ipynb' 60 | 61 | # Execute notebooks and output 62 | num_notebooks = len(notebooks) 63 | print('*****') 64 | for i, n in enumerate(notebooks): 65 | 66 | n_out = n + '_out' if not args.inplace else n 67 | 68 | with open(n + '.ipynb') as f: 69 | nb = nbformat.read(f, as_version = 4) 70 | 71 | ep = ExecutePreprocessor(timeout = int(args.timeout), kernel_name = args.kernel) 72 | try: 73 | print('Running', n, ':', i, '/', num_notebooks) 74 | out = ep.preprocess(nb, {'metadata': {'path': args.run_path}}) 75 | except CellExecutionError as e: 76 | out = None 77 | msg = 'Error executing the notebook "%s".\n' % n 78 | msg += "{traceback}".format(traceback = e.traceback) 79 | # msg += 'See notebook "%s" for the traceback.' % n_out 80 | print(msg) 81 | except TimeoutError: 82 | msg = 'Timeout executing the notebook "%s".\n' % n 83 | print(msg) 84 | finally: 85 | # Write output file 86 | with open(n_out + '.ipynb', mode='wt') as f: 87 | nbformat.write(nb, f) 88 | 89 | --------------------------------------------------------------------------------