Skip to main content

All elementary functions from a single operator: eml(x, y) = exp(x) − ln(y)

Project description

exp-minus-log — all elementary functions from a single operator

pip install exp-minus-log
from eml import eml, parse_rpn, evalf

A Python implementation of the EML (Exp-Minus-Log) operator from

A. Odrzywołek, All elementary functions from a single operator, arXiv:2603.21852, April 2026.

The paper shows that the binary operator

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

paired with the constant 1, suffices to construct every primitive of a scientific calculator: integers and fractions, the constants e, π, i, the four arithmetic operations, the trig / inverse-trig / hyperbolic / inverse-hyperbolic family, exponentiation and logarithms to arbitrary base, , sigmoid, and so on.

This package gives that operator the full Python toolchain: an expression-tree compiler, a single-instruction stack machine, a sympy bridge, gradient-based symbolic regression, exhaustive search for shortest chains, mpmath verification at arbitrary precision, and a matplotlib renderer that draws each chain as the EML circuit symbol from the paper.

Quickstart

from eml import eml, parse_rpn

# Two canonical identities:
eml(1, 1)          # 2.718281828459045  (the constant e)
parse_rpn("11xE1EE")(x=7.5)
# 2.0149030205422647  (== math.log(7.5); the ln chain from Eq. (5))

Every Table-1 primitive is one call away:

from eml.library import build_unary, build_constant
build_unary("sin",  "x")(x=1.0)        # sin(1)  via EML
build_constant("pi")()                  # π       via EML  (K=233)

Verify the whole library at 40-digit mpmath precision:

python examples/07_verify_library.py
# ...
# 41/41 passed

Modules

module what it does
eml.core scalar / NumPy ufunc / mpmath implementations of eml, edl, −eml
eml.tree AST for the grammar S → 1 | eml(S, S), RPN round-trip, sympy bridge
eml.library every primitive of Table 1 expressed as a concrete EML chain (verified)
eml.sympy_eml a SymPy Function EmlFn, with auto-eval, fdiff, evalf, rewrite
eml.vm single-instruction stack VM (compile / disassemble / trace / bytestream)
eml.search brute-force shortest-EML search (the Direct search column of Table 4)
eml.regression the level-n master formula (5·2ⁿ − 6 simplex parameters), Adam, snap-to-vertex
eml.verify Schanuel-witness verification (γ, A) at arbitrary mpmath precision
eml.viz ASCII tree + matplotlib EML-circuit renderer (Fig. 2)
eml.cousins EDL (exp/ln, paired with e) and −eml(y,x) (ln−exp, paired with −∞)

Examples

PYTHONPATH=. python examples/01_canonical_identities.py   # ln, exp, e, 0
PYTHONPATH=. python examples/02_compile_function.py       # build any primitive
PYTHONPATH=. python examples/03_visualise.py              # render Fig.-2-style trees
PYTHONPATH=. python examples/04_vm_bytecode.py            # run on the EML stack VM
PYTHONPATH=. python examples/05_search.py                 # find shortest chains
PYTHONPATH=. python examples/06_symbolic_regression.py    # recover exp(x) from data
PYTHONPATH=. python examples/07_verify_library.py         # verify all 41 primitives
PYTHONPATH=. python examples/08_sympy_calculus.py         # symbolic diff/simplify
PYTHONPATH=. python examples/09_cousins.py                # EDL and −EML in action

Tests

PYTHONPATH=. python -m pytest tests/
# 77 passed

Notes on numerical edge cases

EML chains intentionally route through ln(0) = −∞ and exp(−∞) = 0 when constructing the constant 0, the negation function −x, and a few of their dependents (cf. Table 4 column "without extended reals"). NumPy honours this via signed zeros and infinities, so the chains "just work" in IEEE-754; the core.eml implementation suppresses the benign divide-by-zero RuntimeWarning that the principal log(0) raises.

The principal-branch convention means the chain that the paper writes for i lands on −i — see §4.1 ("manually correct i sign"). The helper eml.core.fix_i_sign performs that one-line correction.

Why complex numbers?

