Showing preview only (782K chars total). Download the full file or copy to clipboard to get everything.
Repository: tulip-control/dd
Branch: main
Commit: 2596de454f95
Files: 68
Total size: 753.2 KB
Directory structure:
gitextract__x1aidr9/
├── .gitattributes
├── .github/
│ └── workflows/
│ ├── main.yml
│ └── setup_build_env.sh
├── .gitignore
├── AUTHORS
├── CHANGES.md
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── dd/
│ ├── __init__.py
│ ├── _abc.py
│ ├── _copy.py
│ ├── _parser.py
│ ├── _utils.py
│ ├── autoref.py
│ ├── bdd.py
│ ├── buddy.pyx
│ ├── buddy_.pxd
│ ├── c_sylvan.pxd
│ ├── cudd.pyx
│ ├── cudd_zdd.pyx
│ ├── dddmp.py
│ ├── mdd.py
│ ├── py.typed
│ └── sylvan.pyx
├── doc.md
├── download.py
├── examples/
│ ├── README.md
│ ├── _test_examples.py
│ ├── bdd_traversal.py
│ ├── boolean_satisfiability.py
│ ├── cudd_configure_reordering.py
│ ├── cudd_memory_limits.py
│ ├── cudd_statistics.py
│ ├── cudd_zdd.py
│ ├── good_vs_bad_variable_order.py
│ ├── install_dd_buddy.sh
│ ├── install_dd_cudd.sh
│ ├── install_dd_sylvan.sh
│ ├── json_example.py
│ ├── json_load.py
│ ├── np.py
│ ├── queens.py
│ ├── reachability.py
│ ├── reordering.py
│ ├── transfer_bdd.py
│ ├── variable_substitution.py
│ └── what_is_a_bdd.py
├── setup.py
└── tests/
├── .coveragerc
├── README.md
├── autoref_test.py
├── bdd_test.py
├── common.py
├── common_bdd.py
├── common_cudd.py
├── copy_test.py
├── cudd_test.py
├── cudd_zdd_test.py
├── dddmp_test.py
├── inspect_cython_signatures.py
├── iterative_recursive_flattener.py
├── mdd_test.py
├── parser_test.py
├── pytest.ini
├── regressions_test.py
└── sylvan_test.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
*.py eol=lf
*.pyx eol=lf
*.pxd eol=lf
*.c eol=lf
*.txt eol=lf
*.md eol=lf
*.yml eol=lf
* filter=trimWhitespace
================================================
FILE: .github/workflows/main.yml
================================================
---
# configuration for GitHub Actions
name: dd tests
on:
push:
pull_request:
schedule:
- cron: '37 5 5 * *'
jobs:
build:
name: Build
runs-on: ubuntu-24.04
strategy:
matrix:
python-version: [
'3.11',
'3.12',
'3.13',
'3.14',
]
steps:
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Prepare installation environment
run: |
./.github/workflows/setup_build_env.sh
- name: Install `dd`
run: |
set -o posix
echo 'Exported environment variables:'
export -p
export \
DD_FETCH=1 \
DD_CUDD=1 \
DD_CUDD_ZDD=1 \
DD_SYLVAN=1
pip install . \
--verbose \
--use-pep517 \
--no-build-isolation
- name: Install test dependencies
run: |
pip install pytest
- name: Run `dd` tests
run: |
set -o posix
echo 'Exported environment variables:'
export -p
# run tests
make test
- name: Run `dd` examples
run: |
pushd examples/
python _test_examples.py
popd
================================================
FILE: .github/workflows/setup_build_env.sh
================================================
#!/usr/bin/env bash
# Prepare environment for building `dd`.
set -x
set -e
sudo apt install \
graphviz
dot -V
pip install --upgrade \
pip \
setuptools \
wheel
# note that installing from `requirements.txt`
# would also install packages that
# may be absent from where `dd` will be installed
pip install cython
#
# install `sylvan`
# download
curl -L \
https://github.com/utwente-fmt/sylvan/tarball/v1.0.0 \
-o sylvan.tar.gz
# checksum
echo "9877fe07a8cfe9889152e29624a4c5b283\
cb34672ec524ccb3edb313b3057fbf8ef45622a4\
9796fae17aa24e0baea5ccfa18f1bc5923e3c552\
45ab3e3c1927c8 sylvan.tar.gz" | shasum -a 512 -c -
# unpack
mkdir sylvan
tar xzf sylvan.tar.gz -C sylvan --strip=1
pushd sylvan
autoreconf -fi
./configure
make
export LD_LIBRARY_PATH=`pwd`/src/.libs:$LD_LIBRARY_PATH
echo $LD_LIBRARY_PATH
if [[ -z "${DEPLOY_ENV}" ]]; then
# store values to use in later steps for
# environment variables
echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" \
>> $GITHUB_ENV
fi
popd
================================================
FILE: .gitignore
================================================
.coverage
.cache
.mypy_cache/
.pytype/
.pytest_cache/
todo.txt
dd/_version.py
cython_debug/*
cudd*/*
extern/
dd/CUDD_LICENSE
dd/GLIBC_COPYING.LIB
dd/GLIBC_LICENSES
dd/PYTHON_LICENSE
buddy-*/
sylvan/
.DS_Store
._.DS_Store
*.out
*_state_machine.py
*.c
__pycache__
*.pyc
*.whl
*.pdf
*.png
*.svg
*.txt
*.p
*.json
*.dddmp
*.so
*.html
*.tar.gz
dist/*
build/*
*.egg-info
*.swp
*.tags
*.xml
================================================
FILE: AUTHORS
================================================
People that have authored commits to `dd`.
Ioannis Filippidis
Sofie Haesaert
Scott C. Livingston
Mario Wenzel
================================================
FILE: CHANGES.md
================================================
# dd changelog
## 0.6.1
- DEP: rm module `dd._compat`
## 0.6.0
- REL: require Python >= 3.11
- REL: require `cython >= 3.0.0`
- REL: require `astutils >= 0.0.5`
- DEP: deprecate hidden module `dd._compat`
API:
- use TLA+ syntax for comments:
- `(* this is a doubly-delimited comment *)`
- `\* this is a trailing comment`
- use symbol `#` as operator that means
the logical negation of `<=>`.
The symbol `#` no longer signifies comments.
- return `list` of loaded roots of BDDs,
when loading BDDs from Pickle files in:
- `dd.autoref.BDD.load()`
- `dd.bdd.BDD.load()`
- in `dd.cudd`, `dd.cudd_zdd`
check available memory only in systems where
both `SC_PAGE_SIZE` and `SC_PHYS_PAGES` are
defined (read `sysconf(3)`).
- raise `ValueError` from:
- `dd.autoref.BDD.level_of_var()`
- `dd.bdd.BDD.level_of_var()`
whenever unknown values are given as arguments
- rename:
- method `dd.buddy.BDD.level()` to `dd.buddy.BDD.level_of_var()`
- method `dd.buddy.BDD.at_level()` to `dd.buddy.BDD.var_at_level()`
as the naming convention in other `dd` modules
- raise `ValueError` from:
- `dd.buddy.BDD.level_of_var()`
- `dd.buddy.BDD.var_at_level()`
whenever unknown values are given as arguments,
as done in other `dd` modules too
- several `assert` statements replaced by `raise`,
with more specific exceptions, e.g.,
`ValueError`, `TypeError`, `RuntimeError`
- strings returned by methods:
- `dd.cudd.Function.__repr__()`
- `dd.cudd_zdd.Function.__repr__()`
changed to follow specification of `object.__repr__()`
(delimited by `<` and `>`).
Now also includes the object `id` as `hex` number.
## 0.5.7
- require `pytest >= 4.6.11`, instead of `nose`, for Python 3.10 compatibility
- support for dumping and loading BDDs to and from JSON files
now requires Python 3
- test using GitHub Actions
API:
- return memory size in bytes from methods `dd.cudd.BDD.statistics` and
`dd.cudd_zdd.ZDD.statistics`
(the value of key `'mem'` in the returned `dict`)
- print used memory in bytes in the methods `dd.cudd.BDD.__str__` and
`dd.cudd_zdd.ZDD.__str__`
- remove the now obsolete constants `dd.cudd.GB`, `dd.cudd_zdd.GB`
- remove the unused constant `dd.sylvan.GB`
- method `dd.cudd_zdd.ZDD.dump`:
- support PNG and SVG formats, in addition to PDF
- allow more than one references to ZDD nodes in the argument `roots`
- add method `apply` to the class `dd.mdd.MDD`
- several `assert` statements replaced by `raise` with
exceptions more specific than `AssertionError`
- set `dd.cudd.Function.node` and `dd.cudd_zdd.Function.node`
to `NULL` when the (local) reference count becomes zero
## 0.5.6
- distribute `manylinux2014_x86_64` wheel via PyPI
API:
- require `cython >= 0.29.15`
- add module `dd.cudd_zdd`
- allow empty support variable names in DDDMP files in function `dd.dddmp.load`
- methods `dump` and `load` of the classes
`dd.cudd.BDD` and `dd.autoref.BDD`:
- add JSON to file types
- load by file extension
- change return type of method `dd.cudd.BDD.load`
to `list` of `dd.cudd.Function`
- multiple roots supported in `dd.cudd.BDD.dump` for
file types other than DDDMP
- method `count` of the classes
`dd.cudd.BDD` and `dd.cudd_zdd.ZDD`:
- make optional the argument `nvars`
- `dd.autoref.BDD.load`:
require file extension `.p` for pickle files
## 0.5.5
API:
- require `networkx <= 2.2` on Python 2
- class `dd.bdd.BDD`:
- remove argument `debug` from method `_next_free_int`
- add method `undeclare_variables`
- plot nodes for external BDD references in function `dd.bdd.to_pydot`,
which is used by the methods `BDD.dump` of the modules
`dd.cudd`, `dd.autoref`, and `dd.bdd`
- function `dd._copy.load_json`:
rename argument from `keep_order` to `load_order`
- add unused keyword arguments to method `autoref.BDD.decref`
## 0.5.4
- enable `KeyboardInterrupt` on POSIX systems for `cudd`
when `cysignals >= 1.7.0` is present at installation
API:
- change signature of method `cudd.BDD.dump`
- add GraphViz as an option of `cudd.BDD.dump`
- allow copying between managers with different variable orders
- allow simultaneous substitution in `bdd.BDD.let`
- add property `BDD.var_levels`
- add method `BDD.reorder` to `cudd` and `autoref`
- add method `cudd.BDD.group` for grouping variables
- add `autoref.BDD` methods `incref` and `decref`
- change signatures of `cudd.BDD` methods `incref` and `decref`
- change default to `recursive=False` in method `cudd.BDD.decref`
- add property `Function.dag_size`
- add module `dd._copy`
- rm function `dd.bdd.copy_vars`, use method `BDD.declare` instead,
and separately copy variable order, if needed.
This function has moved to `_copy.copy_vars`.
- rm method `bdd.BDD.evaluate`, use method `dd.BDD.let`
## 0.5.3
- distribute `manylinux1_x86_64` wheel via PyPI
API:
- update to `networkx >= 2.0` (works with `< 2.0` too)
- class `BDD` in modules `autoref`, `bdd`, `cudd`, `sylvan`:
- remove deprecated methods (where present):
`compose`, `cofactor`, `rename`, `evaluate`,
`sat_iter`, `sat_len`
## 0.5.2
API:
- require `networkx < 2.0.0`
- add module `dd._abc` that defines API implemented by other modules.
- add method `declare` to `BDD` classes
- add methods `implies` and `equiv` to class `cudd.Function`
- change BDD node reference syntax to "@ INTEGER"
- change `Function.__str__` to include `@` in modules `cudd` and `autoref`
- deprecate `BDD` methods `compose`, `cofactor`, `rename`, `evaluate`,
instead use `BDD.let`
- class `BDD` in modules `autoref`, `bdd`, `cudd`, `sylvan`:
- methods `pick`, `pick_iter`:
rename argument from `care_bits` to `care_vars`
- class `BDD` in modules `autoref`, `bdd`:
- method `count`:
rename argument from `n` to `nvars`
- class `BDD` in modules `bdd`, `cudd`:
- allow swapping variables in method `rename`,
accept only variable names, not levels
- rm argument `bdd` from functions:
- `image`, `preimage` in module `autoref`
- `and_exists`, `or_forall`, `dump` in module `cudd`
- `and_exists`, `or_forall` in module `sylvan`
- rm argument `roots` from method `autoref.BDD.collect_garbage`
- rm argument `source` from function:
`copy_bdd` in modules `autoref`, `cudd`
- rm function `cudd.rename`, use method `cudd.BDD.let`
- rm function `autoref.rename`, use method `autoref.BDD.let`
- rm method `autoref.Function.__xor__`
- add TLA constants "TRUE" and "FALSE" to syntax,
use these in method `BDD.to_expr`
## 0.5.1
API:
- classes `cudd.BDD`, `autoref.BDD`, `bdd.BDD`:
- add method `let`, which will replace `compose`, `cofactor`, `rename`
- add method `pick`
- add method `pick_iter`, deprecate `sat_iter`
- add method `count`, deprecate `sat_len`
- allow copying node to same manager, but log warning
- class `sylvan.BDD`:
- add method `let`
- classes `cudd.Function`, `autoref.Function`:
- implement all comparison methods (`__le__`, `__lt__`)
## 0.5.0
API:
- dynamic variable reordering in `dd.bdd.BDD` (by default disabled)
- method `bdd.BDD.sat_len`: count only levels in support (similar to CUDD)
- class `autoref.Function`:
- rename attribute `bdd` to `manager`
- classes `cudd.Function`, `autoref.Function`, `sylvan.Function`:
- add attributes `var, support, bdd`
- add method `__hash__`
- classes `cudd.Function` and `sylvan.Function`:
- hide attribute `index` as `_index`
- classes `cudd.BDD` and `sylvan.BDD`:
- do not memoize attributes `false` and `true`
- classes `cudd.BDD` and `autoref.BDD`:
- add method `find_or_add`
- method `BDD.sat_iter`:
- rm arg `full`
- `care_bits = support` as default
- `care_bits < support` allowed
- function `bdd.to_pydot`: plot only levels in support of given node
- add function `autoref.reorder`
## 0.4.3
API:
- build `dd.cudd` using CUDD v3.0.0
(an older CUDD via an older `download.py` should work too)
## 0.4.2
API:
- classes `bdd.BDD`, `autoref.BDD`:
- rm attribute `ordering`, use `vars`
- rename `__init__` argument `ordering` to `levels`
- allow passing path to CUDD during installation via `--cudd`
## 0.4.1
- add Cython interface `dd.sylvan` to Sylvan
- support TLA+ syntax
BUG:
- in Python 2 use `sys.maxint` for `bdd.BDD.max_nodes`
API:
- classes `bdd.BDD` and `cudd.BDD`:
- method `apply`: rm `"bimplies"` value
- raise `AssertionError` if `care_bits < support` in method `sat_iter`
- rm unused operator `!=` from parser grammar
- class `autoref.Function`:
- rename method `bimplies` to `equiv`
## 0.4.0
- require `pydot >= 1.2.2`
API:
- change quantification syntax to `\E x, y: x`
- add renaming syntax `\S x / y, z / w: y & w`
- class `BDD` in `dd.bdd`, `dd.autoref`, `dd.cudd`:
- add operators `'ite', '\E', '\A'` to method `apply`
- add methods `forall` and `exist` as wrappers of `quantify`
- add method `_add_int` for checking presence of
a BDD node represented as an integer
- add method `succ` to obtain `(level, low, high)`
- class `cudd.BDD`:
- add method `compose`
- add method `ite`
- add method `to_expr`
- class `cudd.Function`:
- add method `__int__` to represent CUDD nodes
uniquely as integers (by shifting the C pointer value to
avoid possible conflicts with reserved values)
- add method `__str__` to return integer repr as `str`
- add attribute `level`
- add attribute `negated`
- module `cudd`:
- add function `restrict`
- add function `count_nodes`
- remove "extra" named `dot`, because `pydot` is now required
## 0.3.1
BUG:
- `dd.bdd.BDD.dump`: if argument `roots is None` (default),
then dump all nodes
- `dd.autoref.BDD.compose`: call wrapped method correctly
## 0.3.0
API:
- `dd.bdd.BDD.rename`, `dd.bdd.image`, `dd.bdd.preimage`: allow non-adjacent variable levels
- `dd.bdd.BDD.descendants`:
- arg `roots` instead of single node `u`
- iteration instead of recursion
- breadth-first instead of depth-first search
- `dd.bdd.BDD.dump`:
- dump nodes reachable from given roots
- dump only variable levels and nodes to pickle file
- correct error that ignored explicit file type for PDF, PNG, SVG
- `dd.bdd.BDD.load`:
- instance method to load nodes
- `dd.bdd.to_pydot`:
- add arg `roots`
- hide methods that dump and load entire manager
- `dd.bdd.BDD._dump_manager` and `_load_manager`
- remove `dd.autoref.Function.from_expr`
## 0.2.2
- install without extensions by default
- try to read git information, but assume release if this fails for any reason
## 0.2.1
- optionally import `gitpython` in `setup.py` to retrieve
version info from `git` repo.
- version identifier when `git` available:
`X.Y.Z.dev0+SHA[.dirty]`
- require `psutil >= 3.2.2`
- require `setuptools >= 19.6` to avoid `cython` affecting `psutil` build
- detect 64-bit system using `ctypes.sizeof` for CUDD flags
API:
- `dd.cudd.BDD.__cinit__`:
- rename arg `memory` -> `memory_estimate`
- assert memory estimate less than `psutil.virtual_memory().total`
- add arg `initial_cache_size`
- `dd.cudd.BDD.statistics`:
- distinguish between peak and live nodes
- cache statistics
- unique table statistics
- read node count without removing unused nodes
- `dd.cudd.BDD.configure`:
- accept keyword args, instead of `dict`
- first read config (returned `dict`), then set given values
- reordering
- garbage collection
- max cache soft
- max swaps
- max variables per reordering
- `dd.bdd`, `dd.autoref`, `dd.cudd`:
- add method `BDD.copy` for copying nodes between managers
- add method `BDD.rename` for substituting variables
- deprecate functions `rename` and `copy_bdd`
- add method `dd.cudd.BDD.sat_iter`
- add function `dd.cudd.count_nodes_per_level`
- add functions that track variable order when saving:
- `dd.cudd.dump`
- `dd.cudd.load`
## 0.2.0
- add user documentation
- support Python 3
- require `pydot3k` in Python 3, `pydot` in Python 2
- expose more control over CUDD configuration
API:
- add `dd.cudd.BDD.configure`
- do not set manager parameters in `__cinit__`
- rename `BDD.False` -> `BDD.false` (same for “true”), to avoid syntax errors in Python 3
- remove `dd.bdd.BDD.add_ast`
- `dd.cudd.reorder` invokes sifting if variable order is `None`
- default to pickle protocol 2
## 0.1.3
Bugfix release to add file `download.py` missing from MANIFEST.
API:
- add `dd.cudd.BDD.statistics`
- add functions `copy_vars` and `copy_bdd`
- remove `dd.bdd.BDD.level_to_variable`
## 0.1.2
- add Cython interface `dd.cudd` to CUDD
- add Cython interface `dd.buddy` to BuDDy
## 0.1.1
- dynamic variable addition in `dd.bdd.BDD`
- add `dd.autoref` wrapper around `dd.bdd`
- avoid randomization inside `sat_iter`
API:
- add `BDD.True` and `BDD.False`
- move `Function` interface to `dd.autoref`
- move parser to `dd._parser`
- rename `BDD.level_to_variable` -> `var_at_level`
- deprecate `BDD.ordering` in favor of `BDD.vars`
## 0.0.4
- add `dd.mdd` for multi-terminal decision diagrams
- add quantifiers to syntax
- add complemented edges to syntax
- require `networkx`
API:
- add `dd.bdd.BDD.cube`
- add `dd.bdd.BDD.descendants`
- add function `reorder_pairs`
## 0.0.3
- add PLY parser for Boolean expressions
- require `astutils`
API:
- add `dd.bdd.BDD.ref`
- assign `bool` as model values
## 0.0.2
- test on Travis
API:
- add `"diff"` operator to `dd.bdd.BDD.apply`
## 0.0.1
Initial release.
================================================
FILE: LICENSE
================================================
Copyright (c) 2014-2022 by California Institute of Technology
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the California Institute of Technology nor
the names of its contributors may be used to endorse or promote
products derived from this software without specific prior
written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CALTECH OR THE
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: MANIFEST.in
================================================
include README.md
include LICENSE
include doc.md
include CHANGES.md
include AUTHORS
include requirements.txt
include download.py
include tests/README.md
include tests/pytest.ini
include tests/inspect_cython_signatures.py
include tests/common.py
include tests/common_bdd.py
include tests/common_cudd.py
include tests/iterative_recursive_flattener.py
include tests/*_test.py
include tests/sample*.dddmp
include examples/README.md
include examples/*.py
include examples/*.sh
include dd/py.typed
include dd/*.pyx
include dd/*.pxd
include dd/*.c
include dd/CUDD_LICENSE
include dd/GLIBC_COPYING.LIB
include dd/GLIBC_LICENSES
include dd/PYTHON_LICENSE
================================================
FILE: Makefile
================================================
# build, install, test, release `dd`
SHELL := bash
wheel_file := $(wildcard dist/*.whl)
.PHONY: cudd install test
.PHONY: clean clean_all clean_cudd wheel_deps
build_cudd: clean cudd install test
build_sylvan: clean wheel_deps
-pip uninstall -y dd
export DD_SYLVAN=1; \
pip install . -vvv --use-pep517 --no-build-isolation
pip install pytest
make test
sdist_test: clean wheel_deps
pip install -U build cython
export DD_CUDD=1 DD_BUDDY=1; \
python -m build --sdist --no-isolation
pushd dist; \
pip install dd*.tar.gz; \
tar -zxf dd*.tar.gz && \
popd
pip install pytest
make -C dist/dd*/ -f ../../Makefile test
sdist_test_cudd: clean wheel_deps
pip install build cython ply
export DD_CUDD=1 DD_BUDDY=1; \
python -m build --sdist --no-isolation
yes | pip uninstall cython ply
pushd dist; \
tar -zxf dd*.tar.gz; \
pushd dd*/; \
export DD_FETCH=1 DD_CUDD=1; \
pip install . -vvv --use-pep517 --no-build-isolation && \
popd && popd
pip install pytest
make -C dist/dd*/ -f ../../Makefile test
# use to create source distributions for PyPI
sdist: clean wheel_deps
-rm dist/*.tar.gz
pip install -U build cython
export DD_CUDD=1 DD_CUDD_ZDD=1 DD_BUDDY=1 DD_SYLVAN=1; \
python -m build --sdist --no-isolation
wheel_deps:
pip install --upgrade \
cython \
pip \
setuptools \
wheel
# use to create binary distributions for PyPI
wheel: clean wheel_deps
-rm dist/*.whl
-rm wheelhouse/*.whl
export DD_CUDD=1 DD_CUDD_ZDD=1; \
pip wheel . \
-vvv \
--wheel-dir dist \
--no-deps
@echo "-------------"
auditwheel show dist/*.whl
@echo "-------------"
auditwheel repair --plat manylinux_2_17_x86_64 dist/*.whl
@echo "-------------"
auditwheel show wheelhouse/*.whl
install: wheel_deps
export DD_CUDD=1; \
pip install . -vvv --use-pep517 --no-build-isolation
reinstall: uninstall wheel_deps
export DD_CUDD=1 DD_CUDD_ZDD=1 DD_SYLVAN=1; \
pip install . -vvv --use-pep517 --no-build-isolation
reinstall_buddy: uninstall wheel_deps
export DD_BUDDY=1; \
pip install . -vvv --use-pep517 --no-build-isolation
reinstall_cudd: uninstall wheel_deps
export DD_CUDD=1 DD_CUDD_ZDD=1; \
pip install . -vvv --use-pep517 --no-build-isolation
reinstall_sylvan: uninstall wheel_deps
export DD_SYLVAN=1; \
pip install . -vvv --use-pep517 --no-build-isolation
uninstall:
pip uninstall -y dd
test:
set -x; \
pushd tests; \
python -X dev -m pytest -v --continue-on-collection-errors . && \
popd
# `pytest -Werror` turns all warnings into errors
# <https://docs.pytest.org/en/latest/how-to/capture-warnings.html>
# including pytest warnings about unraisable exceptions:
# <https://docs.pytest.org/en/latest/how-to/failures.html
# #warning-about-unraisable-exceptions-and-unhandled-thread-exceptions>
# <https://docs.pytest.org/en/latest/reference/reference.html
# #pytest.PytestUnraisableExceptionWarning>
test_abc:
python -X dev tests/inspect_cython_signatures.py
test_examples:
pushd examples/; \
for script in `ls *.py`; \
do \
echo "Running: $$script"; \
python -X dev $$script; \
done && \
popd
show_deprecated:
python -X dev -Wall -c "from dd import bdd"
typecheck:
pytype \
-k \
-v 1 \
-j 'auto' \
dd/*.py \
setup.py \
examples/*.py
# tests/*.py
# download.py
clean_type_cache:
-rm -rf .pytype/
cudd:
pushd cudd-*/; \
./configure "CFLAGS=-fPIC -std=c99" \
make build XCFLAGS="\
-fPIC \
-mtune=native \
-DHAVE_IEEE_754 \
-DBSD \
-DSIZEOF_VOID_P=8 \
-DSIZEOF_LONG=8" && \
popd
doc:
grip --export doc.md index.html
download_licenses:
python -c 'import download; \
download.download_licenses()'
clean_all: clean_cudd clean
clean_cudd:
pushd cudd-*/; make clean && popd
clean:
-rm -rf build/ dist/ dd.egg-info/
-rm dd/*.so
-rm dd/buddy.c
-rm dd/cudd.c
-rm dd/cudd_zdd.c
-rm dd/sylvan.c
-rm *.pyc */*.pyc
-rm -rf __pycache__ */__pycache__
-rm -rf wheelhouse
rm_cudd:
-rm -rf cudd*/ cudd*.tar.gz
================================================
FILE: README.md
================================================
[![Build Status][build_img]][ci]
About
=====
A pure-Python (Python >= 3.11) package for manipulating:
- [Binary decision diagrams](
https://en.wikipedia.org/wiki/Binary_decision_diagram) (BDDs).
- [Multi-valued decision diagrams](
https://dx.doi.org/10.1109/ICCAD.1990.129849) (MDDs).
as well as [Cython](https://cython.org) bindings to the C libraries:
- [CUDD](
https://web.archive.org/web/20180127051756/http://vlsi.colorado.edu/~fabio/CUDD/html/index.html)
(also read [the introduction](
https://web.archive.org/web/20150317121927/http://vlsi.colorado.edu/~fabio/CUDD/node1.html),
and note that the original link for CUDD is <http://vlsi.colorado.edu/~fabio/CUDD/>)
- [Sylvan](https://github.com/utwente-fmt/sylvan) (multi-core parallelization)
- [BuDDy](https://sourceforge.net/projects/buddy/)
These bindings expose almost identical interfaces as the Python implementation.
The intended workflow is:
- develop your algorithm in pure Python (easy to debug and introspect),
- use the bindings to benchmark and deploy
Your code remains the same.
Contains:
- All the standard functions defined, e.g.,
by [Bryant](https://www.cs.cmu.edu/~bryant/pubdir/ieeetc86.pdf).
- Dynamic variable reordering using [Rudell's sifting algorithm](
http://www.eecg.toronto.edu/~ece1767/project/rud.pdf).
- Reordering to obtain a given order.
- Parser of quantified Boolean expressions in either
[TLA+](https://en.wikipedia.org/wiki/TLA%2B) or
[Promela](https://en.wikipedia.org/wiki/Promela) syntax.
- Pre/Image computation (relational product).
- Renaming variables.
- Zero-omitted binary decision diagrams (ZDDs) in CUDD
- Conversion from BDDs to MDDs.
- Conversion functions to [`networkx`](https://networkx.org) and
[DOT](https://www.graphviz.org/doc/info/lang.html) graphs.
- BDDs have methods to `dump` and `load` them using [JSON](
https://wikipedia.org/wiki/JSON), or [`pickle`](
https://docs.python.org/3/library/pickle.html).
- BDDs dumped by CUDD's DDDMP can be loaded using fast iterative parser.
- [Garbage collection](
https://en.wikipedia.org/wiki/Garbage_collection_(computer_science))
that combines reference counting and tracing
If you prefer to work with integer variables instead of Booleans, and have
BDD computations occur underneath, then use the module
[`omega.symbolic.fol`](
https://github.com/tulip-control/omega/blob/main/omega/symbolic/fol.py)
from the [`omega` package](
https://github.com/tulip-control/omega/blob/main/doc/doc.md).
If you are interested in computing minimal covers (two-level logic minimization)
then use the module `omega.symbolic.cover` of the `omega` package.
The method `omega.symbolic.fol.Context.to_expr` converts BDDs to minimal
formulas in disjunctive normal form (DNF).
Documentation
=============
In the [Markdown](https://en.wikipedia.org/wiki/Markdown) file
[`doc.md`](https://github.com/tulip-control/dd/blob/main/doc.md).
The [changelog](https://en.wiktionary.org/wiki/changelog) is in
the file [`CHANGES.md`](
https://github.com/tulip-control/dd/blob/main/CHANGES.md).
Examples
========
The module `dd.autoref` wraps the pure-Python BDD implementation `dd.bdd`.
The API of `dd.cudd` is almost identical to `dd.autoref`.
You can skip details about `dd.bdd`, unless you want to implement recursive
BDD operations at a low level.
```python
from dd.autoref import BDD
bdd = BDD()
bdd.declare('x', 'y', 'z', 'w')
# conjunction (in TLA+ syntax)
u = bdd.add_expr(r'x /\ y')
# symbols `&`, `|` are supported too
# note the "r" before the quote,
# which signifies a raw string and is
# needed to allow for the backslash
print(u.support)
# substitute variables for variables (rename)
rename = dict(x='z', y='w')
v = bdd.let(rename, u)
# substitute constants for variables (cofactor)
values = dict(x=True, y=False)
v = bdd.let(values, u)
# substitute BDDs for variables (compose)
d = dict(x=bdd.add_expr(r'z \/ w'))
v = bdd.let(d, u)
# as Python operators
v = bdd.var('z') & bdd.var('w')
v = ~ v
# quantify universally ("forall")
u = bdd.add_expr(r'\A x, y: (x /\ y) => y')
# quantify existentially ("exist")
u = bdd.add_expr(r'\E x, y: x \/ y')
# less readable but faster alternative,
# (faster because of not calling the parser;
# this may matter only inside innermost loops)
u = bdd.var('x') | bdd.var('y')
u = bdd.exist(['x', 'y'], u)
assert u == bdd.true, u
# inline BDD references
u = bdd.add_expr(rf'x /\ {v}')
# satisfying assignments (models):
# an assignment
d = bdd.pick(u, care_vars=['x', 'y'])
# iterate over all assignments
for d in bdd.pick_iter(u):
print(d)
# how many assignments
n = bdd.count(u)
# write to and load from JSON file
filename = 'bdd.json'
bdd.dump(filename, roots=dict(res=u))
other_bdd = BDD()
roots = other_bdd.load(filename)
print(other_bdd.vars)
```
To run the same code with CUDD installed, change the first line to:
```python
from dd.cudd import BDD
```
Most useful functionality is available via methods of the class `BDD`.
A few of the functions can prove useful too, among them `to_nx()`.
Use the method `BDD.dump` to write a `BDD` to a `pickle` file, and
`BDD.load` to load it back. A CUDD dddmp file can be loaded using
the function `dd.dddmp.load`.
A `Function` object wraps each BDD node and decrements its reference count
when disposed by Python's garbage collector. Lower-level details are
discussed in the documentation.
For using ZDDs, change the first line to
```python
from dd.cudd_zdd import ZDD as BDD
```
Installation
============
## pure-Python
From the [Python Package Index (PyPI)](https://pypi.org) using the
package installer [`pip`](https://pip.pypa.io):
```shell
pip install dd
```
or from the directory of source files:
```shell
pip install .
```
For graph layout, install also [graphviz](https://graphviz.org).
The `dd` package requires Python 3.11 or later.
For Python 2.7, use `dd == 0.5.7`.
## Cython bindings
To compile also the module `dd.cudd` (which interfaces to CUDD)
when installing from PyPI, run:
```shell
pip install --upgrade wheel cython
export DD_FETCH=1 DD_CUDD=1
pip install dd -vvv --use-pep517 --no-build-isolation
```
(`DD_FETCH=1 DD_CUDD=1 pip install dd` also works,
when the source tarball includes cythonized code.)
To confirm that the installation succeeded:
```shell
python -c 'import dd.cudd'
```
The [environment variables](
https://en.wikipedia.org/wiki/Environment_variable)
above mean:
- `DD_FETCH=1`: download CUDD v3.0.0 sources from the internet,
unpack the tarball (after checking its hash), and `make` CUDD.
- `DD_CUDD=1`: build the Cython module `dd.cudd`
More about environment variables that configure the
C extensions of `dd` is described in the file [`doc.md`](
https://github.com/tulip-control/dd/blob/main/doc.md)
## Wheel files with compiled CUDD
[Wheel files](
https://www.python.org/dev/peps/pep-0427/)
are [available from PyPI](
https://pypi.org/project/dd/#files),
which contain the module `dd.cudd`,
with the CUDD library compiled and linked.
If you have a Linux system and Python version compatible with
one of the PyPI wheels,
then `pip install dd` will install also `dd.cudd`.
### Licensing of the compiled modules `dd.cudd` and `dd.cudd_zdd` in the wheel
These notes apply to the compiled modules `dd.cudd` and `dd.cudd_zdd` that are
contained in the [wheel file](https://www.python.org/dev/peps/pep-0427/) on
PyPI (namely the files `dd/cudd.cpython-39-x86_64-linux-gnu.so` and
`dd/cudd_zdd.cpython-39-x86_64-linux-gnu.so` in the [`*.whl` file](
https://pypi.org/project/dd/#files), which can
be obtained using [`unzip`](http://infozip.sourceforge.net/UnZip.html)).
These notes do not apply to the source code of the modules
`dd.cudd` and `dd.cudd_zdd`.
The source distribution of `dd` on PyPI is distributed under a 3-clause BSD
license.
The following libraries and their headers were used when building the modules
`dd.cudd` and `dd.cudd_zdd` that are included in the wheel:
- Python: <https://www.python.org/ftp/python/3.A.B/Python-3.A.B.tgz>
(where `A` and `B` the numerals of
the corresponding Python version used;
for example `10` and `2` to signify Python 3.10.2).
CPython releases are described at:
<https://www.python.org/downloads/>
- [CUDD](https://sourceforge.net/projects/cudd-mirror/files/cudd-3.0.0.tar.gz/download).
The licenses of Python and CUDD are included in the wheel archive.
Cython [does not](https://github.com/cython/cython/blob/master/COPYING.txt)
add its license to C code that it generates.
GCC was used to compile the modules `dd.cudd` and `dd.cudd_zdd` in the wheel,
and the GCC [runtime library exception](
https://github.com/gcc-mirror/gcc/blob/master/COPYING.RUNTIME#L61-L66)
applies.
The modules `dd.cudd` and `dd.cudd_zdd` in the wheel dynamically link to the:
- Linux kernel (in particular [`linux-vdso.so.1`](
https://man7.org/linux/man-pages/man7/vdso.7.html)),
which allows system calls (read the kernel's file [`COPYING`](
https://github.com/torvalds/linux/blob/master/COPYING) and the explicit
syscall exception in the file [`LICENSES/exceptions/Linux-syscall-note`](
https://github.com/torvalds/linux/blob/master/LICENSES/exceptions/Linux-syscall-note))
- [GNU C Library](https://www.gnu.org/software/libc/) (glibc) (in particular
`libpthread.so.0`, `libc.so.6`, `/lib64/ld-linux-x86-64.so.2`), which uses
the [LGPLv2.1](https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=COPYING.LIB;hb=HEAD)
that allows dynamic linking, and other [licenses](
https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=LICENSES;hb=HEAD).
These licenses are included in the wheel file and apply to the GNU C Library
that is dynamically linked.
Tests
=====
Use [`pytest`](https://pypi.org/project/pytest). Run with:
```shell
pushd tests/
pytest -v --continue-on-collection-errors .
popd
```
Tests of Cython modules that were not installed will fail.
The code is covered well by tests.
License
=======
[BSD-3](https://opensource.org/licenses/BSD-3-Clause), read file `LICENSE`.
[build_img]: https://github.com/tulip-control/dd/actions/workflows/main.yml/badge.svg?branch=main
[ci]: https://github.com/tulip-control/dd/actions
================================================
FILE: dd/__init__.py
================================================
"""Package of algorithms based on decision diagrams."""
try:
import dd._version as _version
__version__ = _version.version
except ImportError:
__version__ = None
try:
import dd.cudd as _bdd
except ImportError:
import dd.autoref as _bdd
BDD = _bdd.BDD
================================================
FILE: dd/_abc.py
================================================
"""Interface specification.
This specification is implemented by the modules:
- `dd.autoref`
- `dd.cudd`
- `dd.cudd_zdd`
- `dd.sylvan` (partially)
- `dd.buddy` (partially)
"""
# Copyright 2017 by California Institute of Technology
# All rights reserved. Licensed under BSD-3.
#
import collections.abc as _abc
import typing as _ty
def _literals_of(
type_alias:
type
) -> set[str]:
"""Return arguments of `type_alias`.
Recursive computation.
Assumes `str` literals.
"""
return set(_literals_of_recurse(type_alias))
def _literals_of_recurse(
type_alias:
type
) -> _abc.Iterable[str]:
"""Yield literals of `type_alias`."""
args = _ty.get_args(type_alias)
literals = set()
for arg in args:
match arg:
case str():
yield arg
case _:
yield from _literals_of_recurse(arg)
return literals
Yes: _ty.TypeAlias = bool
Nat: _ty.TypeAlias = int
# ```tla
# Nat
# ```
Cardinality: _ty.TypeAlias = Nat
NumberOfBytes: _ty.TypeAlias = Cardinality
VariableName: _ty.TypeAlias = str
Level: _ty.TypeAlias = Nat
VariableLevels: _ty.TypeAlias = dict[
VariableName,
Level]
Ref = _ty.TypeVar('Ref')
Assignment: _ty.TypeAlias = dict[
VariableName,
bool]
Renaming: _ty.TypeAlias = dict[
VariableName,
VariableName]
Fork: _ty.TypeAlias = tuple[
Level,
Ref | None,
Ref | None]
Formula: _ty.TypeAlias = str
_UnaryOperatorSymbol: _ty.TypeAlias = _ty.Literal[
# negation
'not',
'~',
'!']
UNARY_OPERATOR_SYMBOLS: _ty.Final = _literals_of(
_UnaryOperatorSymbol)
# These assertions guard against typos in
# the enumerations.
if len(UNARY_OPERATOR_SYMBOLS) != 3:
raise AssertionError(UNARY_OPERATOR_SYMBOLS)
_BinaryOperatorSymbol: _ty.TypeAlias = _ty.Literal[
# conjunction
'and',
'/\\',
'&',
'&&',
# disjunction
'or',
r'\/',
'|',
'||',
# different
'#',
'xor',
'^',
# implication
'=>',
'->',
'implies',
# equivalence
'<=>',
'<->',
'equiv',
# subtraction (i.e., `a /\ ~ b`)
'diff',
'-',
# quantification
r'\A',
'forall',
r'\E',
'exists']
BINARY_OPERATOR_SYMBOLS: _ty.Final = _literals_of(
_BinaryOperatorSymbol)
if len(BINARY_OPERATOR_SYMBOLS) != 23:
raise AssertionError(BINARY_OPERATOR_SYMBOLS)
_TernaryOperatorSymbol: _ty.TypeAlias = _ty.Literal[
# ternary conditional
# (if-then-else)
'ite']
TERNARY_OPERATOR_SYMBOLS: _ty.Final = _literals_of(
_TernaryOperatorSymbol)
if len(TERNARY_OPERATOR_SYMBOLS) != 1:
raise AssertionError(TERNARY_OPERATOR_SYMBOLS)
BDD_OPERATOR_SYMBOLS: _ty.Final = {
*UNARY_OPERATOR_SYMBOLS,
*BINARY_OPERATOR_SYMBOLS,
*TERNARY_OPERATOR_SYMBOLS}
if len(BDD_OPERATOR_SYMBOLS) != 3 + 23 + 1:
raise AssertionError(BDD_OPERATOR_SYMBOLS)
OperatorSymbol: _ty.TypeAlias = (
_UnaryOperatorSymbol |
_BinaryOperatorSymbol |
_TernaryOperatorSymbol)
ImageFileType: _ty.TypeAlias = _ty.Literal[
'pdf',
'png',
'svg']
JSONFileType: _ty.TypeAlias = _ty.Literal[
'json']
PickleFileType: _ty.TypeAlias = _ty.Literal[
'pickle']
BDDFileType: _ty.TypeAlias = (
ImageFileType |
JSONFileType)
class BDD(_ty.Protocol[Ref]):
"""Shared reduced ordered binary decision diagram."""
vars: VariableLevels
def __init__(
self,
levels:
dict |
None=None
) -> None:
...
def __eq__(
self,
other
) -> Yes:
"""Return `True` if `other` has same manager"""
def __len__(
self
) -> Cardinality:
"""Return number of nodes."""
def __contains__(
self,
u:
Ref
) -> Yes:
"""Return `True` """
def __str__(
self
) -> str:
return 'Specification of BDD class.'
def configure(
self,
**kw
) -> dict[
str,
_ty.Any]:
"""Read and apply parameter values."""
def statistics(
self
) -> dict[
str,
_ty.Any]:
"""Return BDD manager statistics."""
# default implementation that offers no info
return dict()
def succ(
self,
u:
Ref
) -> Fork:
"""Return `(level, low, high)` for node `u`.
The manager uses complemented edges,
so `low` and `high` correspond to the rectified `u`.
"""
def declare(
self,
*variables:
VariableName
) -> None:
"""Add names in `variables` to `self.vars`.
```python
bdd.declare('x', 'y', 'z')
```
"""
def var(
self,
var:
VariableName
) -> Ref:
"""Return node for variable named `var`."""
def var_at_level(
self,
level:
Level
) -> VariableName:
"""Return variable with `level`."""
def level_of_var(
self,
var:
VariableName
) -> (
Level |
None):
"""Return level of `var`, or `None`."""
@property
def var_levels(
self
) -> VariableLevels:
"""Return mapping from variables to levels."""
def copy(
self,
u:
Ref,
other:
'BDD'
) -> Ref:
"""Copy operator `u` from `self` to `other` manager."""
def support(
self,
u:
Ref,
as_levels:
Yes=False
) -> (
set[VariableName] |
set[Level]):
"""Return variables that node `u` depends on.
@param as_levels:
if `True`, then return variables
as integers, insted of strings
"""
def let(
self,
definitions:
dict[VariableName, VariableName] |
Assignment |
dict[VariableName, Ref],
u:
Ref
) -> Ref:
"""Substitute variables in `u`.
The mapping `definitions` need not have
all declared variables as keys.
"""
def forall(
self,
variables:
_abc.Iterable[
VariableName],
u:
Ref
) -> Ref:
"""Quantify `variables` in `u` universally."""
def exist(
self,
variables:
_abc.Iterable[
VariableName],
u:
Ref
) -> Ref:
"""Quantify `variables` in `u` existentially."""
def count(
self,
u:
Ref,
nvars:
Cardinality |
None=None
) -> Cardinality:
"""Return number of models of node `u`.
@param nvars:
number of variables to assume.
If omitted, then assume those in `support(u)`.
If `nvars >= len(support(u))` then the count
is multiplied by `2**(nvars-len(support(u)))`,
compared to the case `nvars is None`.
"""
def pick(
self,
u:
Ref,
care_vars:
set[VariableName] |
None=None
) -> (
Assignment |
None):
r"""Return a single assignment.
An assignment is a `dict` that maps
each variable to a `bool`. Examples:
```python
>>> u = bdd.add_expr('x')
>>> bdd.pick(u)
{'x': True}
>>> u = bdd.add_expr('y')
>>> bdd.pick(u)
{'y': True}
>>> u = bdd.add_expr('y')
>>> bdd.pick(u, care_vars=['x', 'y'])
{'x': False, 'y': True}
>>> u = bdd.add_expr(r'x \/ y')
>>> bdd.pick(u)
{'x': False, 'y': True}
>>> u = bdd.false
>>> bdd.pick(u) is None
True
```
By default, `care_vars = support(u)`.
Log a warning if `care_vars < support(u)`.
Thin wrapper around `pick_iter`.
"""
picks = self.pick_iter(u, care_vars)
return next(iter(picks), None)
def pick_iter(
self,
u:
Ref,
care_vars:
set[VariableName] |
None=None
) -> _abc.Iterable[
Assignment]:
"""Return iterator over assignments.
By default, `care_vars = support(u)`.
Log a warning if `care_vars < support(u)`.
CASES:
1. `None`: return (uniform) assignments that
include exactly those variables in `support(u)`
2. `set`: return (possibly partial) assignments
that include at least all bits in `care_vars`
"""
# def to_bdd(
# self,
# expr):
# raise NotImplementedError('use `add_expr`')
def add_expr(
self,
expr:
Formula
) -> Ref:
"""Return node for expression `expr`.
Nodes are created for the BDD that
represents the expression `expr`.
"""
def to_expr(
self,
u:
Ref
) -> Formula:
"""Return a Boolean expression for node `u`."""
def ite(
self,
g:
Ref,
u:
Ref,
v:
Ref
) -> Ref:
"""Ternary conditional `IF g THEN u ELSE v`.
@param g:
condition
@param u:
high
@param v:
low
"""
def apply(
self,
op:
OperatorSymbol,
u:
Ref,
v:
Ref |
None=None,
w:
Ref |
None=None
) -> Ref:
r"""Apply operator `op` to nodes `u`, `v`, `w`."""
def _add_int(
self,
i:
int
) -> Ref:
"""Return node from `i`."""
def cube(
self,
dvars:
Assignment
) -> Ref:
"""Return node for conjunction of literals in `dvars`."""
# TODO: homogeneize i/o API with `dd.cudd`
def dump(
self,
filename:
str,
roots:
dict[str, Ref] |
list[Ref] |
None=None,
filetype:
ImageFileType |
None=None,
**kw
) -> None:
"""Write BDDs to `filename`.
The file type is inferred from the
extension (case insensitive),
unless a `filetype` is explicitly given.
`filetype` can have the values:
- `'pickle'` for Pickle
- `'pdf'` for PDF
- `'png'` for PNG
- `'svg'` for SVG
If `filetype is None`, then `filename`
must have an extension that matches
one of the file types listed above.
Dump nodes reachable from `roots`.
If `roots is None`,
then all nodes in the manager are dumped.
@type roots:
- `list` of nodes, or
- for Pickle: `dict` that maps
names to nodes
"""
def load(
self,
filename:
str,
levels:
Yes=True
) -> (
dict[str, Ref] |
list[Ref]):
"""Load nodes from Pickle file `filename`.
If `levels is True`,
then load variables at the same levels.
Otherwise, add missing variables.
@return:
roots of the loaded BDDs
@rtype:
depends on the contents of the file,
either:
- `dict` that maps names
to nodes, or
- `list` of nodes
"""
@property
def false(
self
) -> Ref:
"""Return Boolean constant false."""
@property
def true(
self
) -> Ref:
"""Return Boolean constant true."""
def reorder(
bdd:
BDD,
order:
VariableLevels |
None=None
) -> None:
"""Apply Rudell's sifting algorithm to `bdd`.
@param order:
reorder to this specific order,
if `None` then invoke group sifting
"""
class Operator(_ty.Protocol):
"""Convenience wrapper for edges returned by `BDD`."""
def __init__(
self,
node,
bdd
) -> None:
self.bdd: BDD
self.manager: object
self.node: object
def __hash__(
self
) -> int:
return self.node
def to_expr(
self
) -> Formula:
"""Return Boolean expression of function."""
def __int__(
self
) -> int:
"""Return integer ID of node.
To invert this method call `BDD._add_int`.
"""
def __str__(
self
) -> str:
"""Return string form of node as `@INT`.
"INT" is an integer that depends on
the implementation. For example "@54".
The integer value is `int(self)`.
The integer value is recognized by the method
`BDD._add_int` of the same manager that the
node belongs to.
"""
return f'@{int(self)}'
def __len__(
self
) -> Cardinality:
"""Number of nodes reachable from this node."""
def __del__(
self
) -> None:
r"""Dereference node in manager."""
def __eq__(
self,
other
) -> Yes:
r"""`|= self \equiv other`.
Return `False` if `other is None`.
"""
def __ne__(
self,
other
) -> Yes:
r"""`~ |= self \equiv other`.
Return `True` if `other is None`.
"""
def __lt__(
self,
other
) -> Yes:
r"""`(|= self => other) /\ ~ |= self \equiv other`."""
def __le__(
self,
other
) -> Yes:
"""`|= self => other`."""
def __invert__(
self
) -> 'Operator':
"""Negation `~ self`."""
def __and__(
self,
other
) -> 'Operator':
r"""Conjunction `self /\ other`."""
def __or__(
self,
other
) -> 'Operator':
r"""Disjunction `self \/ other`."""
def __xor__(
self,
other
) -> 'Operator':
"""Exclusive-or `self ^ other`."""
def implies(
self,
other
) -> 'Operator':
"""Logical implication `self => other`."""
def equiv(
self,
other
) -> 'Operator':
r"""Logical equivalence `self <=> other`.
The result is *different* from `__eq__`:
- Logical equivalence is the Boolean function that is
`TRUE` for models for which both `self` and `other`
are `TRUE`, and `FALSE` otherwise.
- BDD equality (`__eq__`) is the Boolean function
that results from universal quantification of the
logical equivalence, over all declared variables.
In other words:
"A <=> B" versus "\A x, y, ..., z: A <=> B"
or, from a metatheoretic viewpoint:
"A <=> B" versus "|= A <=> B"
In the metatheory, [[A <=> B]] (`equiv`) is different from
[[A]] = [[B]] (`__eq__`).
Also, `equiv` differs from `__eq__` in that it returns a BDD
as `Function`, instead of `bool`.
"""
@property
def level(
self
) -> Level:
"""Level where this node currently is."""
@property
def var(
self
) -> (
VariableName |
None):
"""Variable at level where this node is."""
@property
def low(
self
) -> '''(
Operator |
None
)''':
"""Return "else" node."""
@property
def high(
self
) -> '''(
Operator |
None
)''':
"""Return "then" node."""
@property
def ref(
self
) -> Cardinality:
"""Sum of reference counts of node and its negation."""
@property
def negated(
self
) -> Yes:
"""Return `True` if `self` is a complemented edge."""
@property
def support(
self
) -> set[
VariableName]:
"""Return variables in support."""
def let(
self,
**definitions: '''(
VariableName |
Operator |
bool
)'''
) -> 'Operator':
return self.bdd.let(definitions, self)
def exist(
self,
*variables:
VariableName
) -> 'Operator':
return self.bdd.exist(variables, self)
def forall(
self,
*variables:
VariableName
) -> 'Operator':
return self.bdd.forall(variables, self)
def pick(
self,
care_vars:
set[VariableName] |
None=None
) -> (
Assignment |
None):
return self.bdd.pick(self, care_vars)
def count(
self,
nvars:
Cardinality |
None=None
) -> Cardinality:
return self.bdd.count(self, nvars)
================================================
FILE: dd/_copy.py
================================================
"""Utilities for transferring BDDs."""
# Copyright 2016-2018 by California Institute of Technology
# All rights reserved. Licensed under 3-clause BSD.
#
import collections.abc as _abc
import contextlib as _ctx
import json
import os
import shelve
import shutil
import typing as _ty
import dd._abc
import dd._utils as _utils
SHELVE_DIR: _ty.Final = '__shelve__'
_Yes: _ty.TypeAlias = dd._abc.Yes
class _BDD(
dd._abc.BDD[dd._abc.Ref],
_ty.Protocol):
"""BDD context."""
def add_var(
self,
var:
str,
level:
dd._abc.Level |
None=None
) -> dd._abc.Level:
...
def _top_cofactor(
self,
u:
dd._abc.Ref,
level:
dd._abc.Level
) -> tuple[
dd._abc.Ref,
dd._abc.Ref]:
...
def reorder(
self,
var_order:
dict[
dd._abc.VariableName,
dd._abc.Level] |
None=None
) -> None:
...
def find_or_add(
self,
level:
dd._abc.Level,
u:
dd._abc.Ref,
v:
dd._abc.Ref
) -> dd._abc.Ref:
...
def incref(
self,
node:
dd._abc.Ref
) -> None:
...
def decref(
self,
node:
dd._abc.Ref,
**kw
) -> None:
...
def assert_consistent(
self
) -> None:
...
Ref = _ty.TypeVar('Ref')
class _Ref(_ty.Protocol):
var: str | None
level: int
low: '_Ref | None'
high: '_Ref | None'
bdd: _BDD
negated: _Yes
ref: int
def __int__(
self
) -> int:
...
def __invert__(
self
) -> '_Ref':
...
class _Shelf(
_ctx.AbstractContextManager,
_ty.Protocol):
"""Used for type checking."""
# `_abc.MutableMapping` cannot be
# in the bases, because not
# itself a `_ty.Protocol`.
def __setitem__(
self,
key,
value
) -> None:
...
def __getitem__(
self,
key):
...
def __iter__(
self
) -> _abc.Iterable:
...
def __contains__(
self,
item
) -> _Yes:
...
def _open_shelf(
name:
str
) -> _Shelf:
"""Wrapper for type-checking."""
return shelve.open(name)
def copy_vars(
source:
dd._abc.BDD,
target
) -> None:
"""Copy variables, preserving levels."""
for var in source.vars:
level = source.level_of_var(var)
target.add_var(var, level=level)
def copy_bdds_from(
roots:
_abc.Iterable[_Ref],
target:
_BDD
) -> list[_Ref]:
"""Copy BDDs in `roots` to manager `target`."""
cache = dict()
return [
copy_bdd(u, target, cache)
for u in roots]
def copy_bdd(
root:
_Ref,
target:
_BDD,
cache:
dict |
None=None
) -> _Ref:
"""Copy BDD with `root` to manager `target`.
@param target:
BDD or ZDD context
@param cache:
for memoizing results
"""
if cache is None:
cache = dict()
return _copy_bdd(root, target, cache)
def _copy_bdd(
u:
_Ref,
bdd:
_BDD,
cache:
dict
) -> _Ref:
"""Recurse to copy node `u` to `bdd`."""
# terminal ?
if u == u.bdd.true:
return bdd.true
# could be handled via cache,
# but frequent case
if u == u.bdd.false:
return bdd.false
# rectify
z = _flip(u, u)
# non-terminal
# memoized ?
k = int(z)
if k in cache:
r = cache[k]
return _flip(r, u)
# recurse
low = _copy_bdd(u.low, bdd, cache)
high = _copy_bdd(u.high, bdd, cache)
# canonicity
# if low.negated != u.low.negated:
# raise AssertionError((low, u.low))
# if high.negated:
# raise AssertionError(high)
# add node
g = bdd.var(u.var)
r = bdd.ite(g, high, low)
# if r.negated:
# raise AssertionError(r)
# memoize
cache[k] = r
return _flip(r, u)
def _flip(
r:
_Ref,
u:
_Ref
) -> _Ref:
"""Negate `r` if `u` is negated.
Else return `r`.
"""
return ~ r if u.negated else r
def copy_zdd(
root:
_Ref,
target:
_BDD,
cache:
dict |
None=None
) -> _Ref:
"""Copy ZDD with `root` to manager `target`.
@param target:
BDD or ZDD context
@param cache:
for memoizing results
"""
if cache is None:
cache = dict()
level = 0
return _copy_zdd(level, root, target, cache)
def _copy_zdd(
level:
int,
u:
_Ref,
target:
_BDD,
cache:
dict[int, _Ref]
) -> _Ref:
"""Recurse to copy node `u` to `target`."""
src: _BDD = u.bdd
# terminal ?
if u == src.false:
return target.false
if level == len(src.vars):
return target.true
# memoized ?
k = int(u)
if k in cache:
return cache[k]
# recurse
v, w = src._top_cofactor(u, level)
low = _copy_zdd(
level + 1, v, target, cache)
high = _copy_zdd(
level + 1, w, target, cache)
# add node
var = src.var_at_level(level)
g = target.var(var)
r = target.ite(g, high, low)
# memoize
cache[k] = r
return r
def dump_json(
nodes:
dict[str, Ref] |
list[Ref],
file_name:
str
) -> None:
"""Write reachable nodes to JSON file.
Writes the nodes that are reachable from
the roots in `nodes` to the JSON file
named `file_name`.
Also dumps the variable names and the
variable order, to the same JSON file.
@param nodes:
maps names to roots of
the BDDs that will be written to
the JSON file
"""
if not nodes:
raise ValueError(
'Need nonempty `nodes` as roots.')
tmp_fname = os.path.join(
SHELVE_DIR, 'temporary_shelf')
os.makedirs(SHELVE_DIR)
try:
with _open_shelf(tmp_fname) as cache,\
open(file_name, 'w') as fd:
_dump_json(nodes, fd, cache)
finally:
# `shelve` file naming
# depends on context
shutil.rmtree(SHELVE_DIR)
def _dump_json(
nodes:
dict[str, _Ref] |
list[_Ref],
fd:
_ty.TextIO,
cache:
_abc.MutableMapping[str, bool]
) -> None:
"""Dump BDD as JSON to file `fd`.
Use `cache` to keep track of
visited nodes.
"""
fd.write('{')
_dump_bdd_info(nodes, fd)
for u in _utils.values_of(nodes):
_dump_bdd(u, fd, cache)
fd.write('\n}\n')
def _dump_bdd_info(
nodes:
dict[str, _Ref] |
list[_Ref],
fd):
"""Dump variable levels and roots.
@param nodes:
maps names to roots of BDDs
"""
roots = _utils.map_container(_node_to_int, nodes)
u = next(iter(_utils.values_of(nodes)))
bdd = u.bdd
var_level = {
var: bdd.level_of_var(var)
for var in bdd.vars}
info = (
'\n"level_of_var": {level}'
',\n"roots": {roots}').format(
level=json.dumps(var_level),
roots=json.dumps(roots))
fd.write(info)
def _dump_bdd(
u:
_Ref,
fd:
_ty.TextIO,
cache:
_abc.MutableMapping[str, bool]
) -> (
int |
str):
"""Recursive step of dumping nodes."""
# terminal ?
if u == u.bdd.true:
return '"T"'
if u == u.bdd.false:
return '"F"'
# rectify
z = _flip(u, u)
# non-terminal
# dumped ?
k = int(z)
if str(k) in cache:
return -k if u.negated else k
# recurse
low = _dump_bdd(u.low, fd, cache)
high = _dump_bdd(u.high, fd, cache)
# dump node
s = f',\n"{k}": [{u.level}, {low}, {high}]'
fd.write(s)
# record as dumped
cache[str(k)] = True
return -k if u.negated else k
def load_json(
file_name:
str,
bdd,
load_order:
_Yes=False
) -> (
dict[str, _Ref] |
list[_Ref]):
"""Add BDDs from JSON `file_name` to `bdd`.
@param load_order:
if `True`,
then load variable order
from `file_name`.
@return:
- keys (or indices) are names
- values are BDD roots
"""
tmp_fname = os.path.join(
SHELVE_DIR, 'temporary_shelf')
os.makedirs(SHELVE_DIR)
try:
with _open_shelf(tmp_fname) as cache,\
open(file_name, 'r') as fd:
nodes = _load_json(
fd, bdd, load_order, cache)
finally:
shutil.rmtree(SHELVE_DIR)
return nodes
def _load_json(
fd:
_abc.Iterable[str],
bdd,
load_order:
_Yes,
cache:
_abc.MutableMapping[str, int]
) -> (
dict[str, _Ref] |
list[_Ref]):
"""Load BDDs from JSON file `fd` to `bdd`."""
context = dict(load_order=load_order)
# if the variable order is going to be loaded,
# then turn off dynamic reordering,
# because it can change the order midway,
# which would not return the loaded order,
# and can also cause failure of
# the assertion below
if load_order:
old_reordering = bdd.configure(
reordering=False)
for line in fd:
d = _parse_line(line)
_store_line(d, bdd, context, cache)
roots = context['roots']
if hasattr(roots, 'items'):
roots = {
name: _node_from_int(k, bdd, cache)
for name, k in roots.items()}
else:
roots = [
_node_from_int(k, bdd, cache)
for k in roots]
# rm refs to cached nodes
for uid in cache:
u = _node_from_int(int(uid), bdd, cache)
if u.ref < 2:
raise AssertionError(u.ref)
# +1 ref due to `incref` in `_make_node`
# +1 ref due to the `_node_from_int`
# call for `u`
if load_order and u.ref < 3:
raise AssertionError(u.ref)
# +1 ref due to `incref` in `_make_node`
# +1 ref due to either:
# - being a successor node
# - being a root node
# (thus referenced in `roots` above)
# +1 ref due to the `_node_from_int`
# call for `u`
bdd.decref(u, _direct=True)
# this module is unusual,
# in that `incref` and `decref` need
# to be called on different `Function`
# instances for the same node
bdd.assert_consistent()
if load_order:
bdd.configure(
reordering=old_reordering)
return roots
def _parse_line(
line:
str
) -> (
dict |
None):
"""Parse JSON from `line`."""
line = line.rstrip()
if line == '{' or line == '}':
return None
if line.endswith(','):
line = line.rstrip(',')
return json.loads('{' + line + '}')
def _store_line(
d:
dict |
None,
bdd:
_BDD,
context:
dict,
cache:
_abc.MutableMapping[str, int]
) -> None:
"""Interpret data in `d`."""
if d is None:
return
order = d.get('level_of_var')
if order is not None:
order = {
str(k): v
for k, v in order.items()}
bdd.declare(*order)
context['level_of_var'] = order
context['var_at_level'] = {
v: k for k, v in order.items()}
if context['load_order']:
bdd.reorder(order)
return
roots = d.get('roots')
if roots is not None:
context['roots'] = roots
return
_make_node(d, bdd, context, cache)
def _make_node(
d:
dict,
bdd:
_BDD,
context:
dict,
cache:
_abc.MutableMapping[str, int]
) -> None:
"""Create a new node in `bdd` from `d`."""
(uid, (level, low_id, high_id)), = d.items()
k, level = map(int, (uid, level))
if k <= 0:
raise AssertionError(k)
if level < 0:
raise AssertionError(level)
low_id = _decode_node(low_id)
high_id = _decode_node(high_id)
if str(k) in cache:
return
low = _node_from_int(low_id, bdd, cache)
high = _node_from_int(high_id, bdd, cache)
var = context['var_at_level'][level]
if context['load_order']:
u = bdd.find_or_add(var, low, high)
else:
g = bdd.var(var)
u = bdd.ite(g, high, low)
if u.negated:
raise AssertionError(u)
# memoize
cache[str(k)] = int(u)
bdd.incref(u)
def _decode_node(
s:
str
) -> int:
"""Map `s` to node-like number."""
match s:
case 'F':
return -1
case 'T':
return 1
return int(s)
def _node_from_int(
uid:
int,
bdd:
_BDD,
cache:
_abc.Mapping[str, int]
) -> _Ref:
"""Return `bdd` node represented by `uid`."""
if uid == -1:
return bdd.false
elif uid == 1:
return bdd.true
# not constant
k = cache[str(abs(uid))]
u = bdd._add_int(k)
return ~ u if uid < 0 else u
def _node_to_int(
u:
_Ref
) -> int:
"""Return numeric representation of `u`."""
z = _flip(u, u)
k = int(z)
return -k if u.negated else k
================================================
FILE: dd/_parser.py
================================================
"""Construct BDD nodes from quantified Boolean formulae."""
# Copyright 2015 by California Institute of Technology
# All rights reserved. Licensed under BSD-3.
#
import collections.abc as _abc
import logging
import typing as _ty
import astutils
_TABMODULE: _ty.Final[str] =\
'dd._expr_parser_state_machine'
class _Token(_ty.Protocol):
type: str
value: str
class Lexer(astutils.Lexer):
"""Lexer for Boolean formulae."""
def __init__(
self
) -> None:
self.reserved = {
'ite':
'ITE',
'False':
'FALSE',
'True':
'TRUE',
'FALSE':
'FALSE',
'TRUE':
'TRUE'}
self.delimiters = [
'LPAREN',
'RPAREN',
'COMMA']
self.operators = [
'NOT',
'AND',
'OR',
'XOR',
'IMPLIES',
'EQUIV',
'EQUALS',
'MINUS',
'DIV',
'AT',
'COLON',
'FORALL',
'EXISTS',
'RENAME']
self.misc = [
'NAME',
'NUMBER']
super().__init__()
def t_NAME(
self,
token:
_Token
) -> _Token:
r"""
[A-Za-z_]
[A-Za-z0-9_']*
"""
token.type = self.reserved.get(
token.value, 'NAME')
return token
def t_AND(
self,
token:
_Token
) -> _Token:
r"""
\&\&
| \&
| /\\
"""
token.value = '&'
return token
def t_OR(
self,
token:
_Token
) -> _Token:
r"""
\|\|
| \|
| \\/
"""
token.value = '|'
return token
def t_NOT(
self,
token:
_Token
) -> _Token:
r"""
\~
| !
"""
token.value = '!'
return token
def t_IMPLIES(
self,
token:
_Token
) -> _Token:
r"""
=>
| \->
"""
token.value = '=>'
return token
def t_EQUIV(
self,
token:
_Token
) -> _Token:
r"""
<=>
| <\->
"""
token.value = '<->'
return token
t_XOR = r'''
\#
| \^
'''
t_EQUALS = r' = '
t_LPAREN = r' \( '
t_RPAREN = r' \) '
t_MINUS = r' \- '
t_NUMBER = r' \d+ '
t_COMMA = r' , '
t_COLON = r' : '
t_FORALL = r' \\ A '
t_EXISTS = r' \\ E '
t_RENAME = r' \\ S '
t_DIV = r' / '
t_AT = r' @ '
t_ignore = ''.join(['\x20', '\t'])
def t_trailing_comment(
self,
token:
_Token
) -> None:
r' \\ \* .* '
return None
def t_doubly_delimited_comment(
self,
token:
_Token
) -> None:
r"""
\( \*
[\s\S]*?
\* \)
"""
return None
def t_newline(
self,
token
) -> None:
r' \n+ '
_ParserResult = _ty.TypeVar('_ParserResult')
class _ParserProtocol(
_ty.Protocol,
_ty.Generic[
_ParserResult]):
"""Parser internal interface."""
def _apply(
self,
operator:
str,
*operands:
_ty.Any
) -> _ParserResult:
...
def _add_var(
self,
name:
str
) -> _ParserResult:
...
def _add_int(
self,
numeric_literal:
str
) -> _ParserResult:
...
def _add_bool(
self,
bool_literal:
str
) -> _ParserResult:
...
class Parser(
astutils.Parser,
_ParserProtocol):
"""Parser for Boolean formulae."""
def __init__(
self
) -> None:
tabmodule_is_defined = (
hasattr(self, 'tabmodule') and
self.tabmodule)
if not tabmodule_is_defined:
self.tabmodule = _TABMODULE
self.start = 'expr'
# low to high
self.precedence = (
('left',
'COLON'),
('left',
'EQUIV'),
('left',
'IMPLIES'),
('left',
'MINUS'),
('left',
'XOR'),
('left',
'OR'),
('left',
'AND'),
('left',
'EQUALS'),
('right',
'NOT'),
)
kw = dict()
kw.setdefault('lexer', Lexer())
super().__init__(**kw)
def _apply(
self,
operator:
str,
*operands:
_ty.Any):
"""Return syntax tree of application."""
if operator == r'\S':
match operands:
case subs, expr:
pass
case _:
raise AssertionError(operands)
return self.nodes.Operator(
operator, expr, subs)
return self.nodes.Operator(
operator, *operands)
def _add_var(
self,
name:
str):
"""Return syntax tree for identifier."""
return self.nodes.Terminal(
name, 'var')
def _add_int(
self,
numeric_literal:
str):
"""Return syntax tree of given index."""
return self.nodes.Terminal(
numeric_literal, 'num')
def _add_bool(
self,
bool_literal:
str):
"""Return syntax tree for Boolean."""
return self.nodes.Terminal(
bool_literal, 'bool')
def p_bool(
self,
p:
list
) -> None:
"""expr : TRUE
| FALSE
"""
p[0] = self._add_bool(p[1])
def p_node(
self,
p:
list
) -> None:
"""expr : AT number"""
p[0] = p[2]
def p_number(
self,
p:
list
) -> None:
"""number : NUMBER"""
p[0] = self._add_int(p[1])
def p_negative_number(
self,
p:
list
) -> None:
"""number : MINUS NUMBER """
numeric_literal = f'{p[1]}{p[2]}'
p[0] = self._add_int(numeric_literal)
def p_var(
self,
p:
list
) -> None:
"""expr : name"""
p[0] = self._add_var(p[1].value)
def p_unary(
self,
p:
list
) -> None:
"""expr : NOT expr"""
p[0] = self._apply(
p[1], p[2])
def p_binary(
self,
p:
list
) -> None:
"""expr : expr AND expr
| expr OR expr
| expr XOR expr
| expr IMPLIES expr
| expr EQUIV expr
| expr EQUALS expr
| expr MINUS expr
"""
p[0] = self._apply(
p[2], p[1], p[3])
def p_ternary_conditional(
self,
p:
list
) -> None:
("""expr : ITE LPAREN """
""" expr COMMA """
""" expr COMMA """
""" expr RPAREN""")
p[0] = self._apply(
p[1], p[3], p[5], p[7])
def p_quantifier(
self,
p:
list
) -> None:
"""expr : EXISTS names COLON expr
| FORALL names COLON expr
"""
p[0] = self._apply(
p[1], p[2], p[4])
def p_rename(
self,
p:
list
) -> None:
"""expr : RENAME subs COLON expr"""
p[0] = self._apply(
p[1], p[2], p[4])
def p_substitutions_iter(
self,
p:
list
) -> None:
"""subs : subs COMMA sub"""
u = p[1]
u.append(p[3])
p[0] = u
def p_substitutions_end(
self,
p:
list
) -> None:
"""subs : sub"""
p[0] = [p[1]]
def p_substitution(
self,
p:
list
) -> None:
"""sub : name DIV name"""
new = p[1]
old = p[3]
p[0] = (old, new)
def p_names_iter(
self,
p:
list
) -> None:
"""names : names COMMA name"""
u = p[1]
u.append(p[3])
p[0] = u
def p_names_end(
self,
p:
list
) -> None:
"""names : name"""
p[0] = [p[1]]
def p_name(
self,
p:
list
) -> None:
"""name : NAME"""
p[0] = self.nodes.Terminal(
p[1], 'var')
def p_paren(
self,
p:
list
) -> None:
"""expr : LPAREN expr RPAREN"""
p[0] = p[2]
_Ref = _ty.TypeVar('_Ref')
class _BDD(_ty.Protocol[
_Ref]):
"""Interface of BDD context."""
@property
def false(
self
) -> _Ref:
...
@property
def true(
self
) -> _Ref:
...
def var(
self,
name:
str
) -> _Ref:
...
def apply(
self,
op:
str,
u:
_Ref,
v:
_Ref |
None=None,
w:
_Ref |
None=None
) -> _Ref:
...
def quantify(
self,
u:
_Ref,
qvars:
set[str],
forall:
bool=False
) -> _Ref:
...
def rename(
self,
u:
_Ref,
renaming:
_abc.Mapping
) -> _Ref:
...
def _add_int(
self,
number:
int
) -> _Ref:
...
class _Translator(Parser):
"""Parser for Boolean formulas."""
def __init__(
self
) -> None:
super().__init__()
self._reset_state()
def parse(
self,
expression:
str,
bdd:
_BDD[_Ref]
) -> _Ref:
"""Return BDD of `expression`.
The returned BDD is stored in
the BDD manager `bdd`.
"""
self._bdd = bdd
u = super().parse(expression)
self._reset_state()
return u
def _reset_state(
self
) -> None:
"""Set default attribute values."""
self._bdd = None
has_lr_stack = (
self.parser is not None and
hasattr(self.parser, 'statestack') and
hasattr(self.parser, 'symstack'))
if not has_lr_stack:
return
self.parser.restart()
# Avoid references to BDD nodes
# remaining in the LR stack,
# because this side-effect would
# change the reference-counts.
def _add_bool(
self,
bool_literal:
str):
"""Return BDD for Boolean values."""
value = bool_literal.lower()
if value not in {'false', 'true'}:
raise ValueError(value)
return getattr(self._bdd, value)
def _add_int(
self,
numeric_literal:
str):
"""Return BDD with given index."""
number = int(numeric_literal)
return self._bdd._add_int(number)
def _add_var(
self,
name:
str):
"""Return BDD for variable `name`."""
return self._bdd.var(name)
def _apply(
self,
operator:
str,
*operands:
_ty.Any):
"""Return BDD from applying `operator`."""
match operator:
case r'\A' | r'\E':
names, expr = operands
names = {
x.value
for x in names}
forall = (operator == r'\A')
return self._bdd.quantify(
expr, names,
forall=forall)
case r'\S':
subs, expr = operands
renaming = {
k.value: v.value
for k, v in subs}
return self._bdd.rename(
expr, renaming)
return self._bdd.apply(
operator, *operands)
_parsers = dict()
def add_expr(
expression:
str,
bdd:
_BDD[_Ref]
) -> _Ref:
"""Return `bdd` node for `expression`.
Creates in `bdd` a node that represents
`expression`, and returns this node.
"""
if 'boolean' not in _parsers:
_parsers['boolean'] = _Translator()
translator = _parsers['boolean']
return translator.parse(expression, bdd)
def _rewrite_tables(
outputdir:
str='./'
) -> None:
"""Recache state machine of parser."""
astutils.rewrite_tables(
Parser, _TABMODULE, outputdir)
astutils.rewrite_tables(
_Translator, _TABMODULE, outputdir)
def _main(
) -> None:
"""Recompute parser state machine.
Cache the state machine in a file.
Configure logging.
"""
log = logging.getLogger('astutils')
log.setLevel('DEBUG')
log.addHandler(logging.StreamHandler())
_rewrite_tables()
if __name__ == '__main__':
_main()
================================================
FILE: dd/_utils.py
================================================
"""Convenience functions."""
# Copyright 2017-2018 by California Institute of Technology
# All rights reserved. Licensed under 3-clause BSD.
#
import collections as _cl
import collections.abc as _abc
import os
import shlex as _sh
import subprocess as _sbp
import textwrap as _tw
import types
import typing as _ty
import dd._abc
try:
import networkx as _nx
except ImportError as error:
_nx = None
_nx_error = error
if _nx is not None:
MultiDiGraph: _ty.TypeAlias = _nx.MultiDiGraph
# The mapping from values of argument `op` of
# `__richcmp__()` of Cython objects,
# to the corresponding operator symbols.
# Constants are defined in `cpython.object`.
_CY_SYMBOLS: _ty.Final = {
2: '==',
3: '!=',
0: '<',
1: '<=',
4: '>',
5: '>='}
def import_module(
module_name:
str
) -> types.ModuleType:
"""Return module with `module_name`, if present.
Raise `ImportError` otherwise.
"""
modules = dict(
networkx=_nx)
if module_name in modules:
return modules[module_name]
errors = dict(
networkx=_nx_error)
raise errors[module_name]
def print_var_levels(
bdd
) -> None:
"""Print `bdd` variables ordered by level."""
n = len(bdd.vars)
levels = [
bdd.var_at_level(level)
for level in range(n)]
print(
'Variable order (starting at level 0):\n'
f'{levels}')
def var_counts(
bdd
) -> str:
"""Return levels and numbers of variables, CUDD indices.
@type bdd:
`dd.cudd.BDD` or
`dd.cudd_zdd.ZDD`
"""
n_declared_vars = len(bdd.vars)
n_cudd_vars = bdd._number_of_cudd_vars()
return _tw.dedent(f'''
There are:
{n_cudd_vars} variable indices in CUDD,
{n_declared_vars} declared variables in {bdd!r}.
So the set of levels of the declared variables
is not a contiguous range of integers.
This can occur when specific levels have been
given to `{type(bdd)}.add_var()`.
The declared variables and their levels are:
{bdd.var_levels}
''')
def contiguous_levels(
callable:
str,
bdd
) -> str:
"""Return requirement about contiguous levels.
@type bdd:
`dd.cudd.BDD` or
`dd.cudd_zdd.ZDD`
"""
return _tw.dedent(f'''
The callable `{callable}()` requires that
the number of variable indices in CUDD, and
the number of declared variables in {bdd!r}
be equal.
''')
def raise_runtimerror_about_ref_count(
ref_count_lb:
int,
name:
str,
class_name:
str
) -> _ty.NoReturn:
"""Raise `RuntimeError` about reference count lower bound.
Call this function when an unexpected nonpositive
lower bound on a node's reference count is detected
for a `Function` instance.
@param ref_count_lb:
lower bound on the reference count of
the node that the `Function` instance points to.
```tla
ASSUME
ref_count_lb <= 0
```
@param name:
to mention as location where
the error was detected. For example:
```python
'method `dd.cudd.BDD.decref`'
```
@param class_name:
to mention as name of
the class of the object where the value
`ref_count_lb` was found. For example:
```python
'`dd.cudd.Function`'
```
"""
if ref_count_lb > 0:
raise ValueError(ref_count_lb)
raise RuntimeError(
f'The {name} requires '
'that `u._ref > 0` '
f'(where `u` is an instance of {class_name}). '
'This ensures that deallocated memory '
'in CUDD will not be accessed. The current '
f'value of attribute `_ref` is:\n{ref_count_lb}\n'
'For more information read the docstring of '
f'the class {class_name}.')
@_ty.overload
def map_container(
mapper:
_abc.Callable,
container:
_abc.Mapping
) -> dict:
...
@_ty.overload
def map_container(
mapper:
_abc.Callable,
container:
_abc.Iterable
) -> list:
...
def map_container(
mapper,
container):
"""Map `container`, using `mapper()`.
If `container` is a sequence,
then map each item.
If `container` is a mapping of
keys to values, then map each value.
"""
if isinstance(container, _abc.Mapping):
return _map_values(mapper, container)
return list(map(mapper, container))
def _map_values(
mapper:
_abc.Callable,
kv:
_abc.Mapping
) -> dict:
"""Map each value of `kv` using `mapper()`.
The keys of `kv` remain unchanged.
"""
return {k: mapper(v) for k, v in kv.items()}
def values_of(
container:
_abc.Mapping |
_abc.Collection
) -> _abc.Iterable:
"""Return container values.
@return:
- `container.values()` if
`container` is a mapping
- `container` otherwise
"""
if isinstance(container, _abc.Mapping):
return container.values()
return container
def total_memory(
) -> (
int |
None):
"""Return number of bytes of memory.
Requires that:
- `SC_PAGE_SIZE` and
- `SC_PHYS_PAGES`
be readable via `os.sysconf()`.
"""
names = os.sysconf_names
has_both = (
'SC_PAGE_SIZE' in names and
'SC_PHYS_PAGES' in names)
if not has_both:
print(
'skipping check that '
'initial memory estimate fits '
'in available memory of system, '
"because either `'SC_PAGE_SIZE'` or "
"`'SC_PHYS_PAGES'` undefined in "
'`os.sysconf_names`.')
return None
page_size = os.sysconf('SC_PAGE_SIZE')
n_pages = os.sysconf('SC_PHYS_PAGES')
both_defined = (
page_size >= 0 and
n_pages >= 0)
if not both_defined:
return None
return page_size * n_pages
_OPERATOR_MAP: _ty.Final = dict(
bdd=dict(
unary=dd._abc.UNARY_OPERATOR_SYMBOLS,
binary=dd._abc.BINARY_OPERATOR_SYMBOLS,
ternary=dd._abc.TERNARY_OPERATOR_SYMBOLS,
all=dd._abc.BDD_OPERATOR_SYMBOLS))
def assert_operator_arity(
op:
str,
v:
object |
None,
w:
object |
None,
diagram_type:
_ty.Literal[
'bdd']
) -> None:
"""Raise `ValueError` if unexpected values.
Asserts:
- `op` is an operator symbol
- `v` is `None` if `op` is a unary operator
- `w` is `None` if `op` has arity <= 2
"""
operators = _OPERATOR_MAP[diagram_type]
if op not in operators['all']:
raise ValueError(
f'Unknown operator: "{op}"')
if op in operators['unary']:
if v is not None:
raise ValueError(
f'`v is not None`, but: {v}')
if w is not None:
raise ValueError(
f'`w is not None`, but: {w}')
elif op in operators['binary']:
if v is None:
raise ValueError(
'`v is None`')
if w is not None:
raise ValueError(
f'`w is not None`, but: {w}')
elif op in operators['ternary']:
if v is None:
raise ValueError(
'`v is None`')
if w is None:
raise ValueError(
'`w is None`')
_GraphType: _ty.TypeAlias = _ty.Literal[
'digraph',
'graph',
'subgraph']
DOT_FILE_TYPES: _ty.Final = {
'pdf', 'svg', 'png', 'dot'}
class DotGraph:
def __init__(
self,
graph_type:
_GraphType='digraph',
rank:
str |
None=None
) -> None:
"""A DOT graph."""
self.graph_type = graph_type
self.rank = rank
self.nodes = _cl.defaultdict(dict)
self.edges = _cl.defaultdict(list)
self.subgraphs = list()
def add_node(
self,
node,
**kw
) -> None:
"""Add node with attributes `kw`.
If node exists, update its attributes.
"""
self.nodes[node].update(kw)
def add_edge(
self,
start_node,
end_node,
**kw
) -> None:
"""Add edge with attributes `kw`.
Multiple edges can exist between the same nodes.
"""
self.edges[start_node, end_node].append(kw)
def to_dot(
self,
graph_type:
_GraphType |
None=None
) -> str:
"""Return DOT code."""
subgraphs = ''.join(
g.to_dot(
graph_type='subgraph')
for g in self.subgraphs)
def format_attributes(
attr
) -> str:
"""Return formatted assignment."""
return ', '.join(
f'{k}="{v}"'
for k, v in attr.items())
def format_node(
u,
attr
) -> str:
"""Return DOT code for node."""
attributes = format_attributes(attr)
return f'{u} [{attributes}];'
def format_edge(
u,
v,
attr
) -> str:
"""Return DOT code for edge."""
attributes = format_attributes(attr)
return f'{u} -> {v} [{attributes}];'
nodes = '\n'.join(
format_node(u, attr)
for u, attr in self.nodes.items())
edges = list()
for (u, v), attrs in self.edges.items():
for attr in attrs:
edge = format_edge(u, v, attr)
edges.append(edge)
edges = '\n'.join(edges)
indent_level = 4 * '\x20'
def fmt(
text:
str
) -> str:
"""Return indented text."""
newline = '\n' if text else ''
return newline + _tw.indent(
text,
prefix=4 * indent_level)
nodes = fmt(nodes)
edges = fmt(edges)
subgraphs = fmt(subgraphs)
if graph_type is None:
graph_type = self.graph_type
if self.rank is None:
rank = ''
else:
rank = f'rank = {self.rank}'
return _tw.dedent(f'''
{graph_type} {{
{rank}{nodes}{edges}{subgraphs}
}}
''')
def dump(
self,
filename:
str,
filetype:
str,
**kw
) -> None:
"""Write to file."""
if filetype not in DOT_FILE_TYPES:
raise ValueError(
f'Unknown file type "{filetype}" '
f'for "{filename}"')
dot_code = self.to_dot()
if filetype == 'dot':
with open(filename, 'w') as fd:
fd.write(dot_code)
return
dot = _sh.split(f'''
dot
-T{filetype}
-o '{filename}'
''')
_sbp.run(
dot,
encoding='utf8',
input=dot_code,
capture_output=True,
check=True)
================================================
FILE: dd/autoref.py
================================================
"""Wraps `dd.bdd` to automate reference counting.
For function docstrings, refer to `dd.bdd`.
"""
# Copyright 2015 by California Institute of Technology
# All rights reserved. Licensed under BSD-3.
#
import collections.abc as _abc
import logging
import typing as _ty
import warnings
import dd._abc
import dd._copy as _copy
import dd._utils as _utils
import dd.bdd as _bdd
log = logging.getLogger(__name__)
_Yes: _ty.TypeAlias = dd._abc.Yes
_Cardinality: _ty.TypeAlias = dd._abc.Cardinality
_VariableName: _ty.TypeAlias = dd._abc.VariableName
_Level: _ty.TypeAlias = dd._abc.Level
_VariableLevels: _ty.TypeAlias = dd._abc.VariableLevels
_Ref: _ty.TypeAlias = _ty.Union['Function']
_MaybeRef: _ty.TypeAlias = '''(
_Ref |
None
)'''
_Fork: _ty.TypeAlias = '''(
tuple[
_Level,
_MaybeRef,
_MaybeRef]
)'''
_Assignment: _ty.TypeAlias = dd._abc.Assignment
_Renaming: _ty.TypeAlias = dd._abc.Renaming
_Formula: _ty.TypeAlias = dd._abc.Formula
class BDD(dd._abc.BDD[_Ref]):
"""Shared ordered binary decision diagram.
It takes and returns `Function` instances,
which automate reference counting.
Attributes:
- `vars`: `dict` mapping `variables` to `int` levels
Do not assign the `dict` itself.
For docstrings, refer to methods of `dd.bdd.BDD`,
with the difference that `Function`s replace nodes
as arguments and returned types.
"""
# omitted docstrings are inheritted from `super()`
def __init__(
self,
levels:
_VariableLevels |
None=None):
manager = _bdd.BDD(levels)
self._bdd = manager
self.vars: _VariableLevels = manager.vars
def __eq__(
self,
other:
'BDD'
) -> _Yes:
if not isinstance(other, BDD):
raise NotImplementedError
return (self._bdd is other._bdd)
def __len__(
self
) -> _Cardinality:
return len(self._bdd)
def __contains__(
self,
u:
_Ref
) -> _Yes:
if self is not u.bdd:
raise ValueError('`self is not u.bdd`')
return u.node in self._bdd
def __str__(
self
) -> str:
return (
'Binary decision diagram (`dd.bdd.BDD` wrapper):\n'
'------------------------\n'
f'\t {len(self.vars)} BDD variables\n'
f'\t {len(self)} nodes\n')
def _wrap(
self,
u:
int
) -> _Ref:
"""Return reference to node `u`.
References can be thought of also
as edges.
@param u:
node in `self._bdd`
"""
if u not in self._bdd:
raise ValueError(u)
return Function(u, self)
def configure(
self,
**kw
) -> dict[
str,
_ty.Any]:
return self._bdd.configure(**kw)
def succ(
self,
u
) -> _Fork:
i, v, w = self._bdd.succ(u.node)
def wrap(
node:
int |
None
) -> _MaybeRef:
match node:
case None:
return None
case int():
return self._wrap(node)
raise AssertionError(node)
return i, wrap(v), wrap(w)
def incref(
self,
u:
_Ref
) -> None:
self._bdd.incref(u.node)
def decref(
self,
u:
_Ref,
**kw
) -> None:
self._bdd.decref(u.node)
def declare(
self,
*variables:
_VariableName
) -> None:
for var in variables:
self.add_var(var)
def add_var(
self,
var:
_VariableName,
level:
_Level |
None=None
) -> _Level:
return self._bdd.add_var(var, level=level)
def var(
self,
var:
_VariableName
) -> _Ref:
r = self._bdd.var(var)
return self._wrap(r)
def var_at_level(
self,
level:
_Level
) -> _VariableName:
return self._bdd.var_at_level(level)
def level_of_var(
self,
var:
_VariableName
) -> _Level:
return self._bdd.level_of_var(var)
@property
def var_levels(
self
) -> _VariableLevels:
return self._bdd.var_levels
def reorder(
self,
var_order:
_VariableLevels |
None=None
) -> None:
reorder(self, var_order)
def copy(
self,
u:
_Ref,
other:
'BDD'
) -> _Ref:
if u not in self:
raise ValueError(u)
if self is other:
log.warning('copying node to same manager')
return u
r = self._bdd.copy(u.node, other._bdd)
return other._wrap(r)
def support(
self,
u:
_Ref,
as_levels:
_Yes=False
) -> set[_VariableName]:
if u not in self:
raise ValueError(u)
return self._bdd.support(u.node, as_levels)
def let(
self,
definitions:
_Renaming |
_Assignment |
dict[_VariableName, _Ref],
u:
_Ref
) -> _Ref:
if u not in self:
raise ValueError(u)
if not definitions:
return u
var = next(iter(definitions))
value = definitions[var]
match value:
case str() | bool():
d = definitions
case Function():
def node_of(
ref
) -> int:
if isinstance(ref, Function):
return ref.node
raise ValueError(
'Expected homogeneous type '
'for `dict` values.')
d = {
var: node_of(value)
for var, value in
definitions.items()}
case _:
raise TypeError(value)
r = self._bdd.let(d, u.node)
return self._wrap(r)
def quantify(
self,
u:
_Ref,
qvars:
_abc.Iterable[
_VariableName],
forall:
_Yes=False
) -> _Ref:
if u not in self:
raise ValueError(u)
r = self._bdd.quantify(u.node, qvars, forall)
return self._wrap(r)
def forall(
self,
qvars:
_abc.Iterable[_VariableName],
u:
_Ref
) -> _Ref:
return self.quantify(u, qvars, forall=True)
def exist(
self,
qvars:
_abc.Iterable[
_VariableName],
u:
_Ref
) -> _Ref:
return self.quantify(u, qvars, forall=False)
def ite(
self,
g:
_Ref,
u:
_Ref,
v:
_Ref
) -> _Ref:
if g not in self:
raise ValueError(g)
if u not in self:
raise ValueError(u)
if v not in self:
raise ValueError(v)
r = self._bdd.ite(g.node, u.node, v.node)
return self._wrap(r)
def find_or_add(
self,
var:
_VariableName,
low:
_Ref,
high:
_Ref
) -> _Ref:
"""Return node `IF var THEN high ELSE low`."""
level = self.level_of_var(var)
r = self._bdd.find_or_add(level, low.node, high.node)
return self._wrap(r)
def count(
self,
u:
_Ref,
nvars:
_Cardinality |
None=None
) -> _Cardinality:
if u not in self:
raise ValueError(u)
return self._bdd.count(u.node, nvars)
def pick_iter(
self,
u:
_Ref,
care_vars:
set[_VariableName] |
None=None
) -> _abc.Iterable[
_Assignment]:
if u not in self:
raise ValueError(u)
return self._bdd.pick_iter(u.node, care_vars)
def add_expr(
self,
e:
_Formula
) -> _Ref:
r = self._bdd.add_expr(e)
return self._wrap(r)
def to_expr(
self,
u:
_Ref
) -> _Formula:
if u not in self:
raise ValueError(u)
return self._bdd.to_expr(u.node)
def apply(
self,
op:
dd._abc.OperatorSymbol,
u:
_Ref,
v:
_MaybeRef
=None,
w:
_MaybeRef
=None
) -> _Ref:
if u not in self:
raise ValueError(u)
if v is None and w is not None:
raise ValueError(w)
if v is not None and v not in self:
raise ValueError(v)
if w is not None and w not in self:
raise ValueError(w)
if v is None:
r = self._bdd.apply(op, u.node)
elif w is None:
r = self._bdd.apply(op, u.node, v.node)
else:
r = self._bdd.apply(op, u.node, v.node, w.node)
return self._wrap(r)
def _add_int(
self,
i:
int
) -> _Ref:
r = self._bdd._add_int(i)
return self._wrap(r)
def cube(
self,
dvars:
_Assignment
) -> _Ref:
r = self._bdd.cube(dvars)
return self._wrap(r)
def collect_garbage(
self
) -> None:
"""Recursively remove nodes with zero reference count."""
self._bdd.collect_garbage()
def dump(
self,
filename:
str,
roots:
dict[str, _Ref] |
list[_Ref] |
None=None,
filetype:
dd._abc.BDDFileType |
dd._abc.PickleFileType |
None=None,
**kw
) -> None:
"""Write BDDs to `filename`.
The file type is inferred from the
extension (case insensitive),
unless a `filetype` is explicitly given.
`filetype` can have the values:
- `'pickle'` for Pickle
- `'pdf'` for PDF
- `'png'` for PNG
- `'svg'` for SVG
- `'json'` for JSON
If `filetype is None`, then `filename`
must have an extension that matches
one of the file types listed above.
Dump nodes reachable from `roots`.
If `roots is None`,
then all nodes in the manager are dumped.
Dumping a JSON file requires that `roots`
be nonempty.
@type roots:
- `list` of nodes, or
- for JSON or Pickle:
`dict` that maps names
to nodes
"""
# The method's docstring is a slight modification
# of the docstring of the method `dd._abc.BDD.dump`.
if filetype is None:
name = filename.lower()
if name.endswith('.pdf'):
filetype = 'pdf'
elif name.endswith('.png'):
filetype = 'png'
elif name.endswith('.svg'):
filetype = 'svg'
elif name.endswith('.dot'):
filetype = 'dot'
elif name.endswith('.p'):
filetype = 'pickle'
elif name.endswith('.json'):
filetype = 'json'
else:
raise ValueError(
'cannot infer file type '
'from extension of file '
f'name "{filename}"')
if filetype == 'json':
if roots is None:
raise ValueError(roots)
_copy.dump_json(roots, filename)
return
elif (filetype != 'pickle' and
filetype not in _utils.DOT_FILE_TYPES):
raise ValueError(filetype)
if roots is not None:
def mapper(u):
return u.node
roots = _utils.map_container(
mapper, roots)
self._bdd.dump(
filename,
roots=roots,
filetype=filetype)
def load(
self,
filename:
str,
levels:
_Yes=True
) -> (
dict[str, _Ref] |
list[_Ref]):
"""Load nodes from Pickle or JSON file `filename`.
If `levels is True`,
then load variables at the same levels.
Otherwise, add missing variables.
@return:
roots of the loaded BDDs
@rtype:
depends on the contents of the file,
either:
- `dict` that maps names
to nodes, or
- `list` of nodes
"""
# This method's docstring is a slight
# modification of the docstring of
# the method `dd._abc.BDD.dump`.
name = filename.lower()
if name.endswith('.p'):
return self._load_pickle(
filename, levels=levels)
elif name.endswith('.json'):
nodes = _copy.load_json(filename, self)
def check(
node
) -> Function:
if isinstance(node, Function):
return node
raise AssertionError(node)
match nodes:
case dict():
return {
k: check(v)
for k, v in nodes.items()}
case list():
return list(map(check, nodes))
case _:
raise AssertionError(nodes)
else:
raise ValueError(
f'Unknown file type of "{filename}"')
def _load_pickle(
self,
filename:
str,
levels:
_Yes=True
) -> (
dict[str, _Ref] |
list[_Ref]):
roots = self._bdd.load(filename, levels=levels)
return _utils.map_container(self._wrap, roots)
def assert_consistent(
self
) -> None:
self._bdd.assert_consistent()
@property
def false(
self
) -> _Ref:
u = self._bdd.false
return self._wrap(u)
@property
def true(
self
) ->_Ref:
u = self._bdd.true
return self._wrap(u)
def image(
trans:
_Ref,
source:
_Ref,
rename:
_Renaming,
qvars:
set[_VariableName],
forall:
_Yes=False
) -> _Ref:
if trans.bdd is not source.bdd:
raise ValueError(
(trans.bdd, source.bdd))
u = _bdd.image(
trans.node, source.node, rename,
qvars, trans.manager, forall)
return trans.bdd._wrap(u)
def preimage(
trans:
_Ref,
target:
_Ref,
rename:
_Renaming,
qvars:
set[_VariableName],
forall:
_Yes=False
) -> _Ref:
if trans.bdd is not target.bdd:
raise ValueError(
(trans.bdd, target.bdd))
u = _bdd.preimage(
trans.node, target.node, rename,
qvars, trans.manager, forall)
return trans.bdd._wrap(u)
def reorder(
bdd:
BDD,
order:
_VariableLevels |
None=None
) -> None:
"""Apply Rudell's sifting algorithm to `bdd`."""
_bdd.reorder(bdd._bdd, order=order)
def copy_vars(
source:
BDD,
target:
BDD
) -> None:
_copy.copy_vars(source._bdd, target._bdd)
def copy_bdd(
u:
_Ref,
target:
BDD
) -> _Ref:
r = _bdd.copy_bdd(u.node, u.manager, target._bdd)
return target._wrap(r)
class Function(dd._abc.Operator):
r"""Convenience wrapper for edges returned by `BDD`.
```python
import dd.autoref
bdd = dd.autoref.BDD()
bdd.declare('x', 'y')
nd = bdd._bdd.add_expr(r'x /\ y')
# `nd` is an integer
# `bdd._bdd` is an instance of the
# class `dd.bdd.BDD`
u = _bdd.Function(nd, bdd)
```
Attributes:
- `node`: `int` that describes edge (signed node)
- `bdd`: `dd.autoref.BDD` instance that node belongs to
- `manager`: `dd.bdd.BDD` instance that node belongs to
Operations are valid only between functions with
the same `BDD` in `Function.bdd`.
After all references to a `Function` have been deleted,
the reference count of its associated node is decremented.
To explicitly release a `Function` instance, invoke `del f`.
The design here is inspired by the PyEDA package.
"""
def __init__(
self,
node:
int,
bdd:
BDD
) -> None:
if node not in bdd._bdd:
raise ValueError(node)
self.bdd = bdd
self.manager = bdd._bdd
self.node = node
self.manager.incref(node)
def __hash__(
self
) -> int:
return self.node
def to_expr(
self
) -> _Formula:
"""Return Boolean expression of function."""
return self.manager.to_expr(self.node)
def __int__(
self
) -> int:
return self.node
def __len__(
self
) -> _Cardinality:
return len(self.manager.descendants([self.node]))
@property
def dag_size(
self
) -> _Cardinality:
return len(self)
def __del__(
self
) -> None:
"""Decrement reference count of `self.node` in `self.bdd`."""
if self.node is None:
return
node = self.node
self.node = None
self.manager.decref(node)
def __eq__(
self,
other
) -> _Yes:
if other is None:
return False
if not isinstance(other, Function):
raise NotImplementedError
if self.bdd is not other.bdd:
raise ValueError((self.bdd, other.bdd))
return self.node == other.node
def __ne__(
self,
other
) -> _Yes:
if other is None:
return True
if not isinstance(other, Function):
raise NotImplementedError
if self.bdd is not other.bdd:
raise ValueError((self.bdd, other.bdd))
return not (self == other)
def __le__(
self,
other
) -> _Yes:
if not isinstance(other, Function):
raise NotImplementedError
return (other | ~ self) == self.bdd.true
def __lt__(
self,
other
) -> _Yes:
if not isinstance(other, Function):
raise NotImplementedError
return self <= other and self != other
def __invert__(
self
) -> _Ref:
return self._apply('not', other=None)
def __and__(
self,
other:
_Ref
) -> _Ref:
return self._apply('and', other)
def __or__(
self,
other:
_Ref
) -> _Ref:
return self._apply('or', other)
def __xor__(
self,
other:
_Ref
) -> _Ref:
return self._apply('xor', other)
def implies(
self,
other:
_Ref
) -> _Ref:
return self._apply('implies', other)
def equiv(
self,
other:
_Ref
) -> _Ref:
return self._apply('equiv', other)
def _apply(
self,
op:
dd._abc.OperatorSymbol,
other:
_MaybeRef
) -> _Ref:
"""Return result of operation `op` with `other`."""
# unary op ?
if other is None:
u = self.manager.apply(op, self.node)
else:
if self.bdd is not other.bdd:
raise ValueError((self.bdd, other.bdd))
u = self.manager.apply(op, self.node, other.node)
return Function(u, self.bdd)
@property
def level(
self
) -> _Level:
i, _, _ = self.manager._succ[abs(self.node)]
return i
@property
def var(
self
) -> (
_VariableName |
None):
i, low, _ = self.manager._succ[abs(self.node)]
if low is None:
return None
return self.manager.var_at_level(i)
@property
def low(
self
) -> '''(
_Ref |
None
)''':
_, v, _ = self.manager._succ[abs(self.node)]
if v is None:
return None
return Function(v, self.bdd)
@property
def high(
self
) -> '''(
_Ref |
None
)''':
_, _, w = self.manager._succ[abs(self.node)]
if w is None:
return None
return Function(w, self.bdd)
@property
def ref(
self
) -> _Cardinality:
return self.manager._ref[abs(self.node)]
@property
def negated(
self
) -> _Yes:
return self.node < 0
@property
def support(
self
) -> set[_VariableName]:
return self.manager.support(self.node)
================================================
FILE: dd/bdd.py
================================================
"""Ordered binary decision diagrams.
References
==========
Randal E. Bryant
"Graph-based algorithms for Boolean function manipulation"
IEEE Transactions on Computers
Volume C-35, No. 8, August, 1986, pages 677--690
Karl S. Brace, Richard L. Rudell, Randal E. Bryant
"Efficient implementation of a BDD package"
27th ACM/IEEE Design Automation Conference (DAC), 1990
pages 40--45
Richard Rudell
"Dynamic variable ordering for
ordered binary decision diagrams"
IEEE/ACM International Conference on
Computer-Aided Design (ICCAD), 1993
pages 42--47
Christel Baier and Joost-Pieter Katoen
"Principles of model checking"
MIT Press, 2008
Section 6.7, pages 381--421
Fabio Somenzi
"Binary decision diagrams"
Calculational system design, Vol.173
NATO Science Series F: Computer and systems sciences
pages 303--366, IOS Press, 1999
Henrik R. Andersen
"An introduction to binary decision diagrams"
Lecture notes for "Efficient Algorithms and Programs", 1999
The IT University of Copenhagen
"""
# Copyright 2014 by California Institute of Technology
# All rights reserved. Licensed under BSD-3.
#
import collections.abc as _abc
import functools as _ft
import inspect
import logging
import pickle
import pprint as _pp
import sys
import typing as _ty
import warnings
import dd._abc
import dd._parser as _parser
import dd._utils as _utils
logger = logging.getLogger(__name__)
REORDER_STARTS = 100
REORDER_FACTOR = 2
GROWTH_FACTOR = 2
def _request_reordering(
bdd:
'BDD'
) -> None:
"""Raise `NeedsReordering` if `len(bdd)` >= threshold."""
if bdd._last_len is None:
return
if len(bdd) >= REORDER_FACTOR * bdd._last_len:
raise _NeedsReordering()
_Ret = _ty.TypeVar('_Ret')
_CallablePR: _ty.TypeAlias = _abc.Callable[..., _Ret]
def _try_to_reorder(
func:
_CallablePR
) -> _CallablePR:
"""Decorator that serves reordering requests."""
@_ft.wraps(func)
def _wrapper(
bdd:
'BDD',
*args,
**kwargs
) -> _Ret:
with _ReorderingContext(bdd):
return func(
bdd,
*args,
**kwargs)
logger.info('Reordering needed...')
# disable reordering requests while swapping
bdd._last_len = None
reorder(bdd)
len_after = len(bdd)
# try again,
# reordering disabled to avoid livelock
with _ReorderingContext(bdd):
r = func(
bdd,
*args, **kwargs)
# enable reordering requests
bdd._last_len = GROWTH_FACTOR * len_after
return r
return _wrapper
class _ReorderingContext:
"""Context manager that tracks decorator nesting."""
def __init__(
self,
bdd:
'BDD'
) -> None:
self.bdd = bdd
self.nested = None
def __enter__(
self):
self.nested = self.bdd._reordering_context
self.bdd._reordering_context = True
def __exit__(
self,
ex_type,
ex_value,
tb):
self.bdd._reordering_context = self.nested
not_nested = (
ex_type is _NeedsReordering and
not self.nested)
if not_nested:
return True
class _NeedsReordering(Exception):
"""Raise this to request reordering."""
_Yes: _ty.TypeAlias = dd._abc.Yes
_Nat: _ty.TypeAlias = dd._abc.Nat
_Cardinality: _ty.TypeAlias = dd._abc.Cardinality
_VariableName: _ty.TypeAlias = dd._abc.VariableName
_Level: _ty.TypeAlias = dd._abc.Level
_VariableLevels: _ty.TypeAlias = dd._abc.VariableLevels
_Assignment: _ty.TypeAlias = dd._abc.Assignment
_Renaming: _ty.TypeAlias = dd._abc.Renaming
_Node: _ty.TypeAlias = _Nat
_Ref: _ty.TypeAlias = int
# ```tla
# ASSUME
# _Ref \neq 0
# ```
_Fork: _ty.TypeAlias = tuple[
_Level,
_Ref | None,
_Node | None]
_Formula: _ty.TypeAlias = dd._abc.Formula
class BDD(dd._abc.BDD[_Ref]):
"""Shared ordered binary decision diagram.
The terminal node is 1.
Nodes are positive integers,
edges signed integers.
Complemented edges are represented as
negative integers.
Values returned by methods are edges,
possibly complemented.
Attributes:
- `vars`:
`dict` mapping `variables` to `int` levels
- `roots`:
(optional) edges
- `max_nodes`:
raise `Exception` if this limit is reached.
The default value is `sys.maxsize` in Python 3.
Increase it if needed.
To ensure that the target node of a returned edge
is not garbage collected during reordering,
increment its reference counter:
`bdd.incref(edge)`
To ensure that `ite` maintains reducedness add new
nodes using `find_or_add` to keep the table updated,
or call `update_predecessors` prior to calling `ite`.
"""
# omitted docstrings are inheritted from `super()`
def __init__(
self,
levels:
_VariableLevels |
None=None
) -> None:
if levels is None:
levels = dict()
_assert_valid_ordering(levels)
self._pred: dict[
_Fork,
_Node
] = dict()
self._succ: dict[
_Node,
_Fork
] = dict()
self._ref: dict[
_Node,
_Nat
] = dict()
# all smaller positive integers
# are used as node indices, and
# no larger integers are used
# as node indices
self._min_free: _Nat = 2
# minimum number unused as BDD index
self._ite_table: dict[
tuple[_Ref, _Ref, _Ref],
_Ref
] = dict()
# `(predicate, then, else) |-> edge`
# cache for ternary conditional
# ("ite" means "if-then-else")
self.vars: _VariableLevels = dict()
self._level_to_var: dict[
_Level,
_VariableName
] = dict()
# inverse of `self.vars`
# handle no vars
self._init_terminal(len(self.vars))
# for decorator nesting
self._reordering_context: _Yes = False
# after last reordering
self._last_len: _Nat | None = None
for var, level in levels.items():
self.add_var(var, level)
# set of edges
# optional
self.roots: set = set()
self.max_nodes: _Nat = sys.maxsize
def __copy__(
self
) -> 'BDD':
bdd = BDD(self.vars)
bdd._pred = dict(self._pred)
bdd._succ = dict(self._succ)
bdd._ref = dict(self._ref)
bdd._min_free = self._min_free
bdd.roots = set(self.roots)
bdd.max_nodes = self.max_nodes
return bdd
def __del__(
self
) -> None:
"""Assert that all remaining nodes are garbage."""
if self._ref[1] > 0:
self.decref(1)
# free ref from `self._init_terminal()`
self.collect_garbage()
refs_exist = any(
v != 0
for v in self._ref.values())
if not refs_exist:
return
stack = inspect.stack()
stack_str = _pp.pformat(stack)
raise AssertionError(
'There are nodes still referenced '
'upon shutdown. Details:\n'
f'{self._ref}\n'
f'{self._succ}\n'
f'{self.vars}\n'
f'{self._ite_table}\n'
f'{type(self)}\n'
f'{stack_str}')
def __len__(
self
) -> _Cardinality:
return len(self._succ)
def __contains__(
self,
u:
_Ref
) -> _Yes:
return abs(u) in self._succ
def __iter__(
self):
return iter(self._succ)
def __str__(
self
) -> str:
return (
'Binary decision diagram:\n'
'------------------------\n'
f'var levels: {self.vars}\n'
f'roots: {self.roots}\n')
def configure(
self,
**kw
) -> dict[
str,
_ty.Any]:
"""Read and apply parameter values.
First read parameter values (returned as `dict`),
then apply `kw`. Available keyword arguments:
- `'reordering'`:
if `True` then enable, else disable
"""
d = dict(
reordering=(self._last_len is not None))
for k, v in kw.items():
if k == 'reordering':
if v:
self._last_len = max(
REORDER_STARTS, len(self))
else:
self._last_len = None
else:
raise ValueError(
f'Unknown parameter "{k}"')
return d
@property
def ordering(
self):
raise DeprecationWarning(
'use `dd.bdd.BDD.vars` '
'instead of `.ordering`')
def _init_terminal(
self,
level:
_Level
) -> None:
"""Place constant node `1`.
Used for initialization and to shift node `1` to
lower levels, as fresh variables are being added.
"""
u = 1
t = (level, None, None)
told = self._succ.setdefault(u, t)
self._pred.pop(told, None)
self._succ[u] = t
self._pred[t] = u
self._ref.setdefault(u, 1)
def succ(
self,
u:
_Ref
) -> _Fork:
"""Return `(level, low, high)` for `abs(u)`."""
return self._succ[abs(u)]
def incref(
self,
u:
_Ref
) -> None:
"""Increment reference count of node `u`."""
self._ref[abs(u)] += 1
def decref(
self,
u:
_Ref
) -> None:
"""Decrement reference count of node `u`,
with 0 as minimum value.
"""
if self._ref[abs(u)] <= 0:
n = self._ref[abs(u)]
warnings.warn(
'The method `dd.bdd.BDD.decref` was called '
f'for BDD node {u} with reference count {n}. '
'This call has no effect. Calling `decref` '
'for a node with nonpositive reference count '
'may indicate a programming error.',
UserWarning)
return
self._ref[abs(u)] -= 1
def ref(
self,
u:
_Ref
) -> _Nat:
"""Return reference count of edge `u`."""
return self._ref[abs(u)]
def declare(
self,
*variables:
_VariableName
) -> None:
for var in variables:
self.add_var(var)
def add_var(
self,
var:
_VariableName,
level:
_Level |
None=None
) -> _Level:
"""Declare a variable named `var` at `level`.
The new variable is Boolean-valued.
If `level` is absent, then add the new variable
at the bottom level.
Raise `ValueError` if:
- `var` already exists at a level
different than the given `level`, or
- the given `level` is already used by
another variable
- `level` is not given and `var` does not exist,
and the next level larger than the
current bottom level is already used by
another variable.
If `var` already exists, and either `level`
is not given, or `var` has `level`,
then return without raising exceptions.
@param var:
name of new variable to declare
@param level:
level of new variable to declare
@return:
level of variable `var`
"""
# var already exists ?
if var in self.vars:
return self._check_var(var, level)
# level already used ?
level = self._next_free_level(var, level)
# update the mappings between
# vars and levels
self.vars[var] = level
self._level_to_var[level] = var
# move the leaf node to
# the new bottom level
self._init_terminal(len(self.vars))
return level
def _check_var(
self,
var:
_VariableName,
level:
_Level |
None
) -> _Level:
"""Assert that `var` has `level`.
Return the level of `var`.
Exceptions:
- raise `ValueError` if:
- `var` is not a declared variable, or
- `level is not None` and
`level` is not the level of variable `var`
- raise `RuntimeError` if an unexpected
value of level is found in `self.vars[var]`
@param var:
name of variable
@param level:
level of variable
"""
if var not in self.vars:
raise ValueError(
f'"{var}" is not the name of '
'a declared variable')
var_level = self.vars[var]
if var_level is None or var_level < 0:
raise RuntimeError(
f'`{self.vars[var] = }` '
'(expected integer >= 0)')
if level is None or level == var_level:
return var_level
raise ValueError(
f'for variable "{var}": '
f'{level} = level != '
f'level of "{var}" = {var_level}')
def _next_free_level(
self,
var,
level:
_Level |
None
) -> _Nat:
"""Return a free level.
Raise `ValueError`:
- if the given `level` is already used by
a variable, or
- if `level is None` and the next level is
used by a variable.
If `level is None`, then return the
next level after the current largest level.
Otherwise, return the given `level`.
@param var:
name of intended new variable,
used only to form the `ValueError` message
@param level:
level of intended new variable
"""
# assume next level is unoccupied
if level is None:
level = len(self.vars)
if level < 0:
raise AssertionError(
f'`{level = } < 0')
# level already used ?
other = self._level_to_var.get(level)
if other is None:
return level
raise ValueError(
f'level {level} is already '
f'used by variable "{other}", '
'choose another level for the '
f'new variable "{var}"')
@_try_to_reorder
def var(
self,
var:
_VariableName
) -> _Ref:
if var not in self.vars:
raise ValueError(
f'undeclared variable "{var}", '
'the declared variables are:\n'
f' {self.vars}')
j = self.vars[var]
u = self.find_or_add(j, -1, 1)
return u
def var_at_level(
self,
level:
_Level
) -> _VariableName:
if level not in self._level_to_var:
raise ValueError(
f'no variable has level: {level}, '
'the current levels of all variables '
f'are: {self.vars}')
return self._level_to_var[level]
def level_of_var(
self,
var:
_VariableName
) -> _Level:
if var not in self.vars:
raise ValueError(
f'name "{var}" is not '
'a declared variable, '
'the declared variables are:'
f' {self.vars}')
return self.vars[var]
@property
def var_levels(
self
) -> _VariableLevels:
return dict(self.vars)
@_ty.overload
def _map_to_level(
self,
d:
_abc.Mapping[
_VariableName,
_ty.Any] |
_abc.Mapping[
_Level,
_ty.Any]
) -> dict[_Level, bool]:
...
@_ty.overload
def _map_to_level(
self,
d:
_abc.Set[
_VariableName] |
_abc.Set
[_Level]
) -> set[_Level]:
...
def _map_to_level(
self,
d:
_abc.Mapping[
_VariableName,
_ty.Any] |
_abc.Mapping[
_Level,
_ty.Any] |
_abc.Set[
_VariableName] |
_abc.Set[
_Level]
) -> (
dict[_Level, bool] |
set[_Level]):
"""Map keys of `d` to variable levels.
Uses `self.vars` to map the keys to levels.
If `d` is an iterable but not a mapping,
then an iterable is returned.
"""
match d:
case _abc.Mapping():
d = dict(d)
case _abc.Set():
d = set(d)
case _:
raise TypeError(d)
if not d:
match d:
case dict():
return dict()
case set():
return set()
case _:
raise TypeError(d)
# are keys variable names ?
u = next(iter(d))
if u not in self.vars:
self._assert_keys_are_levels(d)
match d:
case dict():
return {
int(k): v
for k, v in d.items()}
case set():
return set(map(int, d))
case _:
raise ValueError(d)
if isinstance(d, _abc.Mapping):
return {
self.vars[var]: bool(val)
for var, val in
d.items()}
else:
return {
self.vars[k]
for k in d}
def _assert_keys_are_levels(
self,
kv:
_abc.Iterable
) -> None:
"""Assert that `kv` values are levels.
Raise `ValueError` if not.
"""
not_levels = set()
def key_is_level(
key
) -> _Yes:
is_level = (
key in self._level_to_var)
if not is_level:
not_levels.add(key)
return is_level
keys_are_levels = all(map(
key_is_level, kv))
if keys_are_levels:
return
def fmt(key):
return (
f'key `{key}` '
'is not a level')
errors = ',\n'.join(map(
fmt, not_levels))
raise ValueError(
f'{errors},\n'
'currently the levels are:\n'
f'{self._level_to_var = }')
def _top_var(
self,
*nodes:
_Ref
) -> _Level:
def level_of(node):
level, *_ = self._succ[abs(node)]
return level
return min(map(level_of, nodes))
def copy(
self,
u:
_Ref,
other:
'BDD'
) -> _Ref:
"""Transfer BDD with root `u` to `other`."""
return copy_bdd(u, self, other)
def descendants(
self,
roots:
_abc.Iterable[_Ref]
) -> set[_Ref]:
"""Return nodes reachable from `roots`.
Nodes pointed to by references in
`roots` are included.
Nodes are represented as positive integers.
"""
abs_roots = set(map(abs, roots))
visited = set()
for u in abs_roots:
visited.add(1)
self._descendants(u, visited)
if not abs_roots.issubset(visited):
raise AssertionError(
(abs_roots, visited))
return visited
def _descendants(
self,
u:
_Ref,
visited:
set[_Node]
) -> None:
r = abs(u)
if r == 1 or r in visited:
return
_, v, w = self._succ[r]
if not v:
raise AssertionError(v)
if not w:
raise AssertionError(w)
self._descendants(v, visited)
self._descendants(w, visited)
visited.add(r)
def is_essential(
self,
u:
_Ref,
var:
_VariableName
) -> _Yes:
"""Return `True` if `var` is essential for node `u`.
If `var` is a name undeclared in
`self.vars`, return `False`.
"""
i = self.vars.get(var)
if i is None:
return False
iu, v, w = self._succ[abs(u)]
# var above node u ?
if i < iu:
return False
if i == iu:
return True
# u depends on node labeled with var ?
if not v:
raise AssertionError(v)
if not w:
raise AssertionError(w)
if self.is_essential(v, var):
return True
if self.is_essential(w, var):
return True
return False
def support(
self,
u:
_Ref,
as_levels:
_Yes=False
) -> set[
_VariableName]:
levels = set()
nodes = set()
self._support(u, levels, nodes)
if as_levels:
return levels
return {self.var_at_level(i) for i in levels}
def _support(
self,
u:
_Ref,
levels:
set[_Level],
nodes:
set[_Ref]):
"""Recurse to collect variables in support."""
# exhausted all vars ?
if len(levels) == len(self.vars):
return
# visited ?
r = abs(u)
if r in nodes:
return
nodes.add(r)
# terminal ?
if r == 1:
return
# add var
i, v, w = self._succ[r]
if not v:
raise AssertionError(v)
if not w:
raise AssertionError(w)
levels.add(i)
# recurse
self._support(v, levels, nodes)
self._support(w, levels, nodes)
def levels(
self,
skip_terminals:
_Yes=False
) -> _abc.Iterable[
tuple[
_Ref,
_Level,
_Ref,
_Node]]:
"""Return generator of tuples `(u, i, v, w)`.
Where `i` ranges from terminals to root.
@param skip_terminals:
if `True`, then omit
terminal nodes.
"""
if skip_terminals:
n = len(self.vars) - 1
else:
n = len(self.vars)
for i in range(n, -1, -1):
for u, (j, v, w) in self._succ.items():
if i != j:
continue
yield u, i, v, w
def _levels(
self
) -> dict[
_Level,
set[_Node]]:
"""Return mapping from levels to nodes."""
n = len(self.vars)
levels = {
i: set()
for var, i in
self.vars.items()}
levels[n] = set()
for u, (i, v, w) in self._succ.items():
levels[i].add(u)
levels.pop(n)
return levels
@_try_to_reorder
def reduction(
self):
"""Return copy reduced with respect to `self.vars`.
This function has educational value.
"""
# terminals
bdd = BDD(self.vars)
umap = {1: 1}
# non-terminals
levels = self.levels(
skip_terminals=True)
for u, i, v, w in levels:
if u <= 0:
raise AssertionError(u)
p, q = umap[abs(v)], umap[abs(w)]
p = _flip(p, v)
q = _flip(q, w)
r = bdd.find_or_add(i, p, q)
if r <= 0:
raise AssertionError(r)
umap[u] = r
for v in self.roots:
p = umap[abs(v)]
p = _flip(p, v)
bdd.roots.add(p)
return bdd
def undeclare_vars(
self,
*vrs
) -> set[str]:
"""Remove unused variables `vrs` from `self.vars`.
Asserts that each variable in `vrs` corresponds to
an empty level.
If `vrs` is empty, then remove all unused variables.
Garbage collection may need to be called before
calling `undeclare_vars`, in order to collect
unused nodes to obtain empty levels.
"""
for var in vrs:
if var not in self.vars:
raise ValueError(
f'name "{var}" is not '
'a declared variable. '
'The declared variables are:\n'
f'{self.vars}')
full_levels = {
i
for i, _, _ in
self._succ.values()}
# remove only unused variables
for var in vrs:
level = self.level_of_var(var)
if level in full_levels:
raise ValueError(
f'the given variable "{var}" is not '
'at an empty level (i.e., there still '
f'exist BDD nodes at level {level}, '
f'where variable "{var}" is)')
# keep unused variables not in `vrs`
if vrs:
full_levels |= {
level
for var, level in
self.vars.items()
if var not in vrs}
# map old to new levels
n = 1 + len(self.vars)
# include terminal
new_levels = [
i
for i in range(n)
if i in full_levels]
new_levels = {
i: new
for new, i in
enumerate(new_levels)}
# update variables and level declarations
rm_vars = {
var for var, level in
self.vars.items()
if level not in full_levels}
self.vars = {
var: new_levels[old]
for var, old in self.vars.items()
if old in full_levels}
self._level_to_var = {
k: var
for var, k in self.vars.items()}
# update node levels
self._succ = {
u: (new_levels[i], v, w)
for u, (i, v, w) in
self._succ.items()}
self._pred = {
v: k
for k, v in
self._succ.items()}
# clear cache
self._ite_table = dict()
return rm_vars
def let(
self,
definitions:
_Renaming |
_Assignment |
dict[
_VariableName,
_Ref],
u:
_Ref
) -> _Ref:
d = definitions
if not d:
logger.warning(
'Call to `BDD.let` with no effect: '
'`defs` is empty.')
return u
var = next(iter(definitions))
value = d[var]
if isinstance(value, bool):
return self.cofactor(u, d)
elif isinstance(value, int):
return self.compose(u, d)
try:
value + 's'
except TypeError:
raise ValueError(
'Key must be var name as `str`, '
'or Boolean value as `bool`, '
'or BDD node as `int`.')
return self.rename(u, d)
@_try_to_reorder
def compose(
self,
f:
_Ref,
var_sub:
dict[
_VariableName,
_Ref]
) -> _Ref:
"""Return substitutions `var_sub` in `f`.
@param f:
node
@param var_sub:
`dict` that maps variables to BDD nodes
"""
cache = dict()
if len(var_sub) == 1:
(var, g), = var_sub.items()
j = self.level_of_var(var)
return self._compose(
f, j, g, cache)
else:
dvars = {
self.level_of_var(var): g
for var, g in
var_sub.items()}
return self._vector_compose(
f, dvars, cache)
def _compose(
self,
f:
_Ref,
j:
_Level,
g:
_Ref,
cache:
dict[
tuple[_Ref, _Ref],
_Ref]
) -> _Ref:
# terminal ?
if abs(f) == 1:
return f
# cached ?
if (f, g) in cache:
return cache[(f, g)]
# independent of j ?
i, v, w = self._succ[abs(f)]
if not v:
raise AssertionError(v)
if not w:
raise AssertionError(w)
# below j ?
if j < i:
return f
elif i == j:
r = self.ite(g, w, v)
# complemented edge ?
if f < 0:
r = -r
else:
if i >= j:
raise AssertionError(
(i, j))
k, _, _ = self._succ[abs(g)]
z = min(i, k)
f0, f1 = self._top_cofactor(f, z)
g0, g1 = self._top_cofactor(g, z)
p = self._compose(
f0, j, g0,
cache)
q = self._compose(
f1, j, g1,
cache)
r = self.find_or_add(z, p, q)
cache[(f, g)] = r
return r
def _vector_compose(
self,
f:
_Ref,
level_sub:
dict[_Level, _Ref],
cache:
dict[_Node, _Ref]
) -> _Ref:
# terminal ?
if abs(f) == 1:
return f
# cached ?
r = cache.get(abs(f))
if r is not None:
if r == 0:
raise AssertionError(r)
# complement ?
if f < 0:
r = -r
return r
# recurse
i, v, w = self._succ[abs(f)]
if not v:
raise AssertionError(v)
if not w:
raise AssertionError(w)
p = self._vector_compose(
v, level_sub,
cache)
q = self._vector_compose(
w, level_sub,
cache)
# map this level
g = level_sub.get(i)
if g is None:
g = self.find_or_add(i, -1, 1)
r = self.ite(g, q, p)
# memoize
cache[abs(f)] = r
# complement ?
if f < 0:
r = -r
return r
@_try_to_reorder
def rename(
self,
u:
_Ref,
dvars:
_Renaming
) -> _Ref:
"""Efficient rename to non-essential neighbors.
@param dvars:
`dict` from variabe levels to variable levels
or from variable names to variable names
"""
return rename(u, self, dvars)
def _top_cofactor(
self,
u:
_Ref,
i:
_Level
) -> tuple[
_Ref,
_Ref]:
"""Return successor pair with respect to level `i`."""
# terminal node ?
if abs(u) == 1:
return (u, u)
# non-terminal node
iu, v, w = self._succ[abs(u)]
if not v:
raise AssertionError(v)
if not w:
raise AssertionError(w)
# u independent of var ?
if i < iu:
return (u, u)
if iu != i:
raise AssertionError(
'for i > iu, call cofactor instead '
f'({i = }, {iu = })')
# u labeled with var
# complement ?
if u < 0:
v, w = -v, -w
return (v, w)
@_try_to_reorder
def cofactor(
self,
u:
_Ref,
values:
_Assignment
) -> _Ref:
"""Replace variables in `u` with Booleans."""
level_values = self._map_to_level(values)
cache = dict()
ordvar = sorted(level_values)
j = 0
if abs(u) not in self:
raise ValueError(
f'node {u} not in `self`')
return self._cofactor(
u, j, ordvar, level_values, cache)
def _cofactor(
self,
u:
_Ref,
j:
_Level,
ordvar:
list[_Level],
values:
dict[_Level, bool],
cache:
dict[_Ref, _Ref]
) -> _Ref:
"""Recurse to compute cofactor."""
# terminal ?
if abs(u) == 1:
return u
# memoized ?
if u in cache:
return cache[u]
i, v, w = self._succ[abs(u)]
if not v:
raise AssertionError(v)
if not w:
raise AssertionError(w)
n = len(ordvar)
# skip nonessential variables
while j < n:
if ordvar[j] < i:
j += 1
else:
break
if j == n:
# exhausted valuation
return u
if j >= n:
raise AssertionError((j, n))
# recurse
if i in values:
val = values[i]
if bool(val):
v = w
r = self._cofactor(
v, j,
ordvar, values,
cache)
else:
p = self._cofactor(
v, j,
ordvar, values,
cache)
q = self._cofactor(
w, j,
ordvar, values,
cache)
r = self.find_or_add(i, p, q)
# complement ?
if u < 0:
r = -r
cache[u] = r
return r
@_try_to_reorder
def quantify(
self,
u:
_Ref,
qvars:
_abc.Iterable[
_VariableName],
forall:
_Yes=False
) -> _Ref:
"""Return existential or universal abstraction.
@param u:
node
@param qvars:
quantified variables
@param forall:
if `True`,
then quantify `qvars` universally,
else existentially.
"""
qvars = self._map_to_level(set(qvars))
cache = dict()
ordvar = sorted(qvars)
j = 0
return self._quantify(
u, j, ordvar,
qvars, forall,
cache)
def _quantify(
self,
u:
_Ref,
j:
_Level,
ordvar:
list[_Level],
qvars:
set[_Level],
forall:
_Yes,
cache:
dict[_Ref, _Ref]
) -> _Ref:
"""Recurse to quantify variables."""
# terminal ?
if abs(u) == 1:
return u
if u in cache:
return cache[u]
i, v, w = self._succ[abs(u)]
if not v:
raise AssertionError(v)
if not w:
raise AssertionError(w)
# complement ?
if u < 0:
v, w = -v, -w
n = len(ordvar)
# skip nonessential variables
while j < n:
if ordvar[j] < i:
j += 1
else:
break
else:
# exhausted valuation
return u
# recurse
p = self._quantify(
v, j, ordvar,
qvars, forall,
cache)
q = self._quantify(
w, j, ordvar,
qvars, forall,
cache)
if i in qvars:
if forall:
r = self.ite(p, q, -1)
# conjoin
else:
r = self.ite(p, 1, q)
# disjoin
else:
r = self.find_or_add(i, p, q)
cache[u] = r
return r
def forall(
self,
qvars:
_abc.Iterable[
_VariableName],
u:
_Ref
) -> _Ref:
return self.quantify(
u, qvars,
forall=True)
def exist(
self,
qvars:
_abc.Iterable[
_VariableName],
u:
_Ref
) -> _Ref:
return self.quantify(
u, qvars,
forall=False)
@_try_to_reorder
def ite(
self,
g:
_Ref,
u:
_Ref,
v:
_Ref
) -> _Ref:
# wrap so reordering can
# delete unused nodes
return self._ite(g, u, v)
def _ite(
self,
g:
_Ref,
u:
_Ref,
v:
_Ref
) -> _Ref:
"""Recurse to compute ternary conditional."""
# is g terminal ?
if g == 1:
return u
elif g == -1:
return v
# g is non-terminal
# already computed ?
r = (g, u, v)
w = self._ite_table.get(r)
if w is not None:
return w
z = min(self._succ[abs(g)][0],
self._succ[abs(u)][0],
self._succ[abs(v)][0])
g0, g1 = self._top_cofactor(g, z)
u0, u1 = self._top_cofactor(u, z)
v0, v1 = self._top_cofactor(v, z)
p = self._ite(g0, u0, v0)
q = self._ite(g1, u1, v1)
w = self.find_or_add(z, p, q)
# cache
self._ite_table[r] = w
return w
def find_or_add(
self,
i:
_Level,
v:
_Ref,
w:
_Ref
) -> _Ref:
"""Return reference to specified node.
The returned node is at level `i`
with successors `v, w`.
If such a node exists already,
then it is quickly found in the cached table,
and the reference returned.
@param i:
level in `range(n_vars - 1)`
@param v:
low edge
@param w:
high edge
"""
_request_reordering(self)
if i < 0:
raise ValueError(
f'The given level: {i = } < 0')
if i >= len(self.vars):
raise ValueError(
f'The given level: {i = } is not < of '
'the number of '
f'declared variables ({len(self.vars)}) '
'(the set of levels is expected to '
'comprise of contiguous numbers)')
if abs(v) not in self._succ:
raise ValueError(
f'argument: {v = } is not '
'a reference to an existing BDD node')
if abs(w) not in self._succ:
raise ValueError(
f'argument: {w = } is not '
'a reference to an existing BDD node')
# ensure canonicity of complemented edges
if w < 0:
v, w = -v, -w
r = -1
else:
r = 1
# eliminate
if v == w:
return r * v
# already exists ?
t = (i, v, w)
u = self._pred.get(t)
if u is not None:
return r * u
# find a free integer
u = self._min_free
if u <= 1:
raise AssertionError(
f'min free index is {u}, '
'which is <= 1')
if u in self._succ:
raise AssertionError(
f'node index {u} '
'is already used. '
f'{self._succ = }')
# add node
self._pred[t] = u
self._succ[u] = t
self._ref[u] = 0
self._min_free = self._next_free_int(u)
# increment reference counters
self.incref(v)
self.incref(w)
return r * u
def _next_free_int(
self,
start:
_Nat
) -> _Nat:
"""Return smallest unused integer `> start`."""
if start < 1:
raise ValueError(
f'{start} = start < 1')
for i in range(start, self.max_nodes):
if i not in self._succ:
return i
raise RuntimeError(
'full: reached `self.max_nodes` nodes '
f'({self.max_nodes = }).')
def collect_garbage(
self,
roots:
_abc.Iterable[_Ref] |
None=None
) -> None:
"""Recursively remove unused nodes
A node is unused when
its reference count is zero.
Removal starts from the nodes in `roots` with zero
reference count. If no `roots` are given, then
all nodes are scanned for zero reference counts.
"""
n = len(self)
if roots is None:
roots = self._ref
def is_unused(
u
) -> _Yes:
return not self._ref[abs(u)]
unused = filter(
is_unused, roots)
unused = set(map(
abs, unused))
# keep terminal
#
# Filtering above implies 1 is kept,
# except within `__del__()`.
# There `roots` happens to be `None`.
if 1 in unused:
unused.remove(1)
while unused:
u = unused.pop()
if u == 1:
raise AssertionError(u)
# remove
i, v, w = self._succ.pop(u)
if not v:
raise AssertionError(v)
if not w:
raise AssertionError(w)
u_ = self._pred.pop((i, v, w))
uref = self._ref.pop(u)
self._min_free = min(u, self._min_free)
if u != u_:
raise AssertionError((u, u_))
if uref:
raise AssertionError(uref)
if self._min_free <= 1:
raise AssertionError(self._min_free)
# decrement reference counters
self.decref(v)
self.decref(w)
# unused ?
if not self._ref[abs(v)] and abs(v) != 1:
unused.add(abs(v))
if not self._ref[w] and w != 1:
unused.add(w)
self._ite_table = dict()
m = len(self)
k = n - m
if k < 0:
raise AssertionError((n, m))
def update_predecessors(
self
) -> None:
"""Update table `self._pred`.
`self._pred` maps triplets
`(level, low, high)` to nodes.
"""
for u, t in self._succ.items():
if abs(u) == 1:
continue
self._pred[t] = u
def swap(
self,
x:
_VariableName |
_Level,
y:
_VariableName |
_Level,
all_levels:
dict[
_Level,
set[_Ref]] |
None=None
) -> tuple[
_Nat,
_Nat]:
"""Permute adjacent variables `x` and `y`.
Swapping invokes the garbage collector,
so be sure to `incref` nodes that should remain.
@param x, y:
variable name or level
"""
if all_levels is None:
self.collect_garbage()
all_levels = self._levels()
logger.debug(
f'swap variables "{x}" and "{y}"')
if x in self.vars:
x = self.vars[x]
if y in self.vars:
y = self.vars[y]
match x:
case int():
pass
case _:
raise ValueError(x)
match y:
case int():
pass
case _:
raise ValueError(y)
if not (0 <= x < len(self.vars)):
raise ValueError(x)
if not (0 <= y < len(self.vars)):
raise ValueError(y)
# ensure x < y
if x > y:
x, y = y, x
if x >= y:
raise ValueError(
(x, y))
if abs(x - y) != 1:
raise ValueError(
(x, y))
# count nodes
oldsize = len(self._succ)
# collect levels x and y
levels: dict[
_Ref,
dict[_Ref, tuple]
] = {
x: dict(),
y: dict()}
for j in (x, y):
for u in all_levels[j]:
i, v, w = self._succ[abs(u)]
if i != j:
raise AssertionError(
(i, x, y))
u_ = self._pred.pop(
(i, v, w))
if u != u_:
raise AssertionError(
(u, u_))
levels[j][u] = (v, w)
# move level y up
for u, (v, w) in levels[y].items():
i, _, _ = self._succ[u]
if i != y:
raise AssertionError((i, y))
r = (x, v, w)
self._succ[u] = r
if r in self._pred:
raise AssertionError(r)
self._pred[r] = u
# move level x down
# first x nodes independent of y
done = set()
for u, (v, w) in levels[x].items():
i, _, _ = self._succ[u]
if i != x:
raise AssertionError((i, x))
if not v:
raise AssertionError(v)
if not w:
raise AssertionError(w)
iv, v0, v1 = self._low_high(v)
iw, w0, w1 = self._low_high(w)
# dependeds on y ?
if iv <= y or iw <= y:
continue
# independent of y
r = (y, v, w)
self._succ[u] = r
if r in self._pred:
raise AssertionError(r)
self._pred[r] = u
done.add(u)
# x nodes dependent on y
garbage = set()
xfresh = set()
for u, (v, w) in levels[x].items():
# for type checking
match u:
case int():
pass
case _:
raise AssertionError(u)
if u in done:
continue
i, _, _ = self._succ[u]
if i != x:
raise AssertionError((i, x))
if not v:
raise AssertionError(v)
if not w:
raise AssertionError(w)
self.decref(v)
self.decref(w)
# possibly unused
garbage.add(abs(v))
garbage.add(w)
# calling cofactor can fail
# because y moved
iv, v0, v1 = self._swap_cofactor(v, y)
iw, w0, w1 = self._swap_cofactor(w, y)
# x node depends on y
if not (y <= iv and y <= iw):
raise AssertionError(
(iv, iw, y))
if not (y == iv or y == iw):
raise AssertionError(
(iv, iw, y))
# complemented edge ?
if v < 0 and y == iv:
v0, v1 = -v0, -v1
p = self.find_or_add(
y, v0, w0)
q = self.find_or_add(
y, v1, w1)
if q < 0:
raise AssertionError(q)
if p == q:
raise AssertionError(
'No elimination: '
'node depends on both x and y')
if self._succ[abs(p)][0] == y:
xfresh.add(abs(p))
if self._succ[q][0] == y:
xfresh.add(q)
r = (x, p, q)
self._succ[u] = r
if r in self._pred:
raise AssertionError(
(u, r, levels, self._pred))
self._pred[r] = u
self.incref(p)
self.incref(q)
# garbage collection could be interleaved
# but only if there is
# substantial loss of efficiency
# swap x and y in `vars`
vx = self.var_at_level(x)
self.vars[vx] = y
vy = self.var_at_level(y)
self.vars[vy] = x
# reset
self._level_to_var[y] = vx
self._level_to_var[x] = vy
self._ite_table = dict()
# count nodes
self.collect_garbage(garbage)
newsize = len(self._succ)
# new levels
newx = set()
newy = set()
for u in levels[x]:
if u not in self._succ:
continue
i, _, _ = self._succ[u]
if i == x:
newy.add(u)
elif i == y:
newx.add(u)
else:
raise AssertionError(
(u, i, x, y))
for u in xfresh:
i, _, _ = self._succ[u]
if i != y:
raise AssertionError(
(u, i, x, y))
newx.add(u)
for u in levels[y]:
if u not in self._succ:
continue
i, _, _ = self._succ[u]
if i != x:
raise AssertionError(
(u, i, x, y))
newy.add(u)
all_levels[x] = newy
all_levels[y] = newx
return (
oldsize,
newsize)
def _low_high(
self,
u:
_Ref
) -> tuple[
_Level,
_Ref,
_Node]:
"""Return level, low, and high.
If node `u` is a leaf,
then `u` is returned as low and high.
This method is similar to the
method `succ`, but different.
@return:
(level, low, high)
"""
i, v, w = self._succ[abs(u)]
if abs(u) == 1:
return i, u, u
if not v:
raise AssertionError(v)
if not w:
raise AssertionError(w)
return i, v, w
def _swap_cofactor(
self,
u:
_Ref,
y:
_Level
) -> tuple[
_Level,
_Ref,
_Ref]:
"""Return cofactor of node `u` wrt level `y`.
If node `u` is above level `y`, that means
it was at level `y` when the swap started.
To account for this,
`y` is returned as the node level.
"""
i, v, w = self._succ[abs(u)]
if y < i:
return (i, u, u)
# restore index of y node that
# moved up
if not v:
raise AssertionError(v)
if not w:
raise AssertionError(w)
return (y, v, w)
def count(
self,
u:
_Ref,
nvars:
_Nat |
None=None
) -> _Nat:
n = nvars
if abs(u) not in self:
raise ValueError(u)
# index those levels in
# support separately
levels = {
self.level_of_var(var)
for var in self.support(u)}
k = len(levels)
if n is None:
n = k
slack = n - k
if slack < 0:
raise ValueError(slack)
map_level = dict()
for new, old in enumerate(sorted(levels)):
map_level[old] = new + slack
old, _, _ = self._succ[1]
map_level[old] = n
map_level['all'] = n
r = self._sat_len(
u, map_level,
d=dict())
i, _, _ = self._succ[abs(u)]
i = map_level[i]
n_models = r * 2**i
return self._assert_int(n_models)
@staticmethod
def _assert_int(
number:
_ty.Any
) -> int:
"""Return `number` if an `int`.
Raise `AssertionError` otherwise.
"""
match number:
case int():
return number
raise AssertionError(
'Expected `int` result, '
f'but: {number = }')
def _sat_len(
self,
u:
_Ref,
map_level:
dict[
_Level |
_ty.Literal['all'],
_Level],
d:
dict[
_Node,
_Nat]
) -> _Nat:
"""Recurse to compute the number of models."""
# terminal ?
if u == 1:
return 1
if u == -1:
return 0
i, v, w = self._succ[abs(u)]
if not v:
raise AssertionError(v)
if not w:
raise AssertionError(w)
i = map_level[i]
# memoized ?
if abs(u) in d:
n = d[abs(u)]
# complement ?
if u < 0:
n = 2**(map_level['all'] - i) - n
return self._assert_int(n)
# non-terminal
nv = self._sat_len(v, map_level, d)
nw = self._sat_len(w, map_level, d)
iv, _, _ = self._succ[abs(v)]
iw, _, _ = self._succ[w]
iv = map_level[iv]
iw = map_level[iw]
# sum
n = self._assert_int(
nv * 2**(iv - i - 1) +
nw * 2**(iw - i - 1))
d[abs(u)] = n
# complement ?
if u < 0:
n = 2**(map_level['all'] - i) - n
return self._assert_int(n)
def pick_iter(
self,
u:
_Ref,
care_vars:
set[_VariableName] |
None=None
) -> _abc.Iterable[
_Assignment]:
# empty ?
if not self._succ:
return
# non-empty
if abs(u) not in self._succ:
raise ValueError(
f'{u} is not a reference to '
'a BDD node in the BDD manager '
f'`self` ({self!r})')
support = self.support(u)
if care_vars is None:
care_vars = support
missing = {
v
for v in support
if v not in care_vars}
if missing:
logger.warning(
'Missing bits: '
f'support - care_vars = {missing}')
cube = dict()
value = True
cubes = self._sat_iter(
u, cube, value)
for cube in cubes:
minterms = _enumerate_minterms(
cube, care_vars)
for m in minterms:
yield m
def _sat_iter(
self,
u:
_Ref,
cube:
dict[
_Level,
bool],
value:
bool
) -> _abc.Iterable[
_Assignment]:
"""Recurse to enumerate models."""
if u < 0:
value = not value
# terminal ?
if abs(u) == 1:
if value:
cube = {
self._level_to_var[i]: v
for i, v in cube.items()}
yield cube
return
# non-terminal
i, v, w = self._succ[abs(u)]
if not v:
raise AssertionError(v)
if not w:
raise AssertionError(w)
d0 = dict(cube)
d0[i] = False
d1 = dict(cube)
d1[i] = True
for x in self._sat_iter(v, d0, value):
yield x
for x in self._sat_iter(w, d1, value):
yield x
def assert_consistent(
self
) -> None:
"""Raise `AssertionError` if not a valid BDD."""
for root in self.roots:
if abs(root) not in self._succ:
raise AssertionError(root)
# inverses
succ_keys = set(self._succ)
succ_values = set(self._succ.values())
pred_keys = set(self._pred)
pred_values = set(self._pred.values())
if succ_keys != pred_values:
raise AssertionError(
succ_keys.symmetric_difference(
pred_values))
if pred_keys != succ_values:
raise AssertionError(
pred_keys.symmetric_difference(
succ_values))
# uniqueness
n = len(succ_keys)
n_ = len(succ_values)
if n != n_:
raise AssertionError(n - n_)
for u, (i, v, w) in self._succ.items():
if not isinstance(i, int):
raise TypeError(i)
# terminal ?
if v is None:
if w is not None:
raise AssertionError(w)
continue
else:
if abs(v) not in self._succ:
raise AssertionError(v)
if w is None:
if v is not None:
raise AssertionError(v)
continue
else:
# "high" is regular edge
if w < 0:
raise AssertionError(w)
if w not in self._succ:
raise AssertionError(w)
# var order should increase
for x in (v, w):
ix, _, _ = self._succ[abs(x)]
if not (i < ix):
raise AssertionError((u, i))
# `_pred` contains inverse of `_succ`
if (i, v, w) not in self._pred:
raise AssertionError((i, v, w))
if self._pred[(i, v, w)] != u:
raise AssertionError(u)
# reference count
if u not in self._ref:
raise AssertionError(u)
if self._ref[u] < 0:
raise AssertionError(self._ref[u])
@_try_to_reorder
def add_expr(
self,
expr:
_Formula
) -> _Ref:
return _parser.add_expr(expr, self)
def to_expr(
self,
u:
_Ref
) -> _Formula:
if u not in self:
raise ValueError(
f'{u} is not a reference to '
'a BDD node in the BDD manager '
f'`self` ({self!r})')
cache = dict()
return self._to_expr(u, cache)
def _to_expr(
self,
u:
_Ref,
cache:
dict[int, str]
) -> _Formula:
if u == 1:
return 'TRUE'
if u == -1:
return 'FALSE'
if u in cache:
return cache[u]
level, v, w = self._succ[abs(u)]
if not v:
raise AssertionError(v)
if not w:
raise AssertionError(w)
var = self._level_to_var[level]
p = self._to_expr(v, cache)
q = self._to_expr(w, cache)
# pure var ?
if p == 'FALSE' and q == 'TRUE':
expr = var
else:
expr = f'ite({var}, {q}, {p})'
# complemented ?
if u < 0:
expr = f'(~ {expr})'
cache[u] = expr
return expr
def apply(
self,
op:
dd._abc.OperatorSymbol,
u:
_Ref,
v:
_Ref |
None=None,
w:
_Ref |
None=None
) -> _Ref:
_utils.assert_operator_arity(op, v, w, 'bdd')
if abs(u) not in self:
raise ValueError(u)
if v is not None and abs(v) not in self:
raise ValueError(v)
if w is not None and abs(w) not in self:
raise ValueError(w)
# unary
if op in ('~', 'not', '!'):
return -u
# Implied by `assert_operator_arity()` above,
# present here for type-checking.
elif v is None:
raise ValueError(
'`v is None`')
# binary
elif op in ('or', r'\/', '|', '||'):
return self.ite(u, 1, v)
elif op in ('and', '/\\', '&', '&&'):
return self.ite(u, v, -1)
elif op in ('#', 'xor', '^'):
return self.ite(u, -v, v)
elif op in ('=>', '->', 'implies'):
return self.ite(u, v, 1)
elif op in ('<=>', '<->', 'equiv'):
return self.ite(u, v, -v)
elif op in ('diff', '-'):
return self.ite(u, -v, -1)
elif op in (r'\A', 'forall'):
qvars = self.support(u)
return self.quantify(
v, qvars,
forall=True)
elif op in (r'\E', 'exists'):
qvars = self.support(u)
return self.quantify(
v, qvars,
forall=False)
# Implied by `assert_operator_arity()` above,
# present here for type-checking.
elif w is None:
raise ValueError(
'`w is None`')
# ternary
elif op == 'ite':
return self.ite(u, v, w)
raise ValueError(
f'unknown operator "{op}"')
def _add_int(
self,
i:
int
) -> _Ref:
if i not in self:
raise ValueError(
f'{i = } is not a reference '
'to a BDD node in the BDD manager '
f'`self` ({self!r})')
return i
@_try_to_reorder
def cube(
self,
dvars:
_Assignment |
_abc.Iterable[
_VariableName]
) -> _Ref:
if not isinstance(dvars, dict):
dvars = {
k: True
for k in dvars}
# `dvars` keys can be var names or levels
r = self.true
for var, val in dvars.items():
u = self.var(var)
u = u if val else -u
r = self.apply('and', u, r)
return r
def dump(
self,
filename:
str,
roots:
dict[str, _Ref] |
list[_Ref] |
None=None,
filetype:
dd._abc.ImageFileType |
dd._abc.PickleFileType |
None=None,
**kw
) -> None:
if filetype is None:
name = filename.lower()
if name.endswith('.pdf'):
filetype = 'pdf'
elif name.endswith('.png'):
filetype = 'png'
elif name.endswith('.svg'):
filetype = 'svg'
elif name.endswith('.dot'):
filetype = 'dot'
elif name.endswith('.p'):
filetype = 'pickle'
else:
raise ValueError(
'cannot infer file type '
'from extension of file '
f'name "{filename}"')
if filetype in _utils.DOT_FILE_TYPES:
self._dump_figure(
roots, filename,
filetype, **kw)
elif filetype == 'pickle':
self._dump_bdd(roots, filename, **kw)
else:
raise ValueError(
f'unknown file type "{filetype}"')
def _dump_figure(
self,
roots:
_abc.Iterable[_Ref] |
None,
filename:
str,
filetype:
dd._abc.ImageFileType,
**kw
) -> None:
"""Write BDDs to `filename` as figure."""
g = _to_dot(roots, self)
g.dump(filename, filetype=filetype, **kw)
def _dump_bdd(
self,
roots:
dict[str, _Ref] |
list[_Ref] |
None,
filename:
str,
**kw
) -> None:
"""Write BDDs to `filename` as pickle."""
if roots is None:
nodes = self._succ
roots = list()
else:
values = _utils.values_of(roots)
nodes = self.descendants(values)
succ = (
(k, self._succ[k])
for k in nodes)
d = dict(
vars=self.vars,
succ=dict(succ),
roots=roots)
kw.setdefault('protocol', 2)
with open(filename, 'wb') as f:
pickle.dump(d, f, **kw)
def load(
self,
filename:
str,
levels:
_Yes=True
) -> (
dict[str, _Ref] |
list[_Ref]):
name = filename.lower()
if not name.endswith('.p'):
raise ValueError(
f'Unknown file type of "{filename}"')
umap, roots = self._load_pickle(
filename, levels=levels)
def map_node(u):
v = umap[abs(u)]
if u < 0:
return - v
else:
return v
return _utils.map_container(
map_node, roots)
def _load_pickle(
self,
filename:
str,
levels:
_Yes=True
) -> tuple[
dict,
dict[str, _Ref] |
list[_Ref]]:
with open(filename, 'rb') as f:
d = pickle.load(f)
var2level = d['vars']
succ = d['succ']
n = len(var2level)
level_map = dict()
# level_map[n] = len(self.vars)
for var, i in var2level.items():
if not (0 <= i < n):
raise AssertionError((i, n))
if var not in self.vars:
logger.warning(
f'variable "{var}" added')
if levels:
j = self.add_var(var, i)
else:
j = self.add_var(var)
level_map[i] = j
umap = dict()
for u in succ:
# already added ?
if u in umap:
continue
# add
self._load(
u, succ, umap, level_map)
return umap, d['roots']
def _load(
self,
u:
_Ref,
succ:
dict,
umap:
dict,
level_map:
dict
) -> _Ref:
"""Recurse to load BDD `u` from `succ`."""
# terminal ?
if abs(u) == 1:
return u
# memoized ?
if u in umap:
r = umap[abs(u)]
if r <= 0:
raise AssertionError(r)
if u < 0:
r = -r
return r
i, v, w = succ[abs(u)]
j = level_map[i]
p = self._load(
v, succ, umap, level_map)
q = self._load(
w, succ, umap, level_map)
r = self.find_or_add(j, p, q)
if r <= 0:
raise AssertionError(r)
umap[abs(u)] = r
if u < 0:
r = -r
return r
def _dump_manager(
self,
filename:
str,
**kw
) -> None:
"""Write `BDD` to `filename` as pickle."""
d = dict(
vars=self.vars,
max_nodes=self.max_nodes,
roots=self.roots,
pred=self._pred,
succ=self._succ,
ref=self._ref,
min_free=self._min_free)
kw.setdefault('protocol', 2)
with open(filename, 'wb') as f:
pickle.dump(d, f, **kw)
@classmethod
def _load_manager(
cls,
filename:
str
) -> 'BDD':
"""Load `BDD` from pickle file `filename`."""
with open(filename, 'rb') as f:
d = pickle.load(f)
bdd = cls(d['vars'])
bdd.max_nodes = d['max_nodes']
bdd.roots = d['roots']
bdd._pred = d['pred']
bdd._succ = d['succ']
bdd._ref = d['ref']
bdd._min_free = d['min_free']
return bdd
@property
def false(
self
) -> _Ref:
return -1
@property
def true(
self
) -> _Ref:
return 1
def _enumerate_minterms(
cube:
_Assignment,
bits:
_abc.Iterable[
_VariableName]
) -> _abc.Iterator[
_Assignment]:
"""Generator of complete assignments in `cube`.
@param bits:
enumerate over those absent from `cube`
"""
if cube is None:
raise ValueError(cube)
if bits is None:
raise ValueError(bits)
bits = set(bits).difference(cube)
# fix order
bits = list(bits)
n = len(bits)
for i in range(2**n):
values = bin(i).lstrip('-0b').zfill(n)
model = {
k: bool(int(v))
for k, v in
zip(bits, values)}
model.update(cube)
if len(model) < len(bits):
raise AssertionError((model, bits))
if len(model) < len(cube):
raise AssertionError((model, cube))
yield model
def _assert_isomorphic_orders(
old:
_VariableLevels,
new:
_VariableLevels,
support:
set[_VariableName]
) -> None:
"""Raise `AssertionError` if not isomorphic.
@param old, new:
levels
@param support:
`old` and `new` compared after
restriction to `support`.
"""
_assert_valid_ordering(old)
_assert_valid_ordering(new)
s = {
k: v
for k, v in
old.items()
if k in support}
t = {
k: v
for k, v in
new.items()
if k in support}
old = sorted(s, key=s.get)
new = sorted(t, key=t.get)
if old != new:
raise AssertionError((old, new))
def _assert_valid_ordering(
levels:
_VariableLevels
) -> None:
"""Check that `levels` is well-formed.
- bijection
- contiguous levels
"""
# `levels` is a mapping from
# each variable to a single level
if not isinstance(levels, _abc.Mapping):
raise TypeError(levels)
# levels are contiguous integers ?
n = len(levels)
numbers = set(levels.values())
numbers_ = set(range(n))
if numbers != numbers_:
raise AssertionError(n, numbers)
def rename(
u:
_Ref,
bdd:
BDD,
dvars:
_Renaming
) -> _Ref:
"""Rename variables of node `u`.
@param dvars:
`dict` from variabe names to variable names
"""
if abs(u) not in bdd:
raise ValueError(
f'{u} (given as `u`) is not a reference to '
'a BDD node in the given BDD manager '
f'`bdd` ({bdd!r})')
# nothing to rename ?
if not dvars:
return u
# map variable names to levels
levels = bdd.vars
dvars = {
levels[var]: levels[dvars.get(var, var)]
for var in bdd.vars}
cache = dict()
return _copy_bdd(u, dvars, bdd, bdd, cache)
def _assert_valid_rename(
u:
_Ref,
bdd:
BDD,
dvars:
dict[
_Level,
_Level]
) -> None:
"""Assert renaming to only adjacent variables.
Raise `AssertionError` if
renaming to non-adjacent variables.
@param dvars:
`dict` that maps var levels to var levels
"""
if not dvars:
return
# valid levels ?
bdd.var_at_level(0)
# pairwise disjoint ?
_assert_no_overlap(dvars)
def _all_adjacent(
dvars:
dict,
bdd:
BDD
) -> _Yes:
"""Return `True` if all levels are adjacent.
The pairs of levels checked for
being adjacent are the key-value pairs
of the mapping `dvars`.
"""
for v, vp in dvars.items():
if not _adjacent(v, vp, bdd):
return False
return True
def _adjacent(
i:
_Level,
j:
_Level,
bdd:
BDD
) -> _Yes:
"""Warn if levels `i` and `j` not adjacent."""
if abs(i - j) == 1:
return True
logger.warning((
'level {i} ("{x}") not adjacent to '
'level {j} ("{y}")').format(
i=i,
j=j,
x=bdd.var_at_level(i),
y=bdd.var_at_level(j)))
return False
def _assert_no_overlap(
d:
dict
) -> None:
"""Raise `AssertionError` if keys and values overlap."""
if any((k in d) for k in d.values()):
raise AssertionError(
f'keys and values overlap: {d}')
def image(
trans:
_Ref,
source:
_Ref,
rename:
_Renaming |
dict[_Level, _Level],
qvars:
_abc.Iterable[_VariableName] |
_abc.Iterable[_Level],
bdd:
BDD,
forall:
_Yes=False
) -> _Ref:
"""Return set reachable from `source` under `trans`.
@param trans:
transition relation
@param source:
the transition must start in this set
@param rename:
maps primed variables in
`trans` to unprimed variables in `trans`.
Applied to the quantified conjunction of
`trans` and `source`.
@param qvars:
variables to quantify
@param forall:
if `True`,
then quantify `qvars` universally,
else existentially.
"""
# map to levels
qvars = bdd._map_to_level(set(qvars))
rename = {
bdd.vars.get(k, k): bdd.vars.get(v, v)
for k, v in rename.items()}
# init
cache = dict()
rename_u = rename
rename_v = None
# no overlap and neighbors
_assert_no_overlap(rename)
if not _all_adjacent(rename, bdd):
logger.warning(
'BDD.image: not all vars adjacent')
# unpriming maps to qvars or
# outside support of conjunction
s = bdd.support(trans, as_levels=True)
s.update(bdd.support(source, as_levels=True))
s.difference_update(qvars)
s.intersection_update(rename.values())
if s:
raise AssertionError(s)
return _image(
trans, source, rename_u, rename_v,
qvars, bdd, forall, cache)
def preimage(
trans:
_Ref,
target:
_Ref,
rename:
_Renaming |
dict[_Level, _Level],
qvars:
_abc.Iterable[_VariableName] |
_abc.Iterable[_Level],
bdd:
BDD,
forall:
_Yes=False
) -> _Ref:
"""Return set that can reach `target` under `trans`.
Also known as the "relational product".
Assumes that primed and
unprimed variables are neighbors.
Variables are identified by their levels.
@param trans:
transition relation
@param target:
the transition must end in this set
@param rename:
maps (unprimed) variables in `target` to
(primed) variables in `trans`
@param qvars:
variables to quantify
@param forall:
if `True`,
then quantify `qvars` universally,
else existentially.
"""
# map to levels
qvars = bdd._map_to_level(set(qvars))
rename = {
bdd.vars.get(k, k): bdd.vars.get(v, v)
for k, v in rename.items()}
# init
cache = dict()
rename_u = None
rename_v = rename
# check
_assert_valid_rename(target, bdd, rename)
return _image(
trans, target, rename_u, rename_v,
qvars, bdd, forall, cache)
def _image(
u:
_Ref,
v:
_Ref,
umap:
dict |
None,
vmap:
dict |
None,
qvars:
set[_Level],
bdd:
BDD,
forall:
_Yes,
cache:
dict[
tuple[_Ref, _Ref],
_Ref]
) -> _Ref:
"""Recursive (pre)image computation.
Renaming requires that in each pair
the variables are adjacent.
@param umap:
renaming of variables in `u`
that occurs after conjunction of `u` with `v`
and quantification.
@param vmap:
renaming of variables in `v`
that occurs before conjunction with `u`.
"""
# controlling values for conjunction ?
if u == -1 or v == -1:
return -1
if u == 1 and v == 1:
return 1
# already computed ?
t = (u, v)
w = cache.get(t)
if w is not None:
return w
# recurse (descend)
iu, _, _ = bdd._succ[abs(u)]
jv, _, _ = bdd._succ[abs(v)]
if vmap is None:
iv = jv
else:
iv = vmap.get(jv, jv)
z = min(iu, iv)
u0, u1 = bdd._top_cofactor(u, z)
v0, v1 = bdd._top_cofactor(v, jv + z - iv)
p = _image(
u0, v0, umap, vmap, qvars,
bdd, forall, cache)
q = _image(
u1, v1, umap, vmap, qvars,
bdd, forall, cache)
# quantified ?
if z in qvars:
if forall:
r = bdd.ite(p, q, -1)
# conjoin
else:
r = bdd.ite(p, 1, q)
# disjoin
else:
if umap is None:
m = z
else:
m = umap.get(z, z)
g = bdd.find_or_add(m, -1, 1)
r = bdd.ite(g, q, p)
cache[t] = r
return r
def reorder(
bdd:
BDD,
order:
_VariableLevels |
None=None
) -> None:
"""Apply Rudell's sifting algorithm to reduce `bdd` size.
Reordering invokes the garbage collector,
so be sure to `incref` nodes that should remain.
@param order:
if given, then swap vars to obtain this order.
The dictionary `order` maps each
variable name to a level.
"""
len_before = len(bdd)
if order is None:
_apply_sifting(bdd)
else:
_sort_to_order(bdd, order)
len_after = len(bdd)
logger.info(
'Reordering changed `BDD` manager size '
f'from {len_before} to {len_after} nodes.')
def _apply_sifting(
bdd:
BDD
) -> None:
"""Apply Rudell's sifting algorithm."""
bdd.collect_garbage()
n = len(bdd)
# using `set` injects some randomness
levels = bdd._levels()
names = set(bdd.vars)
for var in names:
k = _reorder_var(bdd, var, levels)
m = len(bdd)
logger.info(
f'{m} nodes for variable '
f'"{var}" at level {k}')
if m > n:
raise AssertionError(
f'expected: m <= n, but {m = } > {n = }')
logger.info(f'final variable order:\n{bdd.vars}')
def _reorder_var(
bdd:
BDD,
var:
_VariableName,
levels:
dict[
_Level,
set[_Ref]]
) -> _Nat:
"""Reorder by sifting a variable `var`."""
if var not in bdd.vars:
raise ValueError((var, bdd.vars))
m = len(bdd)
n = len(bdd.vars) - 1
if n < 0:
raise AssertionError(n)
start = 0
end = n
level = bdd.level_of_var(var)
# closer to bottom ?
if (2 * level) >= n:
start, end = end, start
_shift(bdd, level, start, levels)
sizes = _shift(bdd, start, end, levels)
k = min(sizes, key=sizes.get)
_shift(bdd, end, k, levels)
m_ = len(bdd)
if sizes[k] != m_:
raise AssertionError((sizes[k], m_))
if m_ > m:
raise AssertionError((m_, m))
return k
def _shift(
bdd:
BDD,
start:
_Level,
end:
_Level,
levels:
dict[
_Level,
set[_Ref]]
) -> dict[
_Level,
_Level]:
r"""Shift level `start` to become `end`, by swapping.
```tla
ASSUMPTION
LET
n_vars == len(bdd.vars)
level_range == 0..(n_vars - 1)
IN
/\ start \in level_range
/\ end \in level_range
```
"""
m = len(bdd.vars)
if not (0 <= start < m):
raise AssertionError((start, m))
if not (0 <= end < m):
raise AssertionError((end, m))
sizes = dict()
d = 1 if start < end else -1
for i in range(start, end, d):
j = i + d
oldn, n = bdd.swap(i, j, levels)
sizes[i] = oldn
sizes[j] = n
return sizes
def _sort_to_order(
bdd:
BDD,
order:
_VariableLevels
) -> None:
"""Swap variables to obtain `order`."""
# TODO: use min number of swaps
if len(bdd.vars) != len(order):
raise ValueError(
'The number of BDD variables: '
f'{len(bdd.vars) = } is not equal to: '
f'{len(order) = }')
m = 0
levels = bdd._levels()
n = len(order)
for k in range(n):
for i in range(n - 1):
for root in bdd.roots:
if root not in bdd:
raise ValueError(
f'{root} in `bdd.roots` is not '
'a reference to a BDD node in '
'the given BDD manager `bdd` '
f'({bdd!r})')
x = bdd.var_at_level(i)
y = bdd.var_at_level(i + 1)
p = order[x]
q = order[y]
if p > q:
bdd.swap(i, i + 1, levels)
m += 1
logger.debug(
f'swap: {p} with {q}, {i}')
if logger.getEffectiveLevel() < logging.DEBUG:
bdd.assert_consistent()
logger.info(f'total swaps: {m}')
def reorder_to_pairs(
bdd:
BDD,
pairs:
_Renaming
) -> None:
"""Reorder variables to make adjacent the given pairs.
@param pairs:
has variable names as keys and values
"""
m = 0
levels = bdd._levels()
for x, y in pairs.items():
jx = bdd.level_of_var(x)
jy = bdd.level_of_var(y)
k = abs(jx - jy)
if k <= 0:
raise AssertionError((jx, jy))
# already adjacent ?
if k == 1:
continue
# shift x next to y
if jx > jy:
jx, jy = jy, jx
_shift(bdd, start=jx, en
gitextract__x1aidr9/
├── .gitattributes
├── .github/
│ └── workflows/
│ ├── main.yml
│ └── setup_build_env.sh
├── .gitignore
├── AUTHORS
├── CHANGES.md
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── dd/
│ ├── __init__.py
│ ├── _abc.py
│ ├── _copy.py
│ ├── _parser.py
│ ├── _utils.py
│ ├── autoref.py
│ ├── bdd.py
│ ├── buddy.pyx
│ ├── buddy_.pxd
│ ├── c_sylvan.pxd
│ ├── cudd.pyx
│ ├── cudd_zdd.pyx
│ ├── dddmp.py
│ ├── mdd.py
│ ├── py.typed
│ └── sylvan.pyx
├── doc.md
├── download.py
├── examples/
│ ├── README.md
│ ├── _test_examples.py
│ ├── bdd_traversal.py
│ ├── boolean_satisfiability.py
│ ├── cudd_configure_reordering.py
│ ├── cudd_memory_limits.py
│ ├── cudd_statistics.py
│ ├── cudd_zdd.py
│ ├── good_vs_bad_variable_order.py
│ ├── install_dd_buddy.sh
│ ├── install_dd_cudd.sh
│ ├── install_dd_sylvan.sh
│ ├── json_example.py
│ ├── json_load.py
│ ├── np.py
│ ├── queens.py
│ ├── reachability.py
│ ├── reordering.py
│ ├── transfer_bdd.py
│ ├── variable_substitution.py
│ └── what_is_a_bdd.py
├── setup.py
└── tests/
├── .coveragerc
├── README.md
├── autoref_test.py
├── bdd_test.py
├── common.py
├── common_bdd.py
├── common_cudd.py
├── copy_test.py
├── cudd_test.py
├── cudd_zdd_test.py
├── dddmp_test.py
├── inspect_cython_signatures.py
├── iterative_recursive_flattener.py
├── mdd_test.py
├── parser_test.py
├── pytest.ini
├── regressions_test.py
└── sylvan_test.py
SYMBOL INDEX (795 symbols across 41 files)
FILE: dd/_abc.py
function _literals_of (line 18) | def _literals_of(
function _literals_of_recurse (line 30) | def _literals_of_recurse(
class BDD (line 147) | class BDD(_ty.Protocol[Ref]):
method __init__ (line 152) | def __init__(
method __eq__ (line 160) | def __eq__(
method __len__ (line 166) | def __len__(
method __contains__ (line 171) | def __contains__(
method __str__ (line 178) | def __str__(
method configure (line 183) | def configure(
method statistics (line 191) | def statistics(
method succ (line 200) | def succ(
method declare (line 211) | def declare(
method var (line 223) | def var(
method var_at_level (line 230) | def var_at_level(
method level_of_var (line 237) | def level_of_var(
method var_levels (line 247) | def var_levels(
method copy (line 252) | def copy(
method support (line 261) | def support(
method let (line 277) | def let(
method forall (line 292) | def forall(
method exist (line 302) | def exist(
method count (line 312) | def count(
method pick (line 331) | def pick(
method pick_iter (line 376) | def pick_iter(
method add_expr (line 404) | def add_expr(
method to_expr (line 415) | def to_expr(
method ite (line 422) | def ite(
method apply (line 441) | def apply(
method _add_int (line 456) | def _add_int(
method cube (line 463) | def cube(
method dump (line 471) | def dump(
method load (line 511) | def load(
method false (line 537) | def false(
method true (line 543) | def true(
function reorder (line 549) | def reorder(
class Operator (line 564) | class Operator(_ty.Protocol):
method __init__ (line 567) | def __init__(
method __hash__ (line 576) | def __hash__(
method to_expr (line 581) | def to_expr(
method __int__ (line 586) | def __int__(
method __str__ (line 594) | def __str__(
method __len__ (line 609) | def __len__(
method __del__ (line 614) | def __del__(
method __eq__ (line 619) | def __eq__(
method __ne__ (line 628) | def __ne__(
method __lt__ (line 637) | def __lt__(
method __le__ (line 643) | def __le__(
method __invert__ (line 649) | def __invert__(
method __and__ (line 654) | def __and__(
method __or__ (line 660) | def __or__(
method __xor__ (line 666) | def __xor__(
method implies (line 672) | def implies(
method equiv (line 678) | def equiv(
method level (line 708) | def level(
method var (line 714) | def var(
method low (line 722) | def low(
method high (line 731) | def high(
method ref (line 740) | def ref(
method negated (line 746) | def negated(
method support (line 752) | def support(
method let (line 758) | def let(
method exist (line 768) | def exist(
method forall (line 775) | def forall(
method pick (line 782) | def pick(
method count (line 792) | def count(
FILE: dd/_copy.py
class _BDD (line 21) | class _BDD(
method add_var (line 26) | def add_var(
method _top_cofactor (line 36) | def _top_cofactor(
method reorder (line 47) | def reorder(
method find_or_add (line 57) | def find_or_add(
method incref (line 68) | def incref(
method decref (line 75) | def decref(
method assert_consistent (line 83) | def assert_consistent(
class _Ref (line 92) | class _Ref(_ty.Protocol):
method __int__ (line 101) | def __int__(
method __invert__ (line 106) | def __invert__(
class _Shelf (line 112) | class _Shelf(
method __setitem__ (line 121) | def __setitem__(
method __getitem__ (line 128) | def __getitem__(
method __iter__ (line 133) | def __iter__(
method __contains__ (line 138) | def __contains__(
function _open_shelf (line 145) | def _open_shelf(
function copy_vars (line 153) | def copy_vars(
function copy_bdds_from (line 164) | def copy_bdds_from(
function copy_bdd (line 177) | def copy_bdd(
function _copy_bdd (line 198) | def _copy_bdd(
function _flip (line 240) | def _flip(
function copy_zdd (line 253) | def copy_zdd(
function _copy_zdd (line 275) | def _copy_zdd(
function dump_json (line 311) | def dump_json(
function _dump_json (line 348) | def _dump_json(
function _dump_bdd_info (line 369) | def _dump_bdd_info(
function _dump_bdd (line 393) | def _dump_bdd(
function load_json (line 427) | def load_json(
function _load_json (line 459) | def _load_json(
function _parse_line (line 522) | def _parse_line(
function _store_line (line 537) | def _store_line(
function _make_node (line 570) | def _make_node(
function _decode_node (line 606) | def _decode_node(
function _node_from_int (line 619) | def _node_from_int(
function _node_to_int (line 638) | def _node_to_int(
FILE: dd/_parser.py
class _Token (line 16) | class _Token(_ty.Protocol):
class Lexer (line 21) | class Lexer(astutils.Lexer):
method __init__ (line 24) | def __init__(
method t_NAME (line 62) | def t_NAME(
method t_AND (line 75) | def t_AND(
method t_OR (line 88) | def t_OR(
method t_NOT (line 101) | def t_NOT(
method t_IMPLIES (line 113) | def t_IMPLIES(
method t_EQUIV (line 125) | def t_EQUIV(
method t_trailing_comment (line 155) | def t_trailing_comment(
method t_doubly_delimited_comment (line 163) | def t_doubly_delimited_comment(
method t_newline (line 175) | def t_newline(
class _ParserProtocol (line 185) | class _ParserProtocol(
method _apply (line 191) | def _apply(
method _add_var (line 200) | def _add_var(
method _add_int (line 207) | def _add_int(
method _add_bool (line 214) | def _add_bool(
class Parser (line 222) | class Parser(
method __init__ (line 227) | def __init__(
method _apply (line 261) | def _apply(
method _add_var (line 279) | def _add_var(
method _add_int (line 287) | def _add_int(
method _add_bool (line 295) | def _add_bool(
method p_bool (line 303) | def p_bool(
method p_node (line 313) | def p_node(
method p_number (line 321) | def p_number(
method p_negative_number (line 329) | def p_negative_number(
method p_var (line 338) | def p_var(
method p_unary (line 346) | def p_unary(
method p_binary (line 355) | def p_binary(
method p_ternary_conditional (line 371) | def p_ternary_conditional(
method p_quantifier (line 383) | def p_quantifier(
method p_rename (line 394) | def p_rename(
method p_substitutions_iter (line 403) | def p_substitutions_iter(
method p_substitutions_end (line 413) | def p_substitutions_end(
method p_substitution (line 421) | def p_substitution(
method p_names_iter (line 431) | def p_names_iter(
method p_names_end (line 441) | def p_names_end(
method p_name (line 449) | def p_name(
method p_paren (line 458) | def p_paren(
class _BDD (line 470) | class _BDD(_ty.Protocol[
method false (line 475) | def false(
method true (line 481) | def true(
method var (line 486) | def var(
method apply (line 493) | def apply(
method quantify (line 508) | def quantify(
method rename (line 519) | def rename(
method _add_int (line 528) | def _add_int(
class _Translator (line 536) | class _Translator(Parser):
method __init__ (line 539) | def __init__(
method parse (line 545) | def parse(
method _reset_state (line 562) | def _reset_state(
method _add_bool (line 579) | def _add_bool(
method _add_int (line 589) | def _add_int(
method _add_var (line 597) | def _add_var(
method _apply (line 604) | def _apply(
function add_expr (line 635) | def add_expr(
function _rewrite_tables (line 652) | def _rewrite_tables(
function _main (line 663) | def _main(
FILE: dd/_utils.py
function import_module (line 40) | def import_module(
function print_var_levels (line 57) | def print_var_levels(
function var_counts (line 70) | def var_counts(
function contiguous_levels (line 97) | def contiguous_levels(
function raise_runtimerror_about_ref_count (line 116) | def raise_runtimerror_about_ref_count(
function map_container (line 166) | def map_container(
function map_container (line 176) | def map_container(
function map_container (line 185) | def map_container(
function _map_values (line 201) | def _map_values(
function values_of (line 214) | def values_of(
function total_memory (line 231) | def total_memory(
function assert_operator_arity (line 273) | def assert_operator_arity(
class DotGraph (line 328) | class DotGraph:
method __init__ (line 329) | def __init__(
method add_node (line 344) | def add_node(
method add_edge (line 355) | def add_edge(
method to_dot (line 367) | def to_dot(
method dump (line 434) | def dump(
FILE: dd/autoref.py
class BDD (line 43) | class BDD(dd._abc.BDD[_Ref]):
method __init__ (line 60) | def __init__(
method __eq__ (line 69) | def __eq__(
method __len__ (line 78) | def __len__(
method __contains__ (line 83) | def __contains__(
method __str__ (line 92) | def __str__(
method _wrap (line 101) | def _wrap(
method configure (line 118) | def configure(
method succ (line 126) | def succ(
method incref (line 144) | def incref(
method decref (line 151) | def decref(
method declare (line 159) | def declare(
method add_var (line 167) | def add_var(
method var (line 177) | def var(
method var_at_level (line 185) | def var_at_level(
method level_of_var (line 192) | def level_of_var(
method var_levels (line 200) | def var_levels(
method reorder (line 205) | def reorder(
method copy (line 213) | def copy(
method support (line 228) | def support(
method let (line 239) | def let(
method quantify (line 275) | def quantify(
method forall (line 290) | def forall(
method exist (line 299) | def exist(
method ite (line 309) | def ite(
method find_or_add (line 327) | def find_or_add(
method count (line 341) | def count(
method pick_iter (line 353) | def pick_iter(
method add_expr (line 366) | def add_expr(
method to_expr (line 374) | def to_expr(
method apply (line 383) | def apply(
method _add_int (line 412) | def _add_int(
method cube (line 420) | def cube(
method collect_garbage (line 428) | def collect_garbage(
method dump (line 434) | def dump(
method load (line 518) | def load(
method _load_pickle (line 570) | def _load_pickle(
method assert_consistent (line 582) | def assert_consistent(
method false (line 588) | def false(
method true (line 595) | def true(
function image (line 602) | def image(
function preimage (line 623) | def preimage(
function reorder (line 644) | def reorder(
function copy_vars (line 655) | def copy_vars(
function copy_bdd (line 664) | def copy_bdd(
class Function (line 674) | class Function(dd._abc.Operator):
method __init__ (line 705) | def __init__(
method __hash__ (line 719) | def __hash__(
method to_expr (line 724) | def to_expr(
method __int__ (line 730) | def __int__(
method __len__ (line 735) | def __len__(
method dag_size (line 741) | def dag_size(
method __del__ (line 746) | def __del__(
method __eq__ (line 756) | def __eq__(
method __ne__ (line 768) | def __ne__(
method __le__ (line 780) | def __le__(
method __lt__ (line 788) | def __lt__(
method __invert__ (line 796) | def __invert__(
method __and__ (line 801) | def __and__(
method __or__ (line 808) | def __or__(
method __xor__ (line 815) | def __xor__(
method implies (line 822) | def implies(
method equiv (line 829) | def equiv(
method _apply (line 836) | def _apply(
method level (line 854) | def level(
method var (line 861) | def var(
method low (line 872) | def low(
method high (line 884) | def high(
method ref (line 896) | def ref(
method negated (line 902) | def negated(
method support (line 908) | def support(
FILE: dd/bdd.py
function _request_reordering (line 64) | def _request_reordering(
function _try_to_reorder (line 79) | def _try_to_reorder(
class _ReorderingContext (line 113) | class _ReorderingContext:
method __init__ (line 116) | def __init__(
method __enter__ (line 124) | def __enter__(
method __exit__ (line 129) | def __exit__(
class _NeedsReordering (line 142) | class _NeedsReordering(Exception):
class BDD (line 167) | class BDD(dd._abc.BDD[_Ref]):
method __init__ (line 200) | def __init__(
method __copy__ (line 253) | def __copy__(
method __del__ (line 265) | def __del__(
method __len__ (line 290) | def __len__(
method __contains__ (line 295) | def __contains__(
method __iter__ (line 302) | def __iter__(
method __str__ (line 306) | def __str__(
method configure (line 315) | def configure(
method ordering (line 344) | def ordering(
method _init_terminal (line 350) | def _init_terminal(
method succ (line 368) | def succ(
method incref (line 376) | def incref(
method decref (line 384) | def decref(
method ref (line 405) | def ref(
method declare (line 413) | def declare(
method add_var (line 421) | def add_var(
method _check_var (line 471) | def _check_var(
method _next_free_level (line 512) | def _next_free_level(
method var (line 554) | def var(
method var_at_level (line 568) | def var_at_level(
method level_of_var (line 580) | def level_of_var(
method var_levels (line 594) | def var_levels(
method _map_to_level (line 600) | def _map_to_level(
method _map_to_level (line 613) | def _map_to_level(
method _map_to_level (line 623) | def _map_to_level(
method _assert_keys_are_levels (line 684) | def _assert_keys_are_levels(
method _top_var (line 717) | def _top_var(
method copy (line 727) | def copy(
method descendants (line 737) | def descendants(
method _descendants (line 758) | def _descendants(
method is_essential (line 777) | def is_essential(
method support (line 809) | def support(
method _support (line 824) | def _support(
method levels (line 855) | def levels(
method _levels (line 883) | def _levels(
method reduction (line 901) | def reduction(
method undeclare_vars (line 929) | def undeclare_vars(
method let (line 1007) | def let(
method compose (line 1040) | def compose(
method _compose (line 1070) | def _compose(
method _vector_compose (line 1121) | def _vector_compose(
method rename (line 1167) | def rename(
method _top_cofactor (line 1182) | def _top_cofactor(
method cofactor (line 1215) | def cofactor(
method _cofactor (line 1233) | def _cofactor(
method quantify (line 1296) | def quantify(
method _quantify (line 1326) | def _quantify(
method forall (line 1386) | def forall(
method exist (line 1398) | def exist(
method ite (line 1411) | def ite(
method _ite (line 1424) | def _ite(
method find_or_add (line 1458) | def find_or_add(
method _next_free_int (line 1537) | def _next_free_int(
method collect_garbage (line 1553) | def collect_garbage(
method update_predecessors (line 1619) | def update_predecessors(
method swap (line 1632) | def swap(
method _low_high (line 1851) | def _low_high(
method _swap_cofactor (line 1879) | def _swap_cofactor(
method count (line 1907) | def count(
method _assert_int (line 1944) | def _assert_int(
method _sat_len (line 1959) | def _sat_len(
method pick_iter (line 2009) | def pick_iter(
method _sat_iter (line 2048) | def _sat_iter(
method assert_consistent (line 2086) | def assert_consistent(
method add_expr (line 2149) | def add_expr(
method to_expr (line 2156) | def to_expr(
method _to_expr (line 2169) | def _to_expr(
method apply (line 2201) | def apply(
method _add_int (line 2263) | def _add_int(
method cube (line 2276) | def cube(
method dump (line 2295) | def dump(
method _dump_figure (line 2336) | def _dump_figure(
method _dump_bdd (line 2351) | def _dump_bdd(
method load (line 2379) | def load(
method _load_pickle (line 2403) | def _load_pickle(
method _load (line 2441) | def _load(
method _dump_manager (line 2478) | def _dump_manager(
method _load_manager (line 2498) | def _load_manager(
method false (line 2516) | def false(
method true (line 2522) | def true(
function _enumerate_minterms (line 2528) | def _enumerate_minterms(
function _assert_isomorphic_orders (line 2563) | def _assert_isomorphic_orders(
function _assert_valid_ordering (line 2597) | def _assert_valid_ordering(
function rename (line 2618) | def rename(
function _assert_valid_rename (line 2648) | def _assert_valid_rename(
function _all_adjacent (line 2674) | def _all_adjacent(
function _adjacent (line 2692) | def _adjacent(
function _assert_no_overlap (line 2713) | def _assert_no_overlap(
function image (line 2723) | def image(
function preimage (line 2784) | def preimage(
function _image (line 2837) | def _image(
function reorder (line 2917) | def reorder(
function _apply_sifting (line 2945) | def _apply_sifting(
function _reorder_var (line 2967) | def _reorder_var(
function _shift (line 3002) | def _shift(
function _sort_to_order (line 3043) | def _sort_to_order(
function reorder_to_pairs (line 3082) | def reorder_to_pairs(
function copy_bdd (line 3113) | def copy_bdd(
function _copy_bdd (line 3142) | def _copy_bdd(
function _flip (line 3206) | def _flip(
function to_nx (line 3216) | def to_nx(
function _to_dot (line 3282) | def _to_dot(
FILE: dd/dddmp.py
class Lexer (line 41) | class Lexer:
method __init__ (line 44) | def __init__(
method t_KEYWORD (line 103) | def t_KEYWORD(
method t_NAME (line 115) | def t_NAME(
method t_comment (line 126) | def t_comment(
method t_newline (line 132) | def t_newline(
method t_error (line 137) | def t_error(
method build (line 143) | def build(
class Parser (line 165) | class Parser:
method __init__ (line 168) | def __init__(
method build (line 176) | def build(
method parse (line 197) | def parse(
method _parse_header (line 208) | def _parse_header(
method _parse_body (line 277) | def _parse_body(
method _add_node (line 301) | def _add_node(
method reset (line 329) | def reset(
method _assert_consistent (line 349) | def _assert_consistent(
method p_file (line 386) | def p_file(
method p_lines_iter (line 391) | def p_lines_iter(
method p_lines_end (line 395) | def p_lines_end(
method p_line (line 399) | def p_line(
method p_version (line 419) | def p_version(
method p_text_mode (line 423) | def p_text_mode(
method p_varinfo (line 435) | def p_varinfo(
method p_dd_name (line 440) | def p_dd_name(
method p_num_nodes (line 445) | def p_num_nodes(
method p_num_vars (line 450) | def p_num_vars(
method p_nsupport_vars (line 455) | def p_nsupport_vars(
method p_support_varnames (line 460) | def p_support_varnames(
method p_ordered_varnames (line 465) | def p_ordered_varnames(
method p_varnames_iter (line 470) | def p_varnames_iter(
method p_varnames_end (line 476) | def p_varnames_end(
method p_varname (line 481) | def p_varname(
method p_var_ids (line 488) | def p_var_ids(
method p_permuted_ids (line 493) | def p_permuted_ids(
method p_aux_ids (line 498) | def p_aux_ids(
method p_integers_iter (line 503) | def p_integers_iter(
method p_integers_end (line 509) | def p_integers_end(
method p_num_roots (line 514) | def p_num_roots(
method p_root_ids (line 519) | def p_root_ids(
method p_root_names (line 524) | def p_root_names(
method p_algebraic_dd (line 529) | def p_algebraic_dd(
method p_number (line 534) | def p_number(
method p_neg_number (line 539) | def p_neg_number(
method p_expression_name (line 544) | def p_expression_name(
method p_error (line 549) | def p_error(
function load (line 554) | def load(
function _rewrite_tables (line 599) | def _rewrite_tables(
FILE: dd/mdd.py
class MDD (line 46) | class MDD:
method __init__ (line 72) | def __init__(
method __len__ (line 103) | def __len__(
method __contains__ (line 108) | def __contains__(
method __iter__ (line 116) | def __iter__(
method _allocate (line 121) | def _allocate(
method _release (line 131) | def _release(
method incref (line 149) | def incref(
method decref (line 157) | def decref(
method ref (line 166) | def ref(
method var_at_level (line 174) | def var_at_level(
method level_of_var (line 186) | def level_of_var(
method ite (line 194) | def ite(
method _top_cofactor (line 229) | def _top_cofactor(
method find_or_add (line 273) | def find_or_add(
method collect_garbage (line 322) | def collect_garbage(
method to_expr (line 360) | def to_expr(
method apply (line 399) | def apply(
method dump (line 457) | def dump(
function bdd_to_mdd (line 483) | def bdd_to_mdd(
function _enumerate_integer (line 595) | def _enumerate_integer(
function _debug_dump (line 609) | def _debug_dump(
function _to_dot (line 636) | def _to_dot(
FILE: download.py
function extensions (line 78) | def extensions(
function _copy_cudd_license (line 154) | def _copy_cudd_license(
function _copy_extern_licenses (line 171) | def _copy_extern_licenses(
function _join (line 200) | def _join(
function fetch (line 212) | def fetch(
function _check_file_hash (line 296) | def _check_file_hash(
function _assert_sha (line 311) | def _assert_sha(
function untar (line 371) | def untar(
function make_cudd (line 382) | def make_cudd(
function fetch_cudd (line 391) | def fetch_cudd(
function download_licenses (line 400) | def download_licenses(
function _fetch_file (line 437) | def _fetch_file(
FILE: examples/_test_examples.py
function _main (line 7) | def _main(
FILE: examples/bdd_traversal.py
function traverse_breadth_first (line 20) | def traverse_breadth_first(u):
function traverse_depth_first (line 34) | def traverse_depth_first(u):
function run_traversals (line 48) | def run_traversals():
FILE: examples/boolean_satisfiability.py
function example (line 5) | def example():
function is_satisfiable (line 19) | def is_satisfiable(formula, names):
function _print_result (line 34) | def _print_result(formula, sat):
FILE: examples/cudd_memory_limits.py
function configure (line 5) | def configure():
FILE: examples/cudd_statistics.py
function print_statistics (line 7) | def print_statistics():
function format_dict (line 16) | def format_dict(d):
function format_number (line 21) | def format_number(x):
FILE: examples/cudd_zdd.py
function zdd_example (line 5) | def zdd_example():
FILE: examples/good_vs_bad_variable_order.py
function comparing_two_variable_orders (line 16) | def comparing_two_variable_orders():
function list_to_dict (line 42) | def list_to_dict(c):
function prime (line 46) | def prime(s):
FILE: examples/json_example.py
function json_example (line 5) | def json_example():
function dump_bdd_as_json (line 12) | def dump_bdd_as_json(filename):
function load_bdd_from_json (line 22) | def load_bdd_from_json(filename):
FILE: examples/json_load.py
function dump_load_example (line 18) | def dump_load_example(
function create_and_dump_bdd (line 36) | def create_and_dump_bdd(
class BDDGraph (line 50) | class BDDGraph(
method __init__ (line 54) | def __init__(
function load_and_map_to_nx (line 63) | def load_and_map_to_nx(
FILE: examples/np.py
function comparing_two_variable_orders (line 16) | def comparing_two_variable_orders():
function list_to_dict (line 54) | def list_to_dict(c):
function prime (line 60) | def prime(s):
FILE: examples/queens.py
function solve_queens (line 20) | def solve_queens(n):
function queens_formula (line 34) | def queens_formula(n):
function at_least_one_queen_per_row (line 47) | def at_least_one_queen_per_row(n):
function at_most_one_queen_per_line (line 57) | def at_most_one_queen_per_line(row, n):
function at_most_one_queen_per_diagonal (line 74) | def at_most_one_queen_per_diagonal(slash, n):
function mutex (line 102) | def mutex(v):
function _var_str (line 117) | def _var_str(i, j):
function _conjoin (line 122) | def _conjoin(
function _disjoin (line 135) | def _disjoin(
function _apply_infix (line 148) | def _apply_infix(
function _parenthesize (line 160) | def _parenthesize(string) -> str:
function benchmark (line 165) | def benchmark(n):
function _example (line 182) | def _example():
FILE: examples/reachability.py
function transition_system (line 18) | def transition_system(bdd):
function least_fixpoint (line 33) | def least_fixpoint(transitions, bdd):
function reachability_example (line 54) | def reachability_example():
FILE: examples/reordering.py
function demo_dynamic_reordering (line 7) | def demo_dynamic_reordering():
function show_logging (line 28) | def show_logging():
function create_manager (line 39) | def create_manager():
function trigger_reordering (line 47) | def trigger_reordering(bdd):
function print_var_levels (line 70) | def print_var_levels(bdd):
function demo_static_reordering (line 81) | def demo_static_reordering():
function demo_specific_var_order (line 97) | def demo_specific_var_order():
function print_manager_size (line 114) | def print_manager_size(bdd):
FILE: examples/transfer_bdd.py
function transfer (line 5) | def transfer():
function copy_variable_order (line 20) | def copy_variable_order():
FILE: examples/variable_substitution.py
function variable_substitution (line 6) | def variable_substitution():
FILE: examples/what_is_a_bdd.py
function bdd_implementation_example (line 10) | def bdd_implementation_example():
function let (line 66) | def let(
FILE: setup.py
function git_version (line 69) | def git_version(
function _parse_version (line 101) | def _parse_version(
function parse_args (line 113) | def parse_args(
function read_env_vars (line 141) | def read_env_vars(
function run_setup (line 157) | def run_setup(
function _build_parsers (line 204) | def _build_parsers(
function _parser_requirements_installed (line 216) | def _parser_requirements_installed(
FILE: tests/autoref_test.py
class Tests (line 18) | class Tests(common.Tests):
method setup_method (line 19) | def setup_method(self):
class BDDTests (line 23) | class BDDTests(common_bdd.Tests):
method setup_method (line 24) | def setup_method(self):
function test_str (line 28) | def test_str():
function test_find_or_add (line 34) | def test_find_or_add():
function test_count (line 47) | def test_count():
function test_dump_load (line 61) | def test_dump_load():
function test_dump_using_graphviz (line 79) | def test_dump_using_graphviz():
function test_image (line 97) | def test_image():
function test_preimage (line 109) | def test_preimage():
function test_reorder_2 (line 121) | def test_reorder_2():
function test_configure_dynamic_reordering (line 142) | def test_configure_dynamic_reordering():
function test_collect_garbage (line 170) | def test_collect_garbage():
function test_copy_vars (line 185) | def test_copy_vars():
function test_copy_bdd (line 194) | def test_copy_bdd():
function test_func_len (line 208) | def test_func_len():
function test_dd_version (line 219) | def test_dd_version():
FILE: tests/bdd_test.py
class BDD (line 14) | class BDD(_bdd.BDD):
method __del__ (line 23) | def __del__(self):
function test_add_var (line 27) | def test_add_var():
function test_var (line 83) | def test_var():
function test_assert_consistent (line 96) | def test_assert_consistent():
function test_level_to_variable (line 118) | def test_level_to_variable():
function test_var_levels_attr (line 127) | def test_var_levels_attr():
function test_descendants (line 136) | def test_descendants():
function test_is_essential (line 174) | def test_is_essential():
function test_support (line 194) | def test_support():
function test_count (line 206) | def test_count():
function test_pick_iter (line 244) | def test_pick_iter():
function compare_iter_to_list_of_sets (line 290) | def compare_iter_to_list_of_sets(u, g, s, care_bits):
function test_enumerate_minterms (line 298) | def test_enumerate_minterms():
function set_from_generator_of_dict (line 331) | def set_from_generator_of_dict(gen):
function test_isomorphism (line 338) | def test_isomorphism():
function test_elimination (line 352) | def test_elimination():
function test_reduce_combined (line 363) | def test_reduce_combined():
function test_reduction_complemented_edges (line 394) | def test_reduction_complemented_edges():
function _test_reduction_complemented_edges (line 412) | def _test_reduction_complemented_edges(expr, bdd):
function test_find_or_add (line 423) | def test_find_or_add():
function test_next_free_int (line 527) | def test_next_free_int():
function _assert_smaller_are_nodes (line 554) | def _assert_smaller_are_nodes(start, bdd):
function test_collect_garbage (line 559) | def test_collect_garbage():
function test_top_cofactor (line 592) | def test_top_cofactor():
function test_ite (line 605) | def test_ite():
function test_add_expr (line 631) | def test_add_expr():
function test_expr_comments (line 645) | def test_expr_comments():
function test_compose (line 663) | def test_compose():
function test_vector_compose (line 700) | def test_vector_compose():
function test_cofactor (line 718) | def test_cofactor():
function test_swap (line 743) | def test_swap():
function test_sifting (line 829) | def test_sifting():
function test_request_reordering (line 848) | def test_request_reordering():
function test_reordering_context (line 866) | def test_reordering_context():
class Dummy (line 895) | class Dummy:
method __init__ (line 898) | def __init__(self):
method __len__ (line 903) | def __len__(self):
method assert_ (line 906) | def assert_(self, value):
function test_dynamic_reordering (line 911) | def test_dynamic_reordering():
class TrackReorderings (line 951) | class TrackReorderings(BDD):
method __init__ (line 954) | def __init__(self, *arg, **kw):
method swap (line 958) | def swap(self, *arg, **kw):
method reordering_is_on (line 962) | def reordering_is_on(self):
function test_undeclare_vars (line 968) | def test_undeclare_vars():
function test_del_repeated_calls (line 1007) | def test_del_repeated_calls():
function _references_exist (line 1025) | def _references_exist(refs):
function test_dump_load (line 1031) | def test_dump_load():
function test_dump_load_manager (line 1050) | def test_dump_load_manager():
function test_dump_using_graphviz (line 1065) | def test_dump_using_graphviz():
function _dump_bdd_roots_as_filetype (line 1078) | def _dump_bdd_roots_as_filetype(
function test_quantify (line 1087) | def test_quantify():
function test_quantifier_syntax (line 1120) | def test_quantifier_syntax():
function test_rename (line 1152) | def test_rename():
function test_rename_syntax (line 1190) | def test_rename_syntax():
function test_image_rename_map_checks (line 1216) | def test_image_rename_map_checks():
function test_preimage (line 1250) | def test_preimage():
function test_assert_valid_ordering (line 1310) | def test_assert_valid_ordering():
function test_assert_refined_ordering (line 1318) | def test_assert_refined_ordering():
function test_to_graphviz_dot (line 1324) | def test_to_graphviz_dot():
function _graph_from_dot (line 1352) | def _graph_from_dot(dot_graph):
function _graph_from_dot_recurse (line 1358) | def _graph_from_dot_recurse(dot_graph, g):
function test_function_wrapper (line 1369) | def test_function_wrapper():
function x_or_y (line 1431) | def x_or_y():
function x_and_y (line 1444) | def x_and_y():
function two_vars_xy (line 1456) | def two_vars_xy():
function x_and_not_y (line 1475) | def x_and_not_y():
function assert_valid_succ_pred (line 1505) | def assert_valid_succ_pred(u, t, g):
function ref_var (line 1514) | def ref_var(i):
function ref_x_and_y (line 1523) | def ref_x_and_y():
function ref_x_or_y (line 1535) | def ref_x_or_y():
function compare (line 1547) | def compare(u, bdd, h):
function _nm (line 1561) | def _nm(x, y):
function _em (line 1565) | def _em(x, y):
FILE: tests/common.py
class Tests (line 9) | class Tests:
method setup_method (line 10) | def setup_method(self):
method test_true_false (line 14) | def test_true_false(self):
method test_configure_reordering (line 25) | def test_configure_reordering(self):
method test_succ (line 37) | def test_succ(self):
method test_add_var (line 47) | def test_add_var(self):
method test_var_cofactor (line 56) | def test_var_cofactor(self):
method test_richcmp (line 67) | def test_richcmp(self):
method test_len (line 73) | def test_len(self):
method test_contains (line 78) | def test_contains(self):
method test_var_levels (line 91) | def test_var_levels(self):
method test_var_levels_attr (line 111) | def test_var_levels_attr(self):
method test_levels (line 119) | def test_levels(self):
method test_copy (line 138) | def test_copy(self):
method test_compose (line 151) | def test_compose(self):
method test_cofactor (line 204) | def test_cofactor(self):
method test_count (line 263) | def test_count(self):
method test_pick_iter (line 308) | def test_pick_iter(self):
method equal_list_contents (line 368) | def equal_list_contents(self, x, y):
method test_apply (line 374) | def test_apply(self):
method test_quantify (line 429) | def test_quantify(self):
method test_exist_forall (line 475) | def test_exist_forall(self):
method test_cube (line 507) | def test_cube(self):
method test_add_expr (line 527) | def test_add_expr(self):
method test_to_expr (line 558) | def test_to_expr(self):
method test_support (line 574) | def test_support(self):
method test_rename (line 611) | def test_rename(self):
method test_ite (line 676) | def test_ite(self):
method test_reorder_with_args (line 690) | def test_reorder_with_args(self):
method test_reorder_without_args (line 703) | def test_reorder_without_args(self):
method _confirm_var_order (line 725) | def _confirm_var_order(self, vrs, bdd):
method test_reorder_contains (line 730) | def test_reorder_contains(self):
method test_comparators (line 737) | def test_comparators(self):
method test_function_support (line 774) | def test_function_support(self):
method test_node_hash (line 785) | def test_node_hash(self):
method test_add_int (line 793) | def test_add_int(self):
method test_dump_using_graphviz (line 807) | def test_dump_using_graphviz(
FILE: tests/common_bdd.py
class Tests (line 9) | class Tests:
method setup_method (line 10) | def setup_method(self):
method test_succ (line 13) | def test_succ(self):
method test_find_or_add (line 22) | def test_find_or_add(self):
method test_function (line 39) | def test_function(self):
method test_function_properties (line 116) | def test_function_properties(self):
method test_negated (line 145) | def test_negated(self):
method test_dump_pdf (line 155) | def test_dump_pdf(self):
method test_dump_load_json (line 166) | def test_dump_load_json(self):
method rm_file (line 182) | def rm_file(self, fname):
FILE: tests/common_cudd.py
class Tests (line 7) | class Tests:
method setup_method (line 8) | def setup_method(self):
method test_add_var (line 14) | def test_add_var(self):
method test_len (line 31) | def test_len(self):
method test_levels (line 39) | def test_levels(self):
method test_var_at_level_exceptions (line 58) | def test_var_at_level_exceptions(self):
method test_incref_decref_locally_inconsistent (line 91) | def test_incref_decref_locally_inconsistent(self):
method test_decref_incref_locally_inconsistent (line 114) | def test_decref_incref_locally_inconsistent(self):
method test_double_incref_decref_locally_inconsistent (line 139) | def test_double_incref_decref_locally_inconsistent(self):
method test_decref_and_dealloc (line 169) | def test_decref_and_dealloc(self):
method test_decref (line 195) | def test_decref(self):
method test_decref_ref_lower_bound (line 223) | def test_decref_ref_lower_bound(self):
method test_dealloc_wrong_ref_lower_bound (line 263) | def test_dealloc_wrong_ref_lower_bound(self):
method test_dealloc_multiple_calls (line 280) | def test_dealloc_multiple_calls(self):
FILE: tests/copy_test.py
function test_involution (line 9) | def test_involution():
function _test_involution (line 14) | def _test_involution(mod):
function test_bdd_mapping (line 22) | def test_bdd_mapping():
function _test_bdd_mapping (line 27) | def _test_bdd_mapping(mod):
function _check_bdd_mapping (line 36) | def _check_bdd_mapping(umap, old_bdd, new_bdd):
function _map_node (line 60) | def _map_node(u, umap):
function _setup (line 67) | def _setup(mod):
function test_dump_load_same_order (line 75) | def test_dump_load_same_order():
function _test_dump_load_same_order (line 80) | def _test_dump_load_same_order(mod):
function test_dump_load_different_order (line 102) | def test_dump_load_different_order():
function _test_dump_load_different_order (line 107) | def _test_dump_load_different_order(mod):
FILE: tests/cudd_test.py
class Tests (line 17) | class Tests(common.Tests):
method setup_method (line 18) | def setup_method(self):
class BDDTests (line 22) | class BDDTests(common_bdd.Tests):
method setup_method (line 23) | def setup_method(self):
class CuddTests (line 27) | class CuddTests(common_cudd.Tests):
method setup_method (line 28) | def setup_method(self):
function test_str (line 33) | def test_str():
function test_insert_var (line 40) | def test_insert_var():
function test_refs (line 53) | def test_refs():
function test_len (line 58) | def test_len():
function test_cube_array (line 84) | def test_cube_array():
function test_dump_load_dddmp (line 89) | def test_dump_load_dddmp():
function test_load_sample0 (line 100) | def test_load_sample0():
function test_and_exists (line 114) | def test_and_exists():
function test_or_forall (line 130) | def test_or_forall():
function test_swap (line 142) | def test_swap():
function test_copy_bdd_same_indices (line 203) | def test_copy_bdd_same_indices():
function test_copy_bdd_different_indices (line 226) | def test_copy_bdd_different_indices():
function test_copy_bdd_different_order (line 250) | def test_copy_bdd_different_order():
function test_count_nodes (line 289) | def test_count_nodes():
function test_function (line 304) | def test_function():
FILE: tests/cudd_zdd_test.py
class Tests (line 18) | class Tests(common.Tests):
method setup_method (line 19) | def setup_method(self):
class CuddTests (line 23) | class CuddTests(common_cudd.Tests):
method setup_method (line 24) | def setup_method(self):
function test_str (line 29) | def test_str():
function test_false (line 36) | def test_false():
function test_true (line 42) | def test_true():
function test_true_node (line 51) | def test_true_node():
function test_index_at_level (line 60) | def test_index_at_level():
function test_var_level_gaps (line 88) | def test_var_level_gaps():
function _max_var_level (line 97) | def _max_var_level(zdd):
function test_gt_var_levels (line 116) | def test_gt_var_levels():
function test_number_of_cudd_vars_without_gaps (line 134) | def test_number_of_cudd_vars_without_gaps():
function test_number_of_cudd_vars_with_gaps (line 148) | def test_number_of_cudd_vars_with_gaps():
function _assert_n_vars_max_level (line 162) | def _assert_n_vars_max_level(
function test_var (line 181) | def test_var():
function test_support_cudd (line 195) | def test_support_cudd():
function test_cudd_cofactor (line 206) | def test_cudd_cofactor():
function test_find_or_add (line 219) | def test_find_or_add():
function test_count (line 231) | def test_count():
function test_bdd_to_zdd_copy (line 246) | def test_bdd_to_zdd_copy():
function test_len (line 263) | def test_len():
function test_ith_var_without_gaps (line 280) | def test_ith_var_without_gaps():
function test_ith_var_with_gaps (line 339) | def test_ith_var_with_gaps():
function test_disjunction (line 351) | def test_disjunction():
function test_conjunction (line 377) | def test_conjunction():
function test_methods_disjoin_conjoin_gaps_opt (line 390) | def test_methods_disjoin_conjoin_gaps_opt():
function test_methods_disjoin_conjoin_gaps (line 395) | def test_methods_disjoin_conjoin_gaps():
function test_method_disjoin (line 411) | def test_method_disjoin():
function test_methods_disjoin_conjoin_with_opt (line 421) | def test_methods_disjoin_conjoin_with_opt():
function test_methods_disjoin_conjoin (line 426) | def test_methods_disjoin_conjoin():
function run_python_with_optimization (line 445) | def run_python_with_optimization(
function _assert (line 474) | def _assert(test):
function test_c_disjunction (line 480) | def test_c_disjunction():
function test_c_conjunction (line 490) | def test_c_conjunction():
function test_c_disjoin_conjoin (line 500) | def test_c_disjoin_conjoin():
function test_c_disjoin_conjoin_leaf_check (line 514) | def test_c_disjoin_conjoin_leaf_check():
function test_c_exist (line 534) | def test_c_exist():
function test_dump (line 551) | def test_dump():
function test_dict_to_zdd (line 563) | def test_dict_to_zdd():
function print_size (line 577) | def print_size(u, msg):
FILE: tests/dddmp_test.py
function test_lexer (line 14) | def test_lexer():
function test_parser (line 24) | def test_parser():
function test_sample0 (line 32) | def test_sample0():
function test_sample1 (line 56) | def test_sample1():
function test_sample2 (line 66) | def test_sample2():
function test_sample3 (line 90) | def test_sample3():
function test_load_dddmp (line 106) | def test_load_dddmp():
function test_rewrite_tables (line 136) | def test_rewrite_tables():
function to_nx (line 146) | def to_nx(bdd, n_vars, ordering, roots):
function test_dump_with_cudd_load_with_dddmp (line 185) | def test_dump_with_cudd_load_with_dddmp():
FILE: tests/inspect_cython_signatures.py
function inspect_signatures (line 33) | def inspect_signatures(spec, imp):
function is_hidden (line 82) | def is_hidden(method_name):
function get_signature (line 87) | def get_signature(func):
function _main (line 99) | def _main():
FILE: tests/iterative_recursive_flattener.py
function _recurse_syntax_tree (line 24) | def _recurse_syntax_tree(
function _reduce_syntax_tree (line 85) | def _reduce_syntax_tree(
function _reduce_step (line 105) | def _reduce_step(
function _reduce_quantifier (line 144) | def _reduce_quantifier(
function _reduce_substitution (line 169) | def _reduce_substitution(
function _reduce_operator (line 191) | def _reduce_operator(
FILE: tests/mdd_test.py
function test_ite (line 12) | def test_ite():
function test_find_or_add (line 25) | def test_find_or_add():
function test_bdd_to_mdd (line 34) | def test_bdd_to_mdd():
function test_mdd_dump_to_pdf (line 51) | def test_mdd_dump_to_pdf():
FILE: tests/parser_test.py
function _make_parser_test_expressions (line 21) | def _make_parser_test_expressions(
function test_all_parsers_same_results (line 81) | def test_all_parsers_same_results():
function test_translator_vs_recursion_limit (line 104) | def test_translator_vs_recursion_limit():
function test_log_syntax_tree_to_bdd (line 116) | def test_log_syntax_tree_to_bdd():
function test_linear_syntax_tree_to_bdd (line 131) | def test_linear_syntax_tree_to_bdd():
function make_expr_gt_recursion_limit (line 146) | def make_expr_gt_recursion_limit(
function syntax_tree_of_shape (line 160) | def syntax_tree_of_shape(
function _delimit (line 186) | def _delimit(
function test_lexing (line 202) | def test_lexing():
function _assert_names_operator (line 333) | def _assert_names_operator(
function tokenize (line 349) | def tokenize(
function test_parsing (line 367) | def test_parsing():
function _check_binary_operator (line 549) | def _check_binary_operator(
function _assert_binary_operator_tree (line 565) | def _assert_binary_operator_tree(
function _assert_false_true_nodes (line 577) | def _assert_false_true_nodes(
function _assert_false_node (line 584) | def _assert_false_node(
function _assert_true_node (line 590) | def _assert_true_node(
function test_add_expr (line 596) | def test_add_expr():
class BDD (line 603) | class BDD:
method __init__ (line 606) | def __init__(
method _add_int (line 611) | def _add_int(
method var (line 617) | def var(
method apply (line 623) | def apply(
method quantify (line 633) | def quantify(
method rename (line 645) | def rename(
function test_recursive_traversal_vs_recursion_limit (line 656) | def test_recursive_traversal_vs_recursion_limit():
function test_iterative_traversal_vs_recursion_limit (line 670) | def test_iterative_traversal_vs_recursion_limit():
FILE: tests/regressions_test.py
function test_reordering_setting_restore (line 4) | def test_reordering_setting_restore():
FILE: tests/sylvan_test.py
function test_len (line 9) | def test_len():
function test_true_false (line 25) | def test_true_false():
function test_add_var (line 36) | def test_add_var():
function test_insert_var (line 55) | def test_insert_var():
function test_add_expr (line 68) | def test_add_expr():
function test_support (line 101) | def test_support():
function test_compose (line 117) | def test_compose():
function test_cofactor (line 129) | def test_cofactor():
function test_rename (line 146) | def test_rename():
function test_count (line 169) | def test_count():
function test_pick_iter (line 200) | def test_pick_iter():
function equal_list_contents (line 263) | def equal_list_contents(x, y):
function test_py_operators (line 270) | def test_py_operators():
Condensed preview — 68 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (809K chars).
[
{
"path": ".gitattributes",
"chars": 113,
"preview": "*.py eol=lf\n*.pyx eol=lf\n*.pxd eol=lf\n*.c eol=lf\n\n*.txt eol=lf\n*.md eol=lf\n*.yml eol=lf\n\n* filter=trimWhitespace\n"
},
{
"path": ".github/workflows/main.yml",
"chars": 1713,
"preview": "---\n# configuration for GitHub Actions\nname: dd tests\non:\n push:\n pull_request:\n schedule:\n - cron: '37 "
},
{
"path": ".github/workflows/setup_build_env.sh",
"chars": 1000,
"preview": "#!/usr/bin/env bash\n\n\n# Prepare environment for building `dd`.\n\n\nset -x\nset -e\nsudo apt install \\\n graphviz\ndot -V\npi"
},
{
"path": ".gitignore",
"chars": 383,
"preview": ".coverage\n.cache\n.mypy_cache/\n.pytype/\n.pytest_cache/\ntodo.txt\ndd/_version.py\ncython_debug/*\ncudd*/*\nextern/\ndd/CUDD_LIC"
},
{
"path": "AUTHORS",
"chars": 112,
"preview": "People that have authored commits to `dd`.\n\nIoannis Filippidis\nSofie Haesaert\nScott C. Livingston\nMario Wenzel\n\n"
},
{
"path": "CHANGES.md",
"chars": 13319,
"preview": "# dd changelog\n\n\n## 0.6.1\n\n- DEP: rm module `dd._compat`\n\n\n## 0.6.0\n\n- REL: require Python >= 3.11\n- REL: require `cytho"
},
{
"path": "LICENSE",
"chars": 1540,
"preview": "Copyright (c) 2014-2022 by California Institute of Technology\nAll rights reserved.\n\nRedistribution and use in source and"
},
{
"path": "MANIFEST.in",
"chars": 646,
"preview": "include README.md\ninclude LICENSE\ninclude doc.md\ninclude CHANGES.md\ninclude AUTHORS\ninclude requirements.txt\ninclude dow"
},
{
"path": "Makefile",
"chars": 4026,
"preview": "# build, install, test, release `dd`\n\n\nSHELL := bash\nwheel_file := $(wildcard dist/*.whl)\n\n.PHONY: cudd install test\n.PH"
},
{
"path": "README.md",
"chars": 10227,
"preview": "[![Build Status][build_img]][ci]\n\n\nAbout\n=====\n\nA pure-Python (Python >= 3.11) package for manipulating:\n\n- [Binary deci"
},
{
"path": "dd/__init__.py",
"chars": 271,
"preview": "\"\"\"Package of algorithms based on decision diagrams.\"\"\"\ntry:\n import dd._version as _version\n __version__ = _versi"
},
{
"path": "dd/_abc.py",
"chars": 18301,
"preview": "\"\"\"Interface specification.\n\nThis specification is implemented by the modules:\n\n- `dd.autoref`\n- `dd.cudd`\n- `dd.cudd_zd"
},
{
"path": "dd/_copy.py",
"chars": 14230,
"preview": "\"\"\"Utilities for transferring BDDs.\"\"\"\n# Copyright 2016-2018 by California Institute of Technology\n# All rights reserved"
},
{
"path": "dd/_parser.py",
"chars": 14342,
"preview": "\"\"\"Construct BDD nodes from quantified Boolean formulae.\"\"\"\n# Copyright 2015 by California Institute of Technology\n# All"
},
{
"path": "dd/_utils.py",
"chars": 11566,
"preview": "\"\"\"Convenience functions.\"\"\"\n# Copyright 2017-2018 by California Institute of Technology\n# All rights reserved. Licensed"
},
{
"path": "dd/autoref.py",
"chars": 22665,
"preview": "\"\"\"Wraps `dd.bdd` to automate reference counting.\n\nFor function docstrings, refer to `dd.bdd`.\n\"\"\"\n# Copyright 2015 by C"
},
{
"path": "dd/bdd.py",
"chars": 90570,
"preview": "\"\"\"Ordered binary decision diagrams.\n\n\nReferences\n==========\n\nRandal E. Bryant\n \"Graph-based algorithms for Boolean f"
},
{
"path": "dd/buddy.pyx",
"chars": 10831,
"preview": "# cython: profile=True\n\"\"\"Cython interface to BuDDy.\n\n\nReference\n=========\n Jorn Lind-Nielsen\n \"BuDDy: Binary Deci"
},
{
"path": "dd/buddy_.pxd",
"chars": 3419,
"preview": "# cython: profile=True\n\"\"\"Cython extern declarations from BuDDy.\n\n\nReference\n=========\n Jorn Lind-Nielsen\n \"BuDDy:"
},
{
"path": "dd/c_sylvan.pxd",
"chars": 2844,
"preview": "\"\"\"Cython extern declarations from Sylvan.\n\n\nReference\n=========\n Tom van Dijk, Alfons Laarman, Jaco van de Pol\n \""
},
{
"path": "dd/cudd.pyx",
"chars": 94981,
"preview": "\"\"\"Cython interface to CUDD.\n\nVariable `__version__` equals CUDD's version string.\n\n\nReference\n=========\n Fabio Somen"
},
{
"path": "dd/cudd_zdd.pyx",
"chars": 131523,
"preview": "\"\"\"Cython interface to ZDD implementation in CUDD.\n\nZDDs are represented without complemented edges in CUDD (unlike BDDs"
},
{
"path": "dd/dddmp.py",
"chars": 16654,
"preview": "\"\"\"Parser for DDDMP file format.\n\nCUDD exports Binary Decision Diagrams (BDD) in DDDMP.\nFor more details on the Decision"
},
{
"path": "dd/mdd.py",
"chars": 18807,
"preview": "\"\"\"Ordered multi-valued decision diagrams.\n\n\nReferences\n==========\n\nArvind Srinivasan, Timothy Kam, Sharad Malik, Robert"
},
{
"path": "dd/py.typed",
"chars": 0,
"preview": ""
},
{
"path": "dd/sylvan.pyx",
"chars": 28198,
"preview": "\"\"\"Cython interface to Sylvan.\n\n\nReference\n=========\n Tom van Dijk, Alfons Laarman, Jaco van de Pol\n \"Multi-Core B"
},
{
"path": "doc.md",
"chars": 53736,
"preview": "# `dd` documentation\n\n\n# Table of Contents\n\n- [Design principles](#design-principles)\n- [Create and plot a binary decisi"
},
{
"path": "download.py",
"chars": 13191,
"preview": "\"\"\"Retrieve and build dependencies of C extensions.\"\"\"\nimport argparse as _arg\nimport collections.abc as _abc\nimport cty"
},
{
"path": "examples/README.md",
"chars": 1574,
"preview": "The examples are:\n\n- `variable_substitution.py`: rename variables that a BDD depends on\n\n- `boolean_satisfiability.py`: "
},
{
"path": "examples/_test_examples.py",
"chars": 596,
"preview": "\"\"\"Run each example module.\"\"\"\nimport os\nimport subprocess as _sbp\nimport sys\n\n\ndef _main(\n ) -> None:\n \"\"\"Run"
},
{
"path": "examples/bdd_traversal.py",
"chars": 1504,
"preview": "\"\"\"Traversing binary decision diagrams.\n\nRead [1, Section 2.2].\n\n\nReference\n=========\n\n[1] Steven M. LaValle\n Plannin"
},
{
"path": "examples/boolean_satisfiability.py",
"chars": 1042,
"preview": "\"\"\"Is a given Boolean formula satisfiable?\"\"\"\nimport dd\n\n\ndef example():\n \"\"\"Demonstrate usage.\"\"\"\n # a formula\n "
},
{
"path": "examples/cudd_configure_reordering.py",
"chars": 699,
"preview": "\"\"\"How to configure reordering in CUDD via `dd.cudd`.\"\"\"\nimport pprint\n\nimport dd.cudd as _bdd\n\n\nbdd = _bdd.BDD()\nvrs = "
},
{
"path": "examples/cudd_memory_limits.py",
"chars": 333,
"preview": "\"\"\"How to place an upper bound on the memory CUDD consumes.\"\"\"\nimport dd.cudd as _bdd\n\n\ndef configure():\n GiB = 2**30"
},
{
"path": "examples/cudd_statistics.py",
"chars": 599,
"preview": "\"\"\"How to print readable statistics.\"\"\"\nimport pprint\n\nimport dd.cudd\n\n\ndef print_statistics():\n b = dd.cudd.BDD()\n "
},
{
"path": "examples/cudd_zdd.py",
"chars": 336,
"preview": "\"\"\"How to use ZDDs with CUDD.\"\"\"\nimport dd.cudd_zdd as _zdd\n\n\ndef zdd_example():\n zdd = _zdd.ZDD()\n zdd.declare('x"
},
{
"path": "examples/good_vs_bad_variable_order.py",
"chars": 1326,
"preview": "\"\"\"How the variable order in a BDD affects the number of nodes.\n\n\nReference\n=========\n\n[1] Randal Bryant\n \"On the com"
},
{
"path": "examples/install_dd_buddy.sh",
"chars": 1363,
"preview": "#!/usr/bin/env bash\n#\n# Install `dd`, including the module\n# `dd.buddy`, which is written in Cython.\n#\n# To run this scr"
},
{
"path": "examples/install_dd_cudd.sh",
"chars": 1061,
"preview": "#!/usr/bin/env bash\n#\n# Install `dd`, including the modules\n# `dd.cudd` and `dd.cudd_zdd`\n# (which are written in Cython"
},
{
"path": "examples/install_dd_sylvan.sh",
"chars": 1420,
"preview": "#!/usr/bin/env bash\n#\n# Install `dd`, including\n# the module `dd.sylvan`.\n# (which is written in Cython).\n#\n# To run thi"
},
{
"path": "examples/json_example.py",
"chars": 672,
"preview": "\"\"\"How to write BDDs to JSON files, and load them.\"\"\"\nimport dd.cudd as _bdd\n\n\ndef json_example():\n \"\"\"Entry point.\"\""
},
{
"path": "examples/json_load.py",
"chars": 2061,
"preview": "\"\"\"Loading BDD from JSON using the `json` module.\n\nThis example shows how to load from JSON,\nand convert to a `networkx`"
},
{
"path": "examples/np.py",
"chars": 1527,
"preview": "\"\"\"How the variable order in a BDD affects the number of nodes.\n\n\nReference\n=========\n\n[1] Randal Bryant\n \"On the com"
},
{
"path": "examples/queens.py",
"chars": 4303,
"preview": "\"\"\"N-Queens problem using one-hot encoding.\n\n\nReference\n=========\n\n[1] Henrik R. Andersen\n \"An introduction to binary"
},
{
"path": "examples/reachability.py",
"chars": 1678,
"preview": "\"\"\"Reachability computation over a graph.\n\n\nThis example is discussed in the documentation [1].\nPropositional variables "
},
{
"path": "examples/reordering.py",
"chars": 3328,
"preview": "\"\"\"Activate dynamic reordering for the Python implementation `dd.autoref`.\"\"\"\nimport logging\n\nimport dd.autoref as _bdd\n"
},
{
"path": "examples/transfer_bdd.py",
"chars": 1050,
"preview": "\"\"\"How to copy a BDD from one manager to another.\"\"\"\nimport dd.autoref as _bdd\n\n\ndef transfer():\n \"\"\"Copy a BDD from "
},
{
"path": "examples/variable_substitution.py",
"chars": 757,
"preview": "\"\"\"Renaming variables.\"\"\"\nimport dd.autoref as _bdd\n# import dd.cudd as _bdd # uncomment to use CUDD\n\n\ndef variable_sub"
},
{
"path": "examples/what_is_a_bdd.py",
"chars": 3171,
"preview": "\"\"\"How BDDs are implemented.\n\nThis module describes the main characteristics of\nthe data structure used in the module `d"
},
{
"path": "setup.py",
"chars": 6741,
"preview": "\"\"\"Installation script.\"\"\"\nimport argparse as _arg\nimport logging\nimport os\nimport sys\n\nimport setuptools\n\nimport downlo"
},
{
"path": "tests/.coveragerc",
"chars": 60,
"preview": "# config for `coverage.py`\n\n[run]\nplugins = Cython.Coverage\n"
},
{
"path": "tests/README.md",
"chars": 229,
"preview": "Besides tests for the package `dd`, this directory contains the script\n`inspect_cython_signatures.py`, which checks the "
},
{
"path": "tests/autoref_test.py",
"chars": 5091,
"preview": "\"\"\"Tests of the module `dd.autoref`.\"\"\"\n# This file is released in the public domain.\n#\nimport logging\n\nimport dd.autore"
},
{
"path": "tests/bdd_test.py",
"chars": 41141,
"preview": "\"\"\"Tests of the module `dd.bdd`.\"\"\"\n# This file is released in the public domain.\n#\nimport logging\nimport os\n\nimport dd."
},
{
"path": "tests/common.py",
"chars": 25540,
"preview": "\"\"\"Common tests for `autoref`, `cudd`, `cudd_zdd`.\"\"\"\n# This file is released in the public domain.\n#\nimport os\n\nimport "
},
{
"path": "tests/common_bdd.py",
"chars": 5352,
"preview": "\"\"\"Common tests for `autoref`, `cudd`.\"\"\"\n# This file is released in the public domain.\n#\nimport os\n\nimport pytest\n\n\ncla"
},
{
"path": "tests/common_cudd.py",
"chars": 10052,
"preview": "\"\"\"Common tests for `cudd`, `cudd_zdd`.\"\"\"\n# This file is released in the public domain.\n#\nimport pytest\n\n\nclass Tests:\n"
},
{
"path": "tests/copy_test.py",
"chars": 2949,
"preview": "\"\"\"Tests of the module `dd._copy`.\"\"\"\n# This file is released in the public domain.\n#\nimport dd.autoref as _autoref\nimpo"
},
{
"path": "tests/cudd_test.py",
"chars": 7877,
"preview": "\"\"\"Tests of the module `dd.cudd`.\"\"\"\n# This file is released in the public domain.\n#\nimport logging\n\nimport dd.cudd as _"
},
{
"path": "tests/cudd_zdd_test.py",
"chars": 14613,
"preview": "\"\"\"Tests of the module `dd.cudd_zdd`.\"\"\"\n# This file is released in the public domain.\n#\nimport inspect\nimport os\nimport"
},
{
"path": "tests/dddmp_test.py",
"chars": 5069,
"preview": "\"\"\"Tests of the module `dd.dddmp`.\"\"\"\nimport logging\nimport os\n\nimport dd.dddmp as _dddmp\nimport pytest\n\n\nlogger = loggi"
},
{
"path": "tests/inspect_cython_signatures.py",
"chars": 3543,
"preview": "\"\"\"Compare the signatures of methods in a Cython `cdef` class to ABC.\n\nA `cdef` class cannot inherit from an ABC (or a P"
},
{
"path": "tests/iterative_recursive_flattener.py",
"chars": 5667,
"preview": "\"\"\"Mapping trees to BDDs by iteration, and by recursion.\n\nThe iterative traversal avoids exceeding:\n- CPython's call-sta"
},
{
"path": "tests/mdd_test.py",
"chars": 1510,
"preview": "\"\"\"Tests of the module `dd.mdd`.\"\"\"\nimport logging\nimport os\n\nimport dd.bdd\nimport dd.mdd\n\n\nlogger = logging.getLogger(_"
},
{
"path": "tests/parser_test.py",
"chars": 18934,
"preview": "\"\"\"Tests of module `dd._parser`.\"\"\"\n# This file is released in the public domain.\n#\nimport collections.abc as _abc\nimpor"
},
{
"path": "tests/pytest.ini",
"chars": 149,
"preview": "# configuration file for package `pytest`\n[pytest]\nfilterwarnings = error\npython_files = *_test.py\npython_classes = *Tes"
},
{
"path": "tests/regressions_test.py",
"chars": 536,
"preview": "import dd.cudd as _cudd\n\n\ndef test_reordering_setting_restore():\n # Original report at https://github.com/tulip-contr"
},
{
"path": "tests/sylvan_test.py",
"chars": 6599,
"preview": "\"\"\"Tests of the module `dd.sylvan`.\"\"\"\nimport logging\nimport dd.sylvan as _sylvan\n\n\nlogging.getLogger('astutils').setLev"
}
]
About this extraction
This page contains the full source code of the tulip-control/dd GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 68 files (753.2 KB), approximately 206.0k tokens, and a symbol index with 795 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.