Spaces:
Running on Zero
Running on Zero
Download src/ulens/simplex.py from loose-bits/u-space: direct link, hf CLI and curl.
- Browser
- Download file 3.75 kB
-
https://huggingface.co/spaces/loose-bits/u-space/resolve/main/src/ulens/simplex.py
- Command line
-
hf download hf://spaces/loose-bits/u-space/src/ulens/simplex.py
-
curl -L -o simplex.py https://huggingface.co/spaces/loose-bits/u-space/resolve/main/src/ulens/simplex.py
3.75 kB
| """The cone's composition as a point in a 3-simplex, and the tetrahedron that is the mark. | |
| The four rectified components, normalised to sum to one, are barycentric coordinates in a | |
| 3-simplex: the positive orthant the cone lives in is exactly a regular tetrahedron whose four | |
| vertices are the four axes, so a state's composition is drawn with nothing reduced or | |
| projected away. Near a vertex, one axis carries the cone; at the barycentre all four | |
| contribute equally. What the picture drops is the magnitude — how strongly the state pointed | |
| — and only that. | |
| Numpy only. The demo draws the view as inline SVG through `project`, and the same camera gives | |
| the page its mark (`data/mark.svg`, the favicon and the eyebrow): four vertex dots in the axis | |
| colours on six thin edges, nothing else. | |
| """ | |
| from __future__ import annotations | |
| from importlib.resources import files | |
| import numpy as np | |
| #: a regular tetrahedron with its barycentre at the origin and circumradius 1. Row order is | |
| #: the category order (`ambiguity, incompleteness, conflicting_evidence, general_uncertainty`). | |
| VERT = np.array([[1, 1, 1], [1, -1, -1], [-1, 1, -1], [-1, -1, 1]], np.float32) / np.sqrt(3.0) | |
| #: the paper's camera (its simplex-path figures): the eye, looking at the barycentre, z up. | |
| EYE = (1.08, 1.87, 0.79) | |
| #: the mark's file inside the package, so `pip install` ships it and `launch` can serve it | |
| MARK_PATH = files("ulens.data") / "mark.svg" | |
| def shares(C: np.ndarray) -> np.ndarray: | |
| """`[T, 4]` non-negative components as shares summing to one; a row of zeros is NaN.""" | |
| C = np.asarray(C, np.float64) | |
| tot = C.sum(axis=-1, keepdims=True) | |
| with np.errstate(invalid="ignore", divide="ignore"): | |
| return np.where(tot > 0, C / tot, np.nan) | |
| def bary(W: np.ndarray) -> np.ndarray: | |
| """`[T, 4]` shares to `[T, 3]` points inside the tetrahedron. Linear: a pure one-axis | |
| state is that axis's vertex and equal shares are the origin.""" | |
| return np.asarray(W, np.float64) @ VERT.astype(np.float64) | |
| def boundary_fraction(P: np.ndarray) -> np.ndarray: | |
| """How far out each `[T, 3]` point sits, as the fraction of the way from the barycentre | |
| to the tetrahedron's surface along its own direction: 0 at the centre, 1 on a face. | |
| The face opposite vertex `i` has outward normal `-VERT[i]` and sits at distance 1/3.""" | |
| P = np.asarray(P, np.float64) | |
| d = -(P @ VERT.T.astype(np.float64)) # [T, 4] signed distance along each normal | |
| return np.max(d, axis=-1) * 3.0 | |
| def project(P: np.ndarray, eye=EYE) -> np.ndarray: | |
| """`[T, 3]` to `[T, 2]` screen coordinates (y up) under a perspective camera at `eye` | |
| looking at the origin with z up. The origin projects to (0, 0).""" | |
| e = np.asarray(eye, np.float64) | |
| d0 = np.linalg.norm(e) | |
| f = -e / d0 # forward | |
| r = np.cross(f, np.array([0.0, 0.0, 1.0])) | |
| r /= np.linalg.norm(r) # right | |
| u = np.cross(r, f) # up on screen | |
| rel = np.asarray(P, np.float64) - e | |
| depth = rel @ f | |
| return np.column_stack([(rel @ r) * d0 / depth, (rel @ u) * d0 / depth]) | |
| def mark_points(size: float = 32.0, margin: float = 4.6) -> np.ndarray: | |
| """The four vertices on a `size`-square canvas (y down), the projected frame fitted | |
| inside `margin`. This is where the dots of `data/mark.svg` are.""" | |
| xy = project(VERT) | |
| lo, hi = xy.min(0), xy.max(0) | |
| s = (size - 2 * margin) / float((hi - lo).max()) | |
| ctr = (lo + hi) / 2 | |
| out = (xy - ctr) * s | |
| out[:, 1] *= -1 | |
| return out + size / 2 | |
| def mark_svg() -> str: | |
| """The mark's markup, for inline use; the file itself is the favicon.""" | |
| return MARK_PATH.read_text(encoding="utf-8").strip() | |