Metadata-Version: 2.4
Name: eml-math
Version: 2.0.16
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Education
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Rust
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Topic :: Scientific/Engineering :: Physics
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Requires-Dist: pytest>=8.0 ; extra == 'dev'
Requires-Dist: pytest-cov ; extra == 'dev'
Requires-Dist: ruff>=0.4 ; extra == 'dev'
Requires-Dist: mypy>=1.9 ; extra == 'dev'
Requires-Dist: pillow>=10.0 ; extra == 'dev'
Requires-Dist: sphinx>=7.0 ; extra == 'docs'
Requires-Dist: sphinx-rtd-theme ; extra == 'docs'
Requires-Dist: myst-parser ; extra == 'docs'
Requires-Dist: sphinxcontrib-katex ; extra == 'docs'
Requires-Dist: numpy>=1.26 ; extra == 'ext'
Requires-Dist: sympy>=1.12 ; extra == 'ext'
Requires-Dist: mpmath>=1.3.0 ; extra == 'precision'
Requires-Dist: pillow>=10.0 ; extra == 'render-raster'
Provides-Extra: dev
Provides-Extra: docs
Provides-Extra: ext
Provides-Extra: precision
Provides-Extra: render-raster
License-File: LICENSE
Summary: Exp-Minus-Log Mathematics: the EML Sheffer operator eml(x,y)=exp(x)−ln(y). Slim core: expression trees, symbolic regression, flow-diagram renderer. (Algebras + GR live in the sister package eml-spectral.)
Keywords: mathematics,eml,sheffer,symbolic-regression,formula-discovery,expression-tree,flow-diagram
Author-email: Andrew K Watts <andrewkwatts@gmail.com>
Requires-Python: >=3.11
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Bug Tracker, https://github.com/andrewkwatts-maker/EML-Math/issues
Project-URL: C/C++ API, https://github.com/andrewkwatts-maker/EML-Math/tree/main/c_api
Project-URL: Documentation, https://github.com/andrewkwatts-maker/EML-Math/blob/main/README.md
Project-URL: Homepage, https://github.com/andrewkwatts-maker/EML-Math
Project-URL: Repository, https://github.com/andrewkwatts-maker/EML-Math

# EML-Math

**EML Mathematics** — a universal real-valued foundation for elementary mathematics, built from a single binary operator.

