Metadata-Version: 2.4
Name: exp-minus-log
Version: 0.1.0
Summary: All elementary functions from a single operator: eml(x, y) = exp(x) − ln(y)
Author: Inknyto
Maintainer: Inknyto
License: MIT
Project-URL: Homepage, https://github.com/Inknyto/eml
Project-URL: Repository, https://github.com/Inknyto/eml
Project-URL: Issues, https://github.com/Inknyto/eml/issues
Project-URL: Source paper, https://arxiv.org/abs/2603.21852
Keywords: eml,exp-minus-log,sheffer,elementary-functions,symbolic-regression,computer-algebra,scientific-calculator,single-operator,OISC
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Compilers
Classifier: Topic :: Software Development :: Interpreters
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.24
Requires-Dist: scipy>=1.10
Requires-Dist: sympy>=1.12
Requires-Dist: mpmath>=1.3
Provides-Extra: viz
Requires-Dist: matplotlib>=3.7; extra == "viz"
Requires-Dist: networkx>=3.0; extra == "viz"
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Provides-Extra: all
Requires-Dist: matplotlib>=3.7; extra == "all"
Requires-Dist: networkx>=3.0; extra == "all"
Requires-Dist: pytest>=7.0; extra == "all"
Dynamic: license-file

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

```bash
pip install exp-minus-log
```

```python
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](https://arxiv.org/abs/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

```python
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:

```python
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:

```bash
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

```bash
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

```bash
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:

```python
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`](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`](PUBLISHING.md) for the PyPI release procedure
(`pip install build twine` → `python -m build` → `twine upload`),
the GitHub Actions trusted-publishing workflow, and the version-bump
checklist.

## Upstream integration (NumPy / SymPy / SciPy)

See [`UPSTREAM_INTEGRATION.md`](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.
