Skip to main content

emltree

Compile elementary-function formulas into pure EML (Exp-Minus-Log) form, where the only operator is

eml(x, y) = exp(x) − ln(y)

and the only constant is 1.

Paper

This project is an implementation of ideas introduced in:

Andrzej Odrzywolek. All elementary functions from a single operator. arXiv:2603.21852 (2026). https://arxiv.org/abs/2603.21852

The paper proves that the single binary operator eml(x, y) = exp(x) − ln(y) together with the constant 1 generates the entire scientific-calculator basis (arithmetic, roots, logs, all trig and hyperbolic functions, the constants e, π, i). Every elementary expression becomes a binary tree whose only internal node is eml. This package is the inverse direction: it takes an ordinary formula and compiles it into that tree. A sibling Rust crate, OxiEML, covers the Rust ecosystem; a zero-dependency JS/TypeScript port lives in js/ (npm: emltree); this package is the Python side.

Quick start

uv venv --python 3.11
uv pip install -e ".[dev]"

# Compile a formula
uv run emltree "sin(x) + exp(y)" -v x -v y

# ASCII tree
uv run emltree "log(x)" -v x -f tree

# Numerical sanity check
uv run emltree "sin(x)**2 + cos(x)**2" -v x=0.9 --verify

Library use

from emltree import compile_formula, evaluate

tree = compile_formula("sqrt(x**2 + y**2)", variables=["x", "y"])
print(tree.to_nested())
print(evaluate(tree, {"x": 3.0, "y": 4.0}))   # ≈ 5+0j

The returned tree is an immutable ADT (One / Var / Eml) — walk it, hash it, render as RPN (exp(x) is x 1 E), or ship it into an FPGA/analog circuit as the paper suggests.

Evaluation is vectorised: pass numpy arrays as bindings and the tree evaluates elementwise.

import numpy as np
xs = np.linspace(-2, 2, 1000)
evaluate(tree, {"x": xs, "y": xs})   # array of 1000 values

Why the trees are large

This compiler is compositional, not optimal. Every primitive bottoms out in exp / ln / sub, so sin(x) produces a tree with hundreds of nodes. The paper's direct-search results (Table 4) are vastly shorter — integrating that search is one of the open directions below.

Numerical caveats

  • Branch cuts: outside their real domains (asin(2), acosh(-2), log of negatives, …) results flow through complex branch cuts and may land on a non-principal branch — or, where float fuzz compounds, off-sheet entirely (paper §4.1). On the usual real domains everything matches sympy to ~1e-7.
  • Addition overflow: add_'s expansion applies exp() to its second operand, so adding values past ~709 overflows float64. Integer/decimal constants avoid this internally (binary decomposition with a multiplicative odd step), but x + y with huge y is an inherent ceiling of the encoding.

Tests

uv run pytest          # Python
cd js && node --test   # JS port

vectors.json holds cross-language cases — formula, bindings, and an expected value taken from Python's math module, so it is independent of both implementations. tests/test_vectors.py and js/vectors.test.js each run every case. A change that makes one port disagree with the other fails there, rather than waiting to be spotted during the next hand-port (which is how the 0.1.1 atan sign bug surfaced). New primitives should land with a vector.

Contributing

Contributions are very welcome — this is an early-stage package and there's plenty of room to improve. A few directions that would make a great first PR:

  • Shorter trees — hand-curated or searched EML identities to replace the naive compositional expansions (see paper Table 4).
  • More primitivesabs, sign, floor, ceil, erf, etc. (many require tricks; see the paper's supplementary).
  • Better output — LaTeX, GraphViz / Mermaid, Jupyter rich repr.
  • Torch evaluation — numpy bindings already vectorise; a torch evaluator would make EML trees differentiable end-to-end.
  • Optional Rust backend — PyO3 bindings to OxiEML for expensive search and symbolic regression.
  • Docs & examples — walk-throughs of the paper's identities, notebooks showing symbolic-regression use cases.

Please open an issue to discuss before sending a large PR. Bug reports, documentation fixes, and additional test cases are also very welcome — no contribution is too small.

Development

uv venv --python 3.11
uv pip install -e ".[dev]"
uv run pytest

License

MIT — see LICENSE.

Citation

If you use emltree in academic work, please cite both the paper and the software:

@article{odrzywolek2026eml,
  title   = {All elementary functions from a single operator},
  author  = {Odrzywolek, Andrzej},
  journal = {arXiv preprint arXiv:2603.21852},
  year    = {2026}
}

@software{hsiao2026emltree,
  author  = {Hsiao, Wei-Chien},
  title   = {emltree: a Python compiler from elementary formulas to EML trees},
  year    = {2026},
  url     = {https://github.com/gba3124/emltree},
  version = {0.1.2}
}

Download files

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

Source Distribution

emltree-0.1.3.tar.gz (41.4 kB view details)

Uploaded Source

Built Distribution

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

emltree-0.1.3-py3-none-any.whl (13.3 kB view details)

Uploaded Python 3

File details

Details for the file emltree-0.1.3.tar.gz.

File metadata

  • Download URL: emltree-0.1.3.tar.gz
  • Upload date:
  • Size: 41.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.17

File hashes

Hashes for emltree-0.1.3.tar.gz
Algorithm Hash digest
SHA256 5fd4c8588467f6f31c4261228acb8af81fc7125d0497ba38ce990fb4c0374812
MD5 77feef7277e60d423a6db7b832099ac4
BLAKE2b-256 2520ffabb83ace6035c523ca1b0232116e400f828bb274667305de91a7903f16

See more details on using hashes here.

File details

Details for the file emltree-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: emltree-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 13.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.17

File hashes

Hashes for emltree-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 f314f78191da6f0ef6d4afab182fcdb91b2548a98a9af7822827fa6c6388ec92
MD5 9cf99a7d172a75602fafbb87721f9caf
BLAKE2b-256 ba18b057552dd21b499c0dfb62bb2246e60d16d0ebb381bc4a58932bd3cb3c18

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page