[![PyPI](https://img.shields.io/pypi/v/eml-math)](https://pypi.org/project/eml-math/)
[![Python 3.11+](https://img.shields.io/pypi/pyversions/eml-math)](https://pypi.org/project/eml-math/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

**Repo:** <https://github.com/andrewkwatts-maker/EML-Math>

Created by **Andrew K Watts**. Based on the EML Sheffer operator established by
Andrzej Odrzywolek: [arXiv:2603.21852v2](https://arxiv.org/html/2603.21852v2)
(CC BY 4.0).

---

## The core idea

A single binary operator generates every elementary function:

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

This is the **EML Sheffer operator** — the continuous analog of the NAND
gate for Boolean logic. From it the 36 standard elementary functions
(`+`, `−`, `×`, `/`, `exp`, `ln`, `sin`, `cos`, `tan`, `π`, `e`, …) can
all be reconstructed as composed expression trees.

`EMLPoint` is the operator's computation node — simultaneously a
mathematical state and a composable expression-tree leaf:

```python
from eml_math import EMLPoint
import math

EMLPoint(1, 1).tension()                                  # e   = eml(1, 1)
EMLPoint(2, 1).tension()                                  # exp(2)
EMLPoint(1, EMLPoint(EMLPoint(1, math.e), 1)).tension()   # ln(e) = 1.0
```

---

## What's new in v2.x

The 2.x line keeps the same scope (pure-EML universal-math toolkit) but
sharpens correctness and readability of the pure-EML LaTeX renderer.
Highlights since v1.x:

- **Numerical validation of every famous equation** — 1,000 random
  variable combinations × 47 entries in `FAMOUS` × `EMLEvaluator` ≡ a
  reference Python form, 46,000+ comparisons in ~2 s. See
  [`tests/test_famous_random.py`](tests/test_famous_random.py).
- **`pow(x, n)` is now mathematically correct** for non-literal
  exponents — emits `exp(exp(ln(n) + ln(ln(x))))` instead of the
  earlier incorrect `exp(add(ln(n), ln(x)))` (which collapsed to
  `n·x`). Fixes `(x-1)**2 → (x-1)^2` after `expand_numeric_constants`.
- **Pure-EML LaTeX recogniser** picks up `1/x`, `−ln x`, `a − b`,
  `a + b`, `x^c` (including the double-exp pow shape) directly from
  the pure-EML structure, in addition to the existing `e^x`, `ln x`,
  `−x` rules.
- **Algebraic simplifications** in the renderer: `ln(1) → 0`,
  `e^0 → 1`, `x + 0 = x`, `0 − x = −x`, double-negation cancellation,
  `exp(ln y) = y`. Hover-on-sub-expression reads as math instead of
  literal EML scaffolding.
- **BOTTOM (`⊥`) renders as `0`** — readable, not an abstract symbol.
  The longer `eml(⊥, …)` shapes are still consumed by the pattern
  matchers so the change only surfaces in rare compound expressions.
- **Width-aware tidy-tree layout** — `compute_layout` accepts a
  `width_for_label` callback; the contour walker reserves the actual
  rendered box footprint per node so wide `eml` labels can't collide
  with narrow `0`/`1` stubs.
- **Layout overlap fix for deep cousins** — the contour walker now
  propagates `n.shift + n.mod` (not just `n.mod`) when descending, so
  descendants of every sibling live in the same frame as descendants
  of earlier siblings.

## The slim core

`eml-math` stays the **pure-EML universal-math toolkit**: the
operator, expression trees, symbolic regression, the elementary-function
operator library, the famous-equations registry, and the flow-diagram
renderer. Nothing else.

| Module | Purpose |
|---|---|
| `EMLPoint`, `_VarNode` | The EML node, with variable-leaf support for symbolic work |
| `tree` | Expression-tree parser, renderer, JSON-array compact form |
| `operators` | The 36 elementary functions as ready-made EML trees |
| `evaluator` | Parse and evaluate EML formula strings |
| `symbols` | Named-symbol registry (e, π, φ, √2, …) |
| `discover` | `compress`, `recognize`, `Searcher` — symbolic regression |
| `famous` | Registered classic equations (Pythagoras, Euler, Einstein, …) |
| `flow` + `flow_layout` | Legacy SVG / PNG / PDF / HTML flow-diagram renderer |
| `render` | New abstracted renderer — raw JSON → layout dict → pluggable Renderer (`SVGRenderer`, `HTMLRenderer`, `PNGRenderer`, `PDFRenderer`, BYO). Three edge styles (`straight` · `curve` · `spline`), Reingold-Tilford tidy-tree layout, MathJax/MathML output via `decompress(r, fmt='mathjax')`. |
| `web` | Bundled `eml_flow.js` UMD bundle for browser-side rendering |

> **Algebras and physics are now in [eml-spectral](https://github.com/andrewkwatts-maker/EML-Spectral)** —
> the sister package, **v1.0.0 release**. EML-tree representations of Clifford
> algebras, octonions, exceptional algebras (E7/E8/Freudenthal), Lorentz-invariant
> spacetime ops, named GR metrics, and the spectral-flow operator Φ all live there.
> Same zero-deps philosophy, optional Rust acceleration, optional C API.
>
> ```bash
> pip install eml-spectral   # transitively installs eml-math >= 1.2.0
> ```

---

## Installation

```bash
pip install eml-math               # core
pip install eml-math[ext]          # + numpy, sympy
pip install eml-math[precision]    # + mpmath
pip install eml-math[dev]          # + pytest, ruff, mypy
```

For the algebras / physics layer:

```bash
pip install eml-spectral
# transitively pulls in eml-math >= 1.2.0
```

---

## Companion app — `eml_math.Launch()`

The pip wheel ships with a one-call launcher for the
[EML-Math-App](https://github.com/andrewkwatts-maker/EML-Math-App) — a
KivyMD visual explorer for the library. It builds the expression tree
live as you type, renders Normal-math / EML-primitive LaTeX previews,
draws the EML graph with hover-per-node sub-expression preview, lets
you copy any sub-tree in Normal / EML / LaTeX / Python / JSON, and
exports PNG at any DPI.

```python
import eml_math
eml_math.Launch()        # finds or clones EML-Math-App, runs it
```

On first call the launcher looks for a sibling ``EML-Math-App``
checkout next to your eml-math source directory; otherwise it clones
the matching version tag from GitHub into ``~/.eml-math-app`` and
``pip install -e`` it. Subsequent calls reuse the cached checkout.

The app needs the Kivy stack:

```bash
pip install eml-math-app   # installs Kivy + KivyMD too
eml-math-app               # CLI entry point if you'd rather skip Python
```

---

## Public API reference

Every name below is importable directly from ``eml_math``.

| Group | Symbols | What it does |
|---|---|---|
| **Core EML node** | ``EMLPoint`` | The eml(x, y) computation node. ``.tension()`` evaluates. Composes recursively (children can be EMLPoints). |
| **Trees & parsing** | ``EMLTreeNode``, ``NodeKind``, ``parse_eml_tree``, ``normalize_input``, ``tree_to_python``, ``EML_EXPANSIONS`` | Expression trees, JSON dict round-trip, compact ↔ pure-EML conversion, "EML:" DSL parser. |
| **Compact ↔ array** | ``to_compact``, ``from_compact``, ``KIND_CHAR``, ``CHAR_KIND`` | JSON-array serialisation of trees (compact bracket form). |
| **Evaluator** | ``EMLEvaluator``, ``eml_eval``, ``ParseError`` | Evaluate an "EML: …" string with a parameter map. |
| **Operators** | ``eml_scalar``, ``eml_pi``, ``eml_vec`` | Atomic leaf factories used inside "EML: ops.…" descriptions. |
| **Symbols** | ``Symbol``, ``SYMBOLS``, ``lookup``, ``construct``, ``register`` | Named-constant registry — e, π, φ, √2, γ, τ … |
| **Symbolic regression** | ``Searcher``, ``SearchResult``, ``compress``, ``compress_str``, ``compress_latex``, ``recognize`` | Find an EML expression matching a target value or text. |
| **Decompose** | ``decompress``, ``get``, ``get_tree``, ``list_symbols``, ``list_constants``, ``expand_numeric_constants`` | Convert a search result into ``math`` / ``latex`` / ``mathjax`` / ``mathml`` / ``python`` / ``eml``; rewrite numeric literals into EML form. |
| **Datasheet API** | ``Get`` | Uniform query — returns a JSON-serialisable dict with value, formula, EML tree for any named constant. |
| **Famous equations** | ``FamousEquation``, ``FAMOUS``, ``get_famous``, ``famous_by_category``, ``all_famous_equations`` | Curated catalogue (Pythagoras, Euler identity, mass-energy equivalence, …) — every entry round-trips through the renderers. |
| **Legacy flow renderer** | ``flow_svg``, ``flow_html``, ``flow_png``, ``flow_pdf``, ``DEFAULT_PALETTE`` | One-shot graph rendering — see "Generating equation graphs" below. |
| **Abstract render pipeline** | ``render``, ``to_layout``, ``render_layout_svg``, ``render_layout_png``, ``render_layout_pdf``, ``gentle_curves``, ``tighten_base``, ``spread_horizontal``, ``fit_to_canvas``, ``organic_layout`` | Layout-dict → renderer; post-process JSON to apply visual styles. |
| **Browser bundle** | ``get_flow_js``, ``FLOW_JS_PATH`` | Inline UMD bundle for client-side rendering in HTML pages. |
| **Companion app** | ``Launch`` | Start the EML-Math-App GUI (see section above). |

Every symbol has a Python docstring — ``help(eml_math.Searcher)``,
``help(eml_math.decompress)``, etc. read the inline documentation.

---

## Quickstart — symbolic regression

`Searcher` finds an EML expression that matches a target numeric value:

```python
from eml_math import Searcher

s = Searcher(target=2.71828)
result = s.search()
print(result.formula)      # 'eml(1, 1)'   (i.e. e)
```

`compress` and `recognize` go in the other direction:

```python
from eml_math import compress, recognize

print(recognize(3.14159))         # ('π', 3.141592653589793, 0.0)
print(compress("exp(x) - ln(y)")) # SearchResult: matches the EML primitive itself
```

---

## Generating equation graphs (SVG / PNG / PDF / HTML)

Every formula in the famous-equations registry can be rendered to
SVG / PNG / PDF / HTML directly. The same primitives work on any
EML expression you build.

### One-liner from a famous equation

```python
from eml_math.famous import get

einstein = get("einstein_e_mc2")

# Bytes → write yourself
open("einstein.svg", "w", encoding="utf-8").write(einstein.flow_svg(width=900, height=600))
open("einstein.png", "wb").write(einstein.flow_png(width=900, height=600))
open("einstein.pdf", "wb").write(einstein.flow_pdf(width=900, height=600))

# Self-contained interactive HTML (UMD bundle inlined)
open("einstein.html", "w", encoding="utf-8").write(einstein.parse().flow_html(width=900, height=600))
```

### Direct from an EML expression

```python
from eml_math import parse_eml_tree

tree = parse_eml_tree(
    "EML: ops.sqrt(ops.add(ops.pow(eml_vec('a'), eml_scalar(2.0)), "
    "ops.pow(eml_vec('b'), eml_scalar(2.0))))",
    pure_eml=True,
)

open("pythagoras.svg", "w", encoding="utf-8").write(tree.flow_svg(width=1200, height=900))
open("pythagoras.png", "wb").write(tree.flow_png(width=1200, height=900))
```

### Render-time options

Useful kwargs accepted by `flow_svg` / `flow_png` / `flow_pdf`:

| Argument | Default | Effect |
|---|---|---|
| `width`, `height` | `800` × `600` | SVG / PNG canvas in px |
| `direction` | `"down"` | growth direction: `"down"` · `"up"` · `"left"` · `"right"` |
| `auto_height` | `True` | grow the canvas vertically for deep trees instead of cropping |
| `min_layer_height` | `38.0` | minimum vertical gap per tree level |
| `palette` | built-in | sequence of `(r, g, b)` colours for variable inputs |
| `output_label` | `"Out"` | label drawn at the root of the tree |
| `show_output_label` | `True` | hide the root label by passing `False` |
| `inline_constants` | `False` | render small constants (`2`, `0.5`) on the edge instead of as a leaf |
| `merge_inputs` | `False` | combine repeated variable leaves into a single fan-out node |
| `expand_symbols` | `False` | expand named symbols (π, φ, …) to their pure-EML form |
| `edge_width` | `3.0` | stroke width for edges |
| `junction_radius` | `4.0` | dot radius at each internal node |
| `label_font_size` / `output_font_size` | `18` / `22` | label sizes |
| `background` | `None` | SVG background colour (`None` = transparent) |

For a different *visual style*, post-process the layout JSON before
rendering — see the next section.

### Layout-intermediate JSON pipeline (this is how styles are applied)

For finer control — or to render the same geometry from JavaScript /
post-process it programmatically — go through the layout-intermediate
form. `to_layout` returns a JSON-serialisable dict; the post-processes
return a transformed copy; `render_layout_svg` / `render_layout_png` /
`render_layout_pdf` produce final output.

The two named styles in the gallery are post-process pipelines applied
to the same `to_layout(tree)` output:

| Style | Post-process pipeline |
|---|---|
| `formal` | `fit_to_canvas(L)` — symmetric, top-down, the default |
| `organic` | `organic_layout(L, branch_angle=24, length_scale=44, length_decay=0.97, min_length=24)` → `fit_to_canvas` — branching, tree-like; `min_length` floor stops deep leaves clumping |

```python
import json
from eml_math import (
    to_layout, render_layout_svg, render_layout_png, render_layout_pdf,
    organic_layout, fit_to_canvas,
)

layout = to_layout(tree, width=1200, height=900)

# Apply organic style
layout = organic_layout(layout,
                        branch_angle=24.0, length_scale=44.0,
                        length_decay=0.97, min_length=24.0,
                        branch_jitter=0.12, trunk_pull=0.35,
                        balance="subtree_size")
layout = fit_to_canvas(layout, margin=20)

# Inspect / save the geometry before rendering
with open("pythagoras_layout.json", "w", encoding="utf-8") as f:
    json.dump(layout, f, indent=2)

# Render to whatever you need
open("pythagoras.svg", "w", encoding="utf-8").write(render_layout_svg(layout))
open("pythagoras.png", "wb").write(render_layout_png(layout))
open("pythagoras.pdf", "wb").write(render_layout_pdf(layout))
```

Knobs on `organic_layout` worth knowing:

- `length_decay` (default `0.97`) — how aggressively branches shrink
  per generation. Closer to `1.0` keeps deep leaves spaciously apart.
- `min_length` (default `22`) — floor on per-generation branch length.
  Even after `length_decay` reduces the geometric length, no branch
  shrinks below this. Set to `0` for the pure geometric decay look.
- `trunk_pull` (default `0.35`) — how strongly each child's growing
  direction is pulled back toward the global trunk axis. Prevents long
  chains from curling into a tight spiral.
- `balance` (default `"subtree_size"`) — assigns the larger subtree to
  the outside of each fork so the tree visually balances.

### Compact JSON for the tree itself

The expression tree (independent of layout) round-trips through a tiny
JSON-friendly array. Useful for storage, transport, or rendering on
the JS side without re-parsing the EML string:

```python
import json
from eml_math import to_compact, from_compact

compact = to_compact(tree)             # list of nested arrays
encoded = json.dumps(compact)          # plain JSON, ~7× smaller than the dict form
rebuilt = from_compact(json.loads(encoded))   # exact structural round-trip
```

### Browser-side rendering (`eml_flow.js`)

`eml_math.web` ships a UMD bundle that renders the same layout JSON in
the browser. Two ways to use it:

```python
from eml_math import flow_html, get_flow_js, FLOW_JS_PATH

# Self-contained HTML (the JS bundle is inlined — open the file, no server needed)
open("einstein.html", "w", encoding="utf-8").write(
    tree.flow_html(width=900, height=600)
)

# Or grab the bundle to host yourself
open("static/eml_flow.js", "w", encoding="utf-8").write(get_flow_js())
print("Bundle path on disk:", FLOW_JS_PATH)
```

In your own page:

```html
<script src="eml_flow.js"></script>
<script>
  const layout = /* JSON dumped from to_layout(...) */;
  document.body.innerHTML = EMLFlow.renderSVG(layout);
</script>
```

### Batch generation — the famous-equations gallery

To regenerate every equation in the registry across all styles
(produces `formal/`, `gentle/`, `organic/`, `tree/` folders with PNG +
PDF for each, plus a self-contained `index.html`):

```bash
python examples/famous_gallery.py [output_dir]   # default: ./famous_gallery
```

Output structure:

```
famous_gallery/
├── index.html        # browse every style of every equation
├── formal/
│   ├── png/einstein_e_mc2.png
│   └── pdf/einstein_e_mc2.pdf
├── organic/   (same layout)
├── gentle/    (same layout)
└── tree/      (same layout)
```

---

## Famous-equations registry

```python
from eml_math import all_famous_equations, get_famous

print([eq.name for eq in all_famous_equations()])
# ['Pythagoras', 'Euler identity', 'E = mc²', 'Schrödinger', ...]

einstein = get_famous("einstein_e_mc2")
print(einstein.eml_formula)    # the EML-tree form
```

---

## Rust accelerator (optional)

The wheel bundles a Rust extension exposing Rayon-parallel batch
operators (`exp_n`, `ln_n`, `add_n`, `mul_n`, `sin_n`, `tension_n`, …):

```python
from eml_math.eml_core import tension_n
import numpy as np

xs = np.linspace(0, 5, 1_000_000)
ys = np.ones_like(xs)
out = tension_n(xs.tolist(), ys.tolist())   # parallel
```

A C/C++/Rust shared-library API lives under [`c_api/`](c_api/) for
embedding the operator into other languages. Build it with:

```bash
cargo build --release -p eml_c_api
```

---

## Project layout

```
eml-math/                           # this repo
├─ src/eml_math/                    # Python sources (the slim core)
│  ├─ render/                       # Abstracted renderer pipeline
│  │  ├─ layout.py                  # Reingold-Tilford tidy-tree
│  │  ├─ edges.py                   # straight / curve / spline path generators
│  │  ├─ palette.py                 # palette helpers
│  │  └─ renderers/                 # SVG, HTML, PNG, PDF — pluggable Renderer protocol
│  └─ ...
├─ rust/eml_core/                   # Rust accelerator (PyO3 module)
├─ c_api/                           # C/C++/Rust shared-library bindings
├─ docs/                            # Static-HTML site (index/api/guide/concepts)
└─ tests/                           # pytest suite (2077 tests, 0 required deps)
```

---

## License

MIT, © Andrew K Watts.

