A lattice polynomial is a term built from variables and constants using only the lattice operations meet and join. Over a bounded distributive lattice \((K, \vee, \wedge, \bot, \top)\) every such term has a normal form, and the functions they denote are exactly the monotone functions generated by the lattice operations. pollywog represents these functions canonically as multi-terminal binary decision diagrams (MTBDDs), so equal polynomials are equal objects, and provides the operations you would expect on them: meet, join, dual (on De Morgan algebras), and substitution.
The diagrams are CUDD ADDs, so the variable order can be reordered dynamically or imposed by hand. There is a C++ core with Python bindings on top of it.
Lattices
Leaves are double. A lattice is a small trait supplying bottom, top, meet, and join; a De Morgan algebra adds dual. Two are built in:
MaxMinExtendedReal - the extended reals with \(\wedge = \min\), \(\vee = \max\), \(\bot = -\infty\), \(\top = +\infty\), and \(\neg a = -a\).
MaxMinUnitInterval - the unit interval \([0, 1]\) with \(\wedge = \min\), \(\vee = \max\), \(\bot = 0\), \(\top = 1\), and the fuzzy negation \(\neg a = 1 - a\), giving a Kleene algebra.
Each lattice carries its polynomial type as a nested Polynomial class.
Example
>>> from pollywog import Context, Var, MaxMinExtendedReal
>>> ctx = Context[str]()
>>> K = ctx.over(MaxMinExtendedReal)
>>> x, y = K.var("x"), K.var("y")
>>> # (x AND y) OR 3.0
>>> f = x.meet(y).join(K.terminal(3.0))
>>> ctx.support(f) == {"x", "y"}
True
>>> bottom = K.lattice.bottom()
>>> f.let({Var(0): bottom, Var(1): bottom}).leaf_value()
3.0
Context maps arbitrary hashables to the integer indices CUDD keys its tables on, so you can name variables with states, atomic predicates or formula subterms. Substitution is keyed by Var, not by the bare index.
ctx.over fixes the lattice and the manager, which are constant across any stretch of code working in one lattice, so the constructors take only what varies. The context underneath stays lattice-agnostic: views over different lattices share its variable naming, so their polynomials share a support and a variable order.
Also in the Box
Manager - owns the unique table, operation cache and variable order. There is no global runtime to start; construct one and build diagrams from it.
BooleanFunction - BDDs, for the ordinary Boolean reasoning the polynomials omit: complementation, quantifiers, cube and prime-implicant enumeration.
AlgebraicFunction - ADDs under CUDD’s own arithmetic, with conversions to and from BDDs.
Why not dd?
If what you want is BDDs in Python, use dd. It wraps CUDD, Sylvan and BuDDy, it has ZDDs, an expression parser and quantifiers, and it has a pure-Python backend that installs anywhere. pollywog is not trying to replace it.
The difference is at the leaves. A dd function is Boolean-valued, \(f : \mathbb{B}^n \to \mathbb{B}\), and its API is the Boolean connectives. A pollywog polynomial is lattice-valued, \(f : K^n \to K\), and its API is the lattice: meet, join, dual, substitution. CUDD has ADDs underneath, but dd does not expose them, so there is no multi-terminal diagram in it to build this on.
A finite lattice can of course be encoded as a family of Boolean functions, one BDD per cut \(\{ \mathbf{a} : f(\mathbf{a}) \ge c \}\). What that costs:
One object instead of \(k\). Equality, hashing, node counts and substitution apply to the function itself, and sharing between the cuts is CUDD’s problem rather than yours.
Real leaves. MaxMinExtendedReal has continuum-many values and no finite family of cuts.
The normal form. A node is read as \(f = f_\bot \vee (x \wedge f_\top)\) under the invariant \(f_\bot \le f_\top\), so there is no complement anywhere and substitution is one meet and one join per node. Encoded into Shannon-expanded BDDs, monotonicity is an invariant you maintain by hand.
The Boolean side is still there when you want it: BooleanFunction lives in the same manager and the same variable order as the polynomials, so a skeleton and the polynomial over it share nodes instead of crossing a library boundary.
So: Boolean functions, dd. Functions valued in a lattice — fuzzy truth values, min-max robustness, weights ordered by \(\le\) — pollywog.
Building
The project builds with scikit-build-core and nanobind. With pixi:
pixi install
pixi run python -c "import pollywog"
C++ tests:
ctest --test-dir build
Documentation (needs Doxygen and Sphinx, both in the docs dependency group):
cmake --build build --target docs
The rendered output lands in build/docs/html.
Documentation
docs/ is split by audience. The concept pages — the representation and its normalization invariant, variables and ordering, managers and lifetimes — carry the long-form prose. docs/api/ documents the end-user surface of each language binding, deliberately selectively.
Internals are documented in the source rather than in the manual, so read the headers for the custom CUDD operators and the reference-counting.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pollywog-0.1.1-cp312-abi3-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: pollywog-0.1.1-cp312-abi3-musllinux_1_2_x86_64.whl
- Upload date:
- Size: 976.9 kB
- Tags: CPython 3.12+, musllinux: musl 1.2+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"42","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f858d538a0a05c638805f5507a5fbffb1b920f728a2ce002ba2d2e8bacf15b23
|
|
| MD5 |
ef16537c2607d34a320ddc5589be5394
|
|
| BLAKE2b-256 |
e97102e2cbacbc72357acdadc046370764cee9b6a7b7992e605315752616ac82
|
File details
Details for the file pollywog-0.1.1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: pollywog-0.1.1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 496.7 kB
- Tags: CPython 3.12+, manylinux: glibc 2.27+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"42","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
766b7ce87c87e49e6c886e67638da3959f72f2c9ff9a833882c74ea716d1d3d2
|
|
| MD5 |
d80c839887df482aedd52d9a8562271b
|
|
| BLAKE2b-256 |
a9adb4b0c7ffc2a5367d3ca162ca9de7fd7c15415af72de952ac59b7338a47d7
|