Spaces:
Running
Running
| """Two-basin reward landscapes and the pluralistic-curation retraining dynamics. | |
| Everything here follows arXiv:2605.07724 (ar5iv rendering, see docs/source_audit.md): | |
| * Eq. (5) / Lemma 3.2 large-K pluralistic update | |
| p_{t+1}(x) = p_t(x) * ( q e^{r1(x)}/Z1(t) + (1-q) e^{r2(x)}/Z2(t) ), | |
| Z_i(t) = E_{p_t}[ e^{r_i } ] | |
| * Definition 2.1 / Lemma 3.1 finite-K Bradley-Terry curation weight | |
| * Assumption 2.1 (main text) and Assumptions B.1-B.6 (appendix) | |
| A distribution is represented as a vector of probability *masses* over atoms. | |
| For a continuous landscape the atoms are grid cells, `coords` holds cell centres | |
| and `cell_volume` the cell measure, so densities are mass / cell_volume. Because | |
| the update is a pointwise multiply-and-renormalise, a cell-wise representation is | |
| exact for cell-wise constant rewards and converges to the continuum otherwise -- | |
| `stages/` always reports a grid-refinement study alongside any continuum claim. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| import numpy as np | |
| # --------------------------------------------------------------------------- # | |
| # landscape | |
| # --------------------------------------------------------------------------- # | |
| class Landscape: | |
| """A bounded two-reward landscape on a finite set of atoms.""" | |
| r1: np.ndarray | |
| r2: np.ndarray | |
| eps: float | |
| name: str = "landscape" | |
| coords: np.ndarray | None = None | |
| cell_volume: np.ndarray | None = None | |
| meta: dict = field(default_factory=dict) | |
| def __post_init__(self) -> None: | |
| self.r1 = np.asarray(self.r1, dtype=np.float64) | |
| self.r2 = np.asarray(self.r2, dtype=np.float64) | |
| if self.r1.shape != self.r2.shape: | |
| raise ValueError("r1 and r2 must have the same shape") | |
| if self.cell_volume is None: | |
| self.cell_volume = np.ones_like(self.r1) | |
| self.cell_volume = np.asarray(self.cell_volume, dtype=np.float64) | |
| # ---- derived quantities of Section 2.1 -------------------------------- # | |
| def r1_star(self) -> float: | |
| return float(self.r1.max()) | |
| def r2_star(self) -> float: | |
| return float(self.r2.max()) | |
| def S1(self) -> np.ndarray: | |
| """S_{1,eps} = { x : r1(x) >= r1* - eps }.""" | |
| return self.r1 >= self.r1_star - self.eps | |
| def S2(self) -> np.ndarray: | |
| return self.r2 >= self.r2_star - self.eps | |
| def S_union(self) -> np.ndarray: | |
| return self.S1 | self.S2 | |
| def outside(self) -> np.ndarray: | |
| """O_eps = X \\ S_eps.""" | |
| return ~self.S_union | |
| # ---- separation constants -------------------------------------------- # | |
| def delta_outside(self) -> float: | |
| """Largest delta with r_i(x) <= r_i* - delta for every x outside S_eps. | |
| Assumption 2.1(iii), outside clause. Returns +inf if O_eps is empty. | |
| """ | |
| o = self.outside | |
| if not o.any(): | |
| return float("inf") | |
| return float( | |
| min((self.r1_star - self.r1[o]).min(), (self.r2_star - self.r2[o]).min()) | |
| ) | |
| def cross_gaps(self) -> tuple[float, float]: | |
| """(Delta_1, Delta_2) in the *main-text* form of Assumption 2.1(iii): | |
| x in S_{2,eps} => r1(x) <= r1* - Delta_1 | |
| x in S_{1,eps} => r2(x) <= r2* - Delta_2 | |
| """ | |
| d1 = ( | |
| float((self.r1_star - self.r1[self.S2]).min()) | |
| if self.S2.any() | |
| else float("inf") | |
| ) | |
| d2 = ( | |
| float((self.r2_star - self.r2[self.S1]).min()) | |
| if self.S1.any() | |
| else float("inf") | |
| ) | |
| return d1, d2 | |
| def cross_gaps_appendix(self) -> tuple[float, float]: | |
| """(Delta_1, Delta_2) in the *appendix* form (Assumption B.5), which reads | |
| x in S_{2,eps} => r1(x) <= r1* - Delta_1 + eps | |
| i.e. the largest admissible gap is eps larger than the main-text one. | |
| """ | |
| d1, d2 = self.cross_gaps() | |
| return d1 + self.eps, d2 + self.eps | |
| def leakage(self, appendix: bool = False) -> tuple[float, float]: | |
| """kappa_i = exp(-(Delta_i - 2 eps)) (definition above Lemma 3.4).""" | |
| d1, d2 = self.cross_gaps_appendix() if appendix else self.cross_gaps() | |
| return ( | |
| float(np.exp(-(d1 - 2 * self.eps))), | |
| float(np.exp(-(d2 - 2 * self.eps))), | |
| ) | |
| # ---- assumption auditor ---------------------------------------------- # | |
| def audit(self, p0: np.ndarray, q: float, appendix: bool = False) -> dict: | |
| """Machine-check every clause of Assumption 2.1 / B.1-B.5. | |
| Returns a dict of clause -> {"ok": bool, ...evidence}. A stage must | |
| refuse to draw any conclusion from a landscape whose audit fails. | |
| """ | |
| d1, d2 = self.cross_gaps_appendix() if appendix else self.cross_gaps() | |
| k1, k2 = self.leakage(appendix=appendix) | |
| delta = self.delta_outside() | |
| a0 = float(p0[self.S1].sum()) | |
| b0 = float(p0[self.S2].sum()) | |
| finite = bool(np.isfinite(self.r1).all() and np.isfinite(self.r2).all()) | |
| out: dict = { | |
| # Assumption 2.1(i) / B.1 -- bounded, measurable rewards | |
| "A1_i_bounded_rewards": { | |
| "ok": finite, | |
| "r1_range": [float(self.r1.min()), float(self.r1.max())], | |
| "r2_range": [float(self.r2.min()), float(self.r2.max())], | |
| }, | |
| # Assumption 2.1(ii) / B.2+B.3 -- disjoint basins, both populated | |
| "A1_ii_basins_disjoint": { | |
| "ok": bool(not (self.S1 & self.S2).any()), | |
| "n_S1": int(self.S1.sum()), | |
| "n_S2": int(self.S2.sum()), | |
| "n_overlap": int((self.S1 & self.S2).sum()), | |
| }, | |
| "A1_ii_nontrivial_init": { | |
| "ok": bool(a0 > 0.0 and b0 > 0.0), | |
| "p0_S1": a0, | |
| "p0_S2": b0, | |
| }, | |
| # Assumption 2.1(iii) -- outside gap and cross-reward leakage gaps | |
| "A1_iii_outside_gap": { | |
| "ok": bool(delta > 0.0), | |
| "delta": delta, | |
| }, | |
| "A1_iii_cross_gaps": { | |
| "ok": bool(d1 > 0.0 and d2 > 0.0), | |
| "Delta1": d1, | |
| "Delta2": d2, | |
| }, | |
| # extra conditions specific quantitative results need | |
| "leakage_factors": { | |
| "ok": bool(k1 < 1.0 and k2 < 1.0), | |
| "kappa1": k1, | |
| "kappa2": k2, | |
| "requires_Delta_gt_2eps": bool( | |
| d1 > 2 * self.eps and d2 > 2 * self.eps | |
| ), | |
| }, | |
| "lemma35_q_window": { | |
| "ok": bool(k1 < q < 1.0 - k2), | |
| "q": float(q), | |
| "window": [k1, 1.0 - k2], | |
| }, | |
| } | |
| out["all_ok"] = all(v["ok"] for k, v in out.items() if isinstance(v, dict)) | |
| return out | |
| def outside_domination_rho(self, p: np.ndarray, q: float) -> float: | |
| """sup_{x not in S_eps} W_t(x) -- the quantity Assumption B.4 bounds by rho<1. | |
| This is the hypothesis the *appendix* proof of Lemma B.4 assumes and the | |
| main text omits, so every stage measures it explicitly. | |
| """ | |
| W = self.multiplier(p, q) | |
| o = self.outside | |
| return float(W[o].max()) if o.any() else 0.0 | |
| # ---- dynamics --------------------------------------------------------- # | |
| def multiplier(self, p: np.ndarray, q: float) -> np.ndarray: | |
| """W_t(x) = q e^{r1}/Z1 + (1-q) e^{r2}/Z2 (Eq. 5). | |
| Shifting r_i by a constant leaves W unchanged because Z_i absorbs it, so | |
| the max-subtraction below is an exact algebraic identity, not an | |
| approximation -- it only buys floating-point range. | |
| """ | |
| e1 = np.exp(self.r1 - self.r1_star) | |
| e2 = np.exp(self.r2 - self.r2_star) | |
| z1 = float(p @ e1) | |
| z2 = float(p @ e2) | |
| return q * e1 / z1 + (1.0 - q) * e2 / z2 | |
| def step(self, p: np.ndarray, q: float) -> np.ndarray: | |
| """One exact large-K pluralistic retraining step.""" | |
| return p * self.multiplier(p, q) | |
| def masses(self, p: np.ndarray) -> tuple[float, float, float]: | |
| """(a_t, b_t, m_t) = (p(S_1), p(S_2), p(X \\ S_eps)).""" | |
| return ( | |
| float(p[self.S1].sum()), | |
| float(p[self.S2].sum()), | |
| float(p[self.outside].sum()), | |
| ) | |
| def reward_stats(self, p: np.ndarray) -> dict: | |
| """E_p[r_i] and Var_p[r_i] for i = 1, 2.""" | |
| out = {} | |
| for i, r in ((1, self.r1), (2, self.r2)): | |
| mean = float(p @ r) | |
| out[f"mean_r{i}"] = mean | |
| out[f"var_r{i}"] = float(p @ (r - mean) ** 2) | |
| return out | |
| def run( | |
| self, p0: np.ndarray, q: float, steps: int, record_every: int = 1 | |
| ) -> dict: | |
| """Iterate the update, recording the theory's tracked quantities.""" | |
| p = np.array(p0, dtype=np.float64) | |
| rows: list[dict] = [] | |
| for t in range(steps + 1): | |
| if t % record_every == 0 or t == steps: | |
| a, b, m = self.masses(p) | |
| row = { | |
| "t": t, | |
| "a_t": a, | |
| "b_t": b, | |
| "m_t": m, | |
| "rho_outside_sup_W": self.outside_domination_rho(p, q), | |
| } | |
| row.update(self.reward_stats(p)) | |
| rows.append(row) | |
| if t < steps: | |
| p = self.step(p, q) | |
| return {"trace": rows, "p_final": p} | |
| # --------------------------------------------------------------------------- # | |
| # finite-K Bradley-Terry weight (Definition 2.1) -- used for the finite-K checks | |
| # --------------------------------------------------------------------------- # | |
| def bt_weight_finite_K( | |
| p: np.ndarray, r: np.ndarray, K: int, n_mc: int, rng: np.random.Generator | |
| ) -> np.ndarray: | |
| """Monte-Carlo estimate of H^{K,r}_p(x) = E[ K e^{r(x)} / (e^{r(x)} + sum e^{r(y_j)}) ]. | |
| y_1..y_{K-1} are i.i.d. from p. Returned for every atom x simultaneously. | |
| """ | |
| e = np.exp(r - r.max()) | |
| idx = rng.choice(len(p), size=(n_mc, K - 1), p=p) | |
| partial = e[idx].sum(axis=1) # (n_mc,) | |
| # E over MC draws, for each atom x | |
| return float(K) * (e[:, None] / (e[:, None] + partial[None, :])).mean(axis=1) | |
| # --------------------------------------------------------------------------- # | |
| # landscape builders | |
| # --------------------------------------------------------------------------- # | |
| def plateau_landscape( | |
| delta1: float, delta2: float, delta_outside: float, eps: float = 0.1 | |
| ) -> tuple[Landscape, np.ndarray]: | |
| """The 3-macro-state plateau reduction used by the CURRENTLY JUDGED logbook. | |
| basin1: r1=0, r2=-Delta2 ; basin2: r1=-Delta1, r2=0 ; outside: r1=r2=-delta_c. | |
| Kept only as the documented reference control (Proposition 3.4 idealisation); | |
| it is a 3-state system, not a continuous landscape. | |
| """ | |
| r1 = np.array([0.0, -delta1, -delta_outside]) | |
| r2 = np.array([-delta2, 0.0, -delta_outside]) | |
| lam = Landscape(r1, r2, eps, name="plateau-3state", meta={"kind": "plateau3"}) | |
| return lam, np.array([0.3, 0.3, 0.4]) | |
| def quadratic_grid_landscape( | |
| d: float, | |
| eps: float, | |
| n: int = 4001, | |
| half_width: float = 12.0, | |
| box: float | None = None, | |
| ) -> Landscape: | |
| """The paper's own synthetic reward geometry (Appendix C.4), on a fine 1-D grid. | |
| r_i(x) = -||x - mu_i||^2, mu_1 = -d/2, mu_2 = +d/2 | |
| X is the bounded interval [-box, box] so Assumption 2.1(i) (bounded rewards) | |
| holds. These basins are genuine continuous regions -- balls of radius | |
| sqrt(eps) about each optimum -- and the density evolves *within* them, which | |
| is exactly what a 3-macro-state reduction cannot represent. | |
| """ | |
| box = half_width if box is None else box | |
| edges = np.linspace(-box, box, n + 1) | |
| x = 0.5 * (edges[:-1] + edges[1:]) | |
| vol = np.diff(edges) | |
| mu1, mu2 = -0.5 * d, 0.5 * d | |
| r1 = -((x - mu1) ** 2) | |
| r2 = -((x - mu2) ** 2) | |
| return Landscape( | |
| r1, | |
| r2, | |
| eps, | |
| name=f"quadratic-1d-d{d:g}-n{n}", | |
| coords=x, | |
| cell_volume=vol, | |
| meta={"kind": "quadratic1d", "d": d, "n": n, "box": box, | |
| "mu1": mu1, "mu2": mu2}, | |
| ) | |
| def gaussian_init(lam: Landscape, mean: float, sd: float) -> np.ndarray: | |
| """Discretised N(mean, sd^2) initial model, restricted and renormalised to X.""" | |
| x = lam.coords | |
| if x is None: | |
| raise ValueError("gaussian_init needs a landscape with coords") | |
| dens = np.exp(-0.5 * ((x - mean) / sd) ** 2) | |
| p = dens * lam.cell_volume | |
| return p / p.sum() | |
| def uniform_init(lam: Landscape) -> np.ndarray: | |
| p = np.array(lam.cell_volume, dtype=np.float64) | |
| return p / p.sum() | |