eml-cost
Stable beta. Patent pending. Source-available; see LICENSE.
Pfaffian chain order and EML routing depth for symbolic expressions — a programmatic complexity measure on SymPy expression trees.
Installation
pip install eml-cost
For local development:
git clone https://github.com/agent-maestro/eml-cost
cd eml-cost
pip install -e ".[dev]"
pytest
Quick start
Three things you can do in under 10 lines each.
1. Get a complexity profile for any expression
from eml_cost import analyze
result = analyze("exp(exp(x)) + sin(x**2)")
print(result.pfaffian_r, result.max_path_r, result.predicted_depth)
# 5 5 7
2. Plug into SymPy's simplify as a cost function
import sympy as sp
from eml_cost import measure
x = sp.Symbol("x", real=True)
sp.simplify(sp.cos(x)**2 + sp.sin(x)**2, measure=measure)
# 1
3. Detect Pfaffian-but-not-EML expressions (Bessel, Airy, Lambert W)
import sympy as sp
from eml_cost import is_pfaffian_not_eml
is_pfaffian_not_eml(sp.besselj(0, sp.Symbol("x"))) # True
is_pfaffian_not_eml(sp.exp(sp.Symbol("x"))) # False
4. Canonicalize before profiling (eliminate form-fragility)
50% of textbook expressions yield different cost classes when written in
algebraically equivalent forms. canonicalize() is a curated, content-
preserving rewrite-rule sequence that drops drift to ~35% in our audit.
import sympy as sp
from eml_cost import PfaffianProfile
x = sp.Symbol("x")
forms = [
1 / (1 + sp.exp(-x)),
sp.exp(x) / (sp.exp(x) + 1),
1 - 1 / (1 + sp.exp(x)),
]
for f in forms:
p = PfaffianProfile.from_expression(f) # canonicalize=True is default
print(f"{f} -> {p.cost_class}")
# All three collapse to the same cost class.
5. Compare two expressions with a real distance metric
from eml_cost import PfaffianProfile
a = PfaffianProfile.from_expression("exp(x)")
b = PfaffianProfile.from_expression("sin(x)")
a.distance(b) # weighted Euclidean in (r, d, w, c) space
a.compare(b) # per-axis deltas + same_class flag
a.is_elementary() # True (not Pfaffian-not-EML)
The metric satisfies identity, symmetry, and the triangle inequality
(verified in tests/test_profile_metric.py). Default weights:
r=4, d=1, w=2, c=1 — chain order dominates.
6. Run on the bundled 50-expression cross-domain corpus
import csv
from importlib.resources import files
from eml_cost import PfaffianProfile
corpus_path = files("eml_cost").joinpath("data/demo_corpus.csv")
with open(corpus_path) as f:
rows = list(csv.DictReader(f))
profiles = [PfaffianProfile.from_expression(r["sympy_expr"]) for r in rows]
# 50 expressions, 9 domains (polynomial, exp_log, trig, pfaffian_not_eml,
# ml_activation, physics, biology, engineering, random_null), all with
# citations.
For an interactive walk-through with plots, see
notebooks/quickstart.ipynb.
Command-line interface (0.7.1+)
eml-cost ships with a console command that exposes the prediction
trilogy at the shell:
# Pretty report for one or more expressions
eml-cost report "exp(exp(x))" "sin(x**2)"
# JSON for machine consumers
eml-cost report --json "exp(x) + log(x+1)"
# Read one expression per line (use - for stdin; # comments allowed)
eml-cost report --file expressions.txt
# Pre-commit gate: exits non-zero if any budget is exceeded
eml-cost check "exp(exp(x)) + sin(x**2)" \
--max-simplify-ms 200 \
--max-digits-lost 3
What report shows for each expression:
- Pfaffian profile:
pfaffian_r,max_path_r,eml_depth,predicted_depth, PNE flag estimate_timeper proxy: simplify / factor / cse / lambdify predicted ms with 95% CIpredict_precision_loss: predicted relerr, predicted decimal digits lost, 95% CI
Exit codes for check:
| code | meaning |
|---|---|
| 0 | all checks passed |
| 1 | at least one threshold violation (check only) |
| 2 | at least one expression failed to parse |
| 64 | usage error (no expressions given) |
Pre-commit hook recipe
Block any commit that introduces a SymPy expression exceeding your
team's compile-time or numerical-precision budgets. Drop a list of
the expressions you care about into a tracked file (one per line,
# comments allowed), then add the hook to your repo's
.pre-commit-config.yaml:
- repo: local
hooks:
- id: eml-cost
name: eml-cost numerical / compile-cost gate
entry: eml-cost check --file
language: system
files: ^expressions\.txt$
args: [--max-simplify-ms, "200", --max-digits-lost, "3"]
expressions.txt contents:
# Each line is one SymPy-parseable expression.
# Blank lines and # comments are skipped.
# This file is the authoritative list of expressions the team has
# committed to keeping under the budget below.
exp(exp(x)) + sin(x**2)
1/(1 + exp(-x))
log(x + sqrt(x**2 + 1))
# Add more as you go. Removing an expression is the only way to
# legitimately drop a budget breach below the threshold.
Both files live alongside the rest of your repo. The hook runs
locally on every commit (and in CI if you use pre-commit run --all-files
in the workflow). A complete working sample lives at
examples/.pre-commit-config.example.yaml.
CI integration (GitHub Actions, etc.)
Same eml-cost check invocation works in any CI:
# .github/workflows/eml-cost.yml
name: eml-cost
on: [push, pull_request]
jobs:
budget:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install eml-cost
- run: eml-cost check --file expressions.txt --max-simplify-ms 200 --max-digits-lost 3
The check is deterministic — same expression file + same eml-cost
version produces the same exit code on every run.
Result shape
from eml_cost import analyze
result = analyze("exp(exp(x)) + sin(x**2)")
result.pfaffian_r # total Pfaffian chain order
result.max_path_r # chain order along the deepest path
result.eml_depth # EML routing tree depth
result.structural_overhead # tree-structural depth
result.corrections # Corrections(c_osc, c_composite, delta_fused)
result.predicted_depth # max_path_r + corrections + structural
result.is_pfaffian_not_eml # True for Bessel, Airy, Lambert W, ...
Drop-in measure for SymPy's simplify:
import sympy as sp
from eml_cost import measure
x = sp.Symbol("x", real=True)
sp.simplify(sp.cos(x)**2 + sp.sin(x)**2, measure=measure)
# 1
Public API
from eml_cost import (
analyze, # main entry point
measure, # SymPy simplify(..., measure=...) helper
AnalyzeResult, # frozen dataclass (result type)
Corrections, # frozen dataclass (correction terms)
pfaffian_r, # total chain order
max_path_r, # path-restricted chain order
eml_depth, # routing tree depth
structural_overhead, # Add/Mul/poly-Pow tree depth
is_pfaffian_not_eml, # True for Bessel/Airy/Lambert W/hyper
PFAFFIAN_NOT_EML_R, # registry: name -> chain order
)
What gets counted
Khovanskii r-counting throughout:
| Operator | Chain contribution |
|---|---|
exp(g) |
1 |
log(g) |
1 |
sin(g), cos(g) (pair) |
2 |
tan(g) |
1 |
tanh, atan, atanh, asinh, acosh |
1 each |
sinh(g), cosh(g) (pair) |
2 |
sqrt(g), Pow(g, non-integer) |
1 |
Pow(g, integer), Add, Mul |
0 |
| Bessel J/Y/I/K, Airy Ai/Bi, Lambert W, hyper | per registry |
max_path_r differs from pfaffian_r only at Add and Mul nodes:
pfaffian_r sums children, max_path_r takes the max. For independent-
variable products like atomic orbital wavefunctions
(R(r) * Y(theta) * Phi(phi)), the path-restricted count is dramatically
smaller than the total — capturing the parallel-composition behavior.
EML routing depth
The eml_depth function models SuperBEST routing:
| Operator | Depth contribution |
|---|---|
exp, log |
1 |
sin, cos |
3 (Euler bypass) |
tan |
4 |
tanh, atan, sinh, cosh |
1 (F-family primitive) |
Pow, Add, Mul |
1 + max over children |
F-family fusion patterns are recognized:
log(c + exp(g))(LEAd / softplus shape) -> depth 1 + depth(g)1/(1 + exp(-g))(sigmoid shape) -> depth 1 + depth(g)
Pfaffian-but-not-EML class
Bessel J/Y/I/K, Hankel, Airy Ai/Bi, hypergeometric, and Lambert W are
Pfaffian (admit polynomial-coefficient ODE chains) but lie outside the
EML-elementary class. They are flagged by is_pfaffian_not_eml(expr)
and contribute their registered chain order under pfaffian_r.
Links
- Project home: monogate.org
- Source: github.com/agent-maestro/eml-cost
- Package: pypi.org/project/eml-cost
License
PROPRIETARY-PRE-RELEASE. See LICENSE.
Citation
Citation form will be locked at public release.
Metadata
Release files for eml-cost 0.25.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| eml_cost-0.25.0.tar.gz | 299.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| eml_cost-0.25.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 470.7 kB
Release files / eml_cost-0.25.0.tar.gz
| Download URL | eml_cost-0.25.0.tar.gz |
|---|---|
| Size | 299.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1ed5945aa56b2fa0109aa9594ded188d6081891f24fb63df9ac4f72edd689280
|
|
BLAKE2b-256 checksum How to use checksums |
ed41293ec90b35ebe821667d3473c3b64812fd59ad7d79d9cdfb4fe0cb442ec9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Release files / eml_cost-0.25.0-py3-none-any.whl
| Download URL | eml_cost-0.25.0-py3-none-any.whl |
|---|---|
| Size | 171.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eeadfd5e12480611cad6fa933f1a84bb66c74ba4d2a01df07f2622ead60647f0
|
|
BLAKE2b-256 checksum How to use checksums |
d7f640bd8ee8b339a6ecf89d403d93bb789fe2d1a5007fdd1a27280637190204
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|