dontelightfoot's picture
Publish SRC + Amyloid beta-42 open package
5d07399 verified
Raw
History Blame Contribute Delete
6.75 kB
"""
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"],
}