""" DBITS core dynamics — paper-faithful discrete & continuous PL kernel. Eqs: 86–107 (discrete J, ρ, stability box), 34–48 (Lyapunov V), 134–143 (continuous E_PL), 150–161 / Thm 9.3 (C_PL), 65 / Thm 6.7 (multi-dbit scale). Constants: μ = 0.16905, D₀ = 149.9992314 (Lightfoot / PIQB spine). """ from __future__ import annotations import math from dataclasses import dataclass, field from typing import List, Optional, Sequence, Tuple import numpy as np MU = 0.16905 D0 = 149.9992314 @dataclass class DbitParams: """Discrete PL kernel parameters (paper stability box Eq.107).""" delta: float = 0.3 # quantum-mode damping δ ∈ [0.2, 0.4] lam: float = 0.3 # memory learning rate λ ∈ [0.2, 0.4] kappa: float = 0.05 # cross coupling κ a_c: float = 1.0 # scale a_c with |a_c κ| ≤ 0.1 def J(self) -> np.ndarray: """Jacobian of (x, m) map — Eq.101–104.""" return np.array( [ [1.0 - self.delta, self.kappa], [self.lam * self.a_c, 1.0 - self.lam], ], dtype=float, ) def rho(self) -> float: """Spectral radius ρ(J) — stable iff ρ < 1.""" return float(np.max(np.abs(np.linalg.eigvals(self.J())))) def in_stability_box(self) -> bool: """Eq.107: δ,λ ∈ [0.2,0.4], |a_c κ| ≤ 0.1.""" return ( 0.2 <= self.delta <= 0.4 and 0.2 <= self.lam <= 0.4 and abs(self.a_c * self.kappa) <= 0.1 + 1e-12 ) @dataclass class DbitState: """Single dbit: quantum mode x + memory m.""" x: float = 0.0 m: float = 0.0 history_x: List[float] = field(default_factory=list) history_m: List[float] = field(default_factory=list) def as_dict(self) -> dict: return {"x": self.x, "m": self.m, "V": lyapunov_V(self.x, self.m)} def rho_J(params: DbitParams) -> float: return params.rho() def lyapunov_V(ex: float, em: float, a: float = 1.0, b: float = 1.0) -> float: """V = a x² + b m² (Eq.34–48).""" return a * ex**2 + b * em**2 def step_dbit(x: float, m: float, p: DbitParams) -> Tuple[float, float]: """ One discrete PL step: x' = (1-δ) x + κ m m' = (1-λ) m + λ a_c x """ xn = (1.0 - p.delta) * x + p.kappa * m mn = (1.0 - p.lam) * m + p.lam * p.a_c * x return xn, mn def trajectory( p: DbitParams, steps: int, x0: float = 0.5, m0: float = 0.0, ) -> Tuple[np.ndarray, np.ndarray]: x = np.zeros(steps) m = np.zeros(steps) x[0], m[0] = x0, m0 for t in range(steps - 1): x[t + 1], m[t + 1] = step_dbit(x[t], m[t], p) return x, m def cpl(t: float, lam_x: float, lam_p: float, a_x: float = 1.0, a_p: float = 1.0) -> float: """ Primal-Heisenberg C_PL (Eq.159 / Thm 9.3): C_PL(t) = a_x a_p (1 - e^{-(λ_x+λ_p)t}) / (λ_x+λ_p) """ s = lam_x + lam_p if s <= 0: return a_x * a_p * t return a_x * a_p * (1.0 - math.exp(-s * t)) / s def continuous_offset_series( *, t_end: float = 4.0, n_points: int = 40, x0: float = 150.0, attractor: float = D0, ) -> dict: """ Gateway dbit continuous offset simulator → D₀ attractor. state(s) = D0 - (x0-D0) e^{-0.82 s} - 5 sin(1.8 s) e^{-0.55 s} """ n_points = int(max(2, min(n_points, 200))) t_end = float(max(1e-6, t_end)) times: List[float] = [] states: List[float] = [] for i in range(n_points): progress = i / (n_points - 1) s = progress * t_end times.append(round(s, 4)) val = attractor - (x0 - attractor) * math.exp(-0.82 * s) - 5.0 * math.sin(1.8 * s) * math.exp( -0.55 * s ) states.append(round(val, 6)) final_err = abs(states[-1] - attractor) return { "time_series": times, "state_series": states, "final_error": round(final_err, 6), "attractor": attractor, "x0": x0, "status": "STABILIZED" if final_err < 1.0 else "CONVERGING", "mu": MU, "D0": D0, } def multi_dbit_step( X: np.ndarray, M: np.ndarray, p: DbitParams, *, coup: float = 0.05, ) -> Tuple[np.ndarray, np.ndarray]: """N coupled dbits: local PL + mean-field memory (Thm 6.7 / Eq.65).""" Xn = (1 - p.delta) * X + p.kappa * M Mn = (1 - p.lam) * M + p.lam * p.a_c * X mean_m = float(np.mean(Mn)) Mn = (1 - coup) * Mn + coup * mean_m return Xn, Mn def multi_dbit_trajectory( n_agents: int = 64, steps: int = 200, p: Optional[DbitParams] = None, *, seed: int = 42, coup: float = 0.05, ) -> dict: p = p or DbitParams() rng = np.random.default_rng(seed) X = rng.normal(scale=0.5, size=n_agents) M = rng.normal(scale=0.5, size=n_agents) energy: List[float] = [] for _ in range(steps): X, M = multi_dbit_step(X, M, p, coup=coup) energy.append(float(np.mean(X**2 + M**2))) return { "n_agents": n_agents, "steps": steps, "rho_local": p.rho(), "in_box": p.in_stability_box(), "E0": energy[0], "E_final": energy[-1], "decay_ratio": energy[-1] / (energy[0] + 1e-15), "energy_sample": energy[:: max(1, steps // 50)], "status": "PASS" if energy[-1] < 0.05 * energy[0] else "FAIL", "mu": MU, "D0": D0, } def continuous_pl_lyapunov( *, lam: float = 0.2, theta_max: float = 1.0, alpha: float = 1.0, beta: float = 0.1, k: float = 0.5, dt: float = 0.01, T: float = 15.0, ) -> dict: """Continuous PL E_PL filter + composite V (Eq.134–143).""" gamma = 0.5 * (alpha - k * beta) / (k * theta_max) mu = alpha - k * (beta + gamma * theta_max) design_ok = mu > 0 and 0 < gamma < (alpha - k * beta) / (k * theta_max) n = int(T / dt) + 1 t = np.linspace(0, T, n) x = np.zeros(n) x[0] = 1.0 A = -0.5 for i in range(n - 1): x[i + 1] = x[i] + dt * A * x[i] E = x**2 E_PL = np.zeros(n) for i in range(1, n): decay = math.exp(-lam * dt) E_PL[i] = decay * E_PL[i - 1] + theta_max * E[i] * (1 - decay) / lam V0 = x**2 Vcomp = V0 + gamma * E_PL dV = np.diff(Vcomp) / dt late = dV[len(dV) // 2 :] frac_neg = float(np.mean(late <= 1e-6)) return { "design_ok": design_ok, "mu": mu, "gamma": gamma, "E_PL_final": float(E_PL[-1]), "x_final": float(x[-1]), "frac_late_dV_nonpositive": frac_neg, "V_final": float(Vcomp[-1]), "status": "PASS" if design_ok and abs(x[-1]) < 0.05 and frac_neg > 0.9 else "FAIL", "equation_ids": ["Eq.134", "Eq.136", "Eq.139", "Eq.140", "Eq.143"], }