The paper requires it (page 5):

"Computations must be done in the complex domain, e.g. generating constants like i and π requires evaluating ln(−1), so eml(x, y) internally operates over ℂ using the principal branch."

A real-only EML cannot:

  • generate i, π, −1, sin x, cos x, tan x, ... — every chain for these routes through ln(z) for some z < 0;
  • even compute ln 0 = −∞ portably (some platforms trap it).

So eml.core.eml always promotes to complex128 internally. When the inputs are real and the output is real (within numerical noise), the return value is silently demoted back to float64 so the API still feels "real" to the user. If you want explicit numpy floats end-to-end, restrict yourself to chains that don't pass through any negative-real log argument (essentially: +, *, exp, ln of positives) and unwrap with .real after each call.

Precision: bit-exact agreement with math.*

The natural floating-point evaluation of an EML chain accumulates ~0.5 ULP per node, so e.g. build_constant("pi")() lands ~2 ULPs away from math.pi. Most users don't need bit-exact, but if you do:

from eml.core import evalf
from eml.library import build_constant, build_unary
import math

evalf(build_constant("pi"), real=True) == math.pi          # True
evalf(build_unary("sin", "x"), {"x": 1.0}, real=True) == math.sin(1.0)   # True
evalf(build_unary("ln",  "x"), {"x": 2.0}, real=True) == math.log(2.0)   # True

evalf evaluates the chain at 30-digit mpmath precision and rounds once to complex128. Any chain that fits in 30 digits is then correctly rounded — no ULP slippage from intermediate evaluations.

"Should I just hard-code math.pi as a leaf?"

No. That would defeat the entire point of a single-operator system. The interesting fact about EML is that π = ln(−1)/i as an EML expression — exposing it as a leaf would erase that. Use evalf if you need bit-exact agreement; use the natural numpy path if you don't care about the last bit.

Use cases

See USECASES.md for fifteen concrete and speculative applications of the EML operator, grouped by maturity and pointing at the relevant module / example for each.

Publishing

See PUBLISHING.md for the PyPI release procedure (pip install build twinepython -m buildtwine upload), the GitHub Actions trusted-publishing workflow, and the version-bump checklist.

Upstream integration (NumPy / SymPy / SciPy)

See UPSTREAM_INTEGRATION.md. Short version: file an RFC issue against each upstream first, write the PR only after a maintainer asks for it. NumPy/SciPy are unlikely to accept; SymPy is the realistic candidate. Templates included.

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

exp_minus_log-0.1.0.tar.gz (42.7 kB view details)

Uploaded Source

Built Distribution

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

exp_minus_log-0.1.0-py3-none-any.whl (35.0 kB view details)

Uploaded Python 3

File details

Details for the file exp_minus_log-0.1.0.tar.gz.

File metadata

  • Download URL: exp_minus_log-0.1.0.tar.gz
  • Upload date:
  • Size: 42.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for exp_minus_log-0.1.0.tar.gz
Algorithm Hash digest
SHA256 0a5b11f32d9afcfd709ce03a0becc4f4188532f06fdb4b77d3f8cf054ef0cd15
MD5 36577184ad0be53c17b19a6d5bf45bec
BLAKE2b-256 7fb6e36cc601a40ed0cfc8f84c51ae69f2583941718fb62da364cc6eb30093ec

See more details on using hashes here.

Provenance

The following attestation bundles were made for exp_minus_log-0.1.0.tar.gz:

Publisher: publish.yml on Inknyto/eml

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file exp_minus_log-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: exp_minus_log-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 35.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for exp_minus_log-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 98e72a05f6367f37ddd57bfa0058b5cbc676d540f76285991655e2f0777b5c02
MD5 4539f59c98770bef8e90e69c55d407e2
BLAKE2b-256 c9f857e5909de859f96fc945bbf1aa5d34cb2e3911dd4ff62f8dbac03427c1a9

See more details on using hashes here.

Provenance

The following attestation bundles were made for exp_minus_log-0.1.0-py3-none-any.whl:

Publisher: publish.yml on Inknyto/eml

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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