F-family fusion pattern rewriter for symbolic expressions and Python source.
Project description
eml-rewrite
Stable beta. Patent pending. Source-available; see LICENSE.
F-family fusion pattern rewriter for symbolic expressions. Detects
equivalent rewrites that strictly reduce predicted EML cost (per
eml-cost). The library's
headline guarantee: every proposed rewrite clears three independent
gates (cost, domain, numerical equivalence) before it's surfaced to
the caller.
Installation
pip install eml-rewrite
eml-cost is installed as a dependency.
For local development:
git clone https://github.com/almaguer1986/eml-rewrite
cd eml-rewrite
pip install -e ".[dev]"
pytest
Library API
from eml_rewrite import suggest, best, score, Suggestion
import sympy as sp
x = sp.Symbol("x", real=True)
# Find improving rewrites (only those that strictly improve)
sugg = suggest(sp.exp(x) / (1 + sp.exp(x)))
# [Suggestion(pattern_name="sigmoid",
# rewritten=1/(1 + exp(-x)),
# score_before=3, score_after=2, reduction=1,
# domain_required="", domain_verified=True,
# numerically_verified=True)]
# Pick the lowest-score rewrite, or original if no improvement
best(sp.sinh(x) / sp.cosh(x))
# tanh(x)
best(sp.exp(sp.exp(x)))
# exp(exp(x)) <- original; nothing in library matches
Pattern library (9)
exp(x)/(1+exp(x))->1/(1+exp(-x))— sigmoid canonicalizationsinh(x)/cosh(x)->tanh(x)— F-family fusion(exp(x)+exp(-x))/2->cosh(x)— hyperbolic fusion (cosh)(exp(x)-exp(-x))/2->sinh(x)— hyperbolic fusion (sinh)sin(x)^2 + cos(x)^2->1— Pythagorean identitycosh(x)^2 - sinh(x)^2->1— hyperbolic identityexp(log(x)),log(exp(x))->x— inverse pairlog(a/b)->log(a) - log(b)— when score improveslog(x^n)->n*log(x)— when score improves
Real-world examples
Scanning a SciPy-style script
# stats.py
from sympy import Symbol, exp, log
x = Symbol("x", positive=True)
log(x ** 4) # gets flagged: log_pow rewrite available
exp(x) / (1 + exp(x)) # gets flagged: sigmoid canonicalization
$ eml-rewrite scan stats.py
stats.py:3 log(x**4)
-> 4*log(x) (log_pow, -0 cost units)
stats.py:4 exp(x)/(exp(x) + 1)
-> 1/(1 + exp(-x)) (sigmoid, -1 cost units)
Found 2 improving rewrite(s) across 1 file(s).
Fixing a PyTorch-flavored model definition
# model.py — math written by hand for clarity, then auto-canonicalized
from sympy import Symbol, exp, sinh, cosh
x = Symbol("x", real=True, positive=True)
sinh(x) / cosh(x) # → tanh(x)
exp(x) / (1 + exp(x)) # → 1/(1 + exp(-x))
$ eml-rewrite fix model.py
model.py:3 sinh(x)/cosh(x)
Would rewrite to: tanh(x)
model.py:4 exp(x)/(exp(x) + 1)
Would rewrite to: 1/(1 + exp(-x))
Would apply 2 rewrite(s) across 1 file(s).
NOTE: v0.1 prints proposed rewrites only.
Surfacing conditional rewrites for editor / notebook UX
$ eml-rewrite analyze "log(x**4)" --include-conditional
Expression: log(x**4)
pfaffian_r: 1
...
Suggested rewrites (1):
[log_pow | conditional: x > 0] 4*log(x) (-0 cost units)
The conditional: annotation appears when the rewrite's domain
requirement (here, x > 0) cannot be established by SymPy's
assumption system. Without --include-conditional, the rewrite
is silently rejected. With it, the requirement is annotated so the
caller can prompt or confirm.
Command line
eml-rewrite scan FILE [FILE ...] # report rewrites without applying
eml-rewrite fix FILE [FILE ...] # show rewrites that would be applied
eml-rewrite analyze "exp(sin(x))" # full Pfaffian profile + suggestions
Example:
$ eml-rewrite analyze "exp(x)/(1 + exp(x))"
Expression: exp(x)/(exp(x) + 1)
pfaffian_r: 1
max_path_r: 1
eml_depth: 2
structural_overhead: 2
corrections: Corrections(c_osc=0, c_composite=0, delta_fused=0)
predicted_depth: 3
is_pfaffian_not_eml: False
Suggested rewrites (1):
[sigmoid] 1/(1 + exp(-x)) (-1 cost units)
Non-regression: three layers
Every rewrite returned with only_improvements=True is required to
clear three independent gates before being surfaced to the caller.
1. Cost gate
for s in suggest(expr, only_improvements=True):
assert s.score_after < s.score_before
Strict EML cost reduction (per eml-cost's measure) — the
historical headline guarantee. Suggestions whose post-rewrite cost is
not strictly lower are filtered out before return.
2. Domain gate
A subset of the rewrites in the library are valid only on a restricted input domain. Examples:
| Pattern | Identity | Valid when |
|---|---|---|
log(x^n) -> n*log(x) |
real-valued log identity | x > 0 |
log(a/b) -> log(a) - log(b) |
log split | a > 0 and b > 0 |
exp(log(x)) -> x |
inverse pair | x > 0 |
log(exp(x)) -> x |
inverse pair (branch correctness) | x real |
For these patterns, the engine probes SymPy's assumption system
against the relevant subexpression (expr.is_positive,
expr.is_real). A rewrite is accepted only when the assumption
required for soundness is established by the assumption system. So:
import sympy as sp
from eml_rewrite import suggest
# Unconstrained symbol — no positivity assumption available.
u = sp.Symbol("u")
suggest(sp.log(u**2)) # [] (log_pow rejected; would silently
# produce a complex value for u<0)
# Same expression with a positive symbol — accepted.
x = sp.Symbol("x", positive=True)
suggest(sp.log(x**2), only_improvements=False)
# [Suggestion(pattern_name='log_pow', rewritten=2*log(x),
# domain_required='x > 0', domain_verified=True, ...)]
# Conditional mode: surface the rewrite WITH the requirement annotated
# rather than dropping it silently. Useful for editor / notebook UI.
suggest(sp.log(u**2), only_improvements=False, include_conditional=True)
# [Suggestion(pattern_name='log_pow', rewritten=2*log(u),
# domain_required='u > 0', domain_verified=False, ...)]
3. Numerical-equivalence gate
After the structural and domain gates pass, the engine evaluates both
the original and the rewritten expression at sample points drawn from
regions consistent with each free symbol's SymPy assumptions
(positive, negative, near-zero, moderately large). Evaluation runs at
30 decimal digits via SymPy's evalf, with a fast path that
short-circuits when SymPy's simplify proves the identity directly
— this avoids false positives on rewrites that improve numerical
stability (e.g., cosh(x)^2 - sinh(x)^2 -> 1 at large x, where
float-precision evaluation of the original collapses to zero from
catastrophic cancellation).
A rewrite that disagrees on any probed point — including a one-sided
domain error indicating the rewrite changes the domain of definition
— is rejected. Set numerical_verify=False only for benchmarking
the structural+domain layers in isolation.
Verifying equivalence directly
from eml_rewrite import verify_equivalence
import sympy as sp
u = sp.Symbol("u")
verify_equivalence(sp.log(u**2), 2 * sp.log(u))
# False (disagrees on u<0)
verify_equivalence(sp.sin(u)**2 + sp.cos(u)**2, sp.S.One)
# True (universal identity)
Links
- Project home: monogate.org
- Source: github.com/almaguer1986/eml-rewrite
- Package: pypi.org/project/eml-rewrite
- Companion: eml-cost (required dependency)
License
PROPRIETARY-PRE-RELEASE. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
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 eml_rewrite-0.5.0.tar.gz.
File metadata
- Download URL: eml_rewrite-0.5.0.tar.gz
- Upload date:
- Size: 45.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
951c6f01bf17e842186637de014cd7374769f2b17218df835d7ef1f8c0370f9f
|
|
| MD5 |
7821852274925100068506a643a89aaf
|
|
| BLAKE2b-256 |
9bd81dba6b02204ed835619009fe21dffcc941cf803da00b563e1e8afea3fc46
|
File details
Details for the file eml_rewrite-0.5.0-py3-none-any.whl.
File metadata
- Download URL: eml_rewrite-0.5.0-py3-none-any.whl
- Upload date:
- Size: 32.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
85c78775696a3ac9b2dadcfe2806be628d51a438ab91594046da46f5ae5a177d
|
|
| MD5 |
959aa6ff4128671150670bdb8bd64e18
|
|
| BLAKE2b-256 |
161c05d63750e3639a97bb25e042083d2b87d10338cfed3f53638c53e0b54314
|