Skip to main content

Pfaffian chain order and EML routing depth for symbolic expressions.

Project description

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/almaguer1986/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_time per proxy: simplify / factor / cse / lambdify predicted ms with 95% CI
  • predict_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

License

PROPRIETARY-PRE-RELEASE. See LICENSE.

Citation

Citation form will be locked at public release.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

eml_cost-0.12.0.tar.gz (169.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

eml_cost-0.12.0-py3-none-any.whl (70.8 kB view details)

Uploaded Python 3

File details

Details for the file eml_cost-0.12.0.tar.gz.

File metadata

  • Download URL: eml_cost-0.12.0.tar.gz
  • Upload date:
  • Size: 169.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for eml_cost-0.12.0.tar.gz
Algorithm Hash digest
SHA256 c2d936209c3fb43fbd336641bc3b330c2a5018b5b04a78077c3d671401c35dff
MD5 210a1a63d66ec88895a088e3d8a0e728
BLAKE2b-256 6793162675b963be064d8ebb45c1d37ea590f623ed1430d9f1e6ae92d03ed04f

See more details on using hashes here.

File details

Details for the file eml_cost-0.12.0-py3-none-any.whl.

File metadata

  • Download URL: eml_cost-0.12.0-py3-none-any.whl
  • Upload date:
  • Size: 70.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for eml_cost-0.12.0-py3-none-any.whl
Algorithm Hash digest
SHA256 691cd16cabaef36461555a0bd594cf938a45bf9e62527c63c384f3439adfed5f
MD5 3fe3204f7dba7d493adf24e4990134d2
BLAKE2b-256 4b2186c3f6eac290715d26aadd8ed9709e1458055e99287085cc5ae002b1999d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page