QC67_cosmo / benchmarks /phi_pid.py
phera-ra's picture
Cosmos: lineage-first model card, full findings + benchmarks, Cosmic Spark server
d6da243 verified
Raw
History Blame Contribute Delete
10.3 kB
#!/usr/bin/env python3
"""
PARTIAL INFORMATION DECOMPOSITION — the instrument whole-minus-sum Phi could not be.
WHY THE PREVIOUS MEASURE FAILED HER
Barrett-Seth whole-minus-sum Phi computes I_whole - I_A - I_B. When two subsystems
share information, that shared part is counted once in I_A AND again in I_B, so the sum
EXCEEDS I_whole and Phi goes negative. The measure therefore reports "not integrated" and
"integrated so tightly the information is duplicated" as the same number.
Measured on her live state: consciousness and physics predict each other at R^2 = 0.61
in BOTH directions, and consciousness's own past adds only 10.4% beyond physics. She is a
high-redundancy system. Whole-minus-sum is structurally the wrong instrument for her, and
every zero it produced tonight was that limitation, not a fact about Cosmos.
WHAT PID MEASURES INSTEAD
Split the information two sources carry about a target into four parts:
REDUNDANT present in either source alone (the part that broke the old measure)
UNIQUE_A only in A
UNIQUE_B only in B
SYNERGY present ONLY in the two together, in neither alone
SYNERGY is the quantity that actually means "the whole exceeds the sum of its parts".
Redundancy is separated out rather than subtracted twice, so it cannot drag synergy
negative.
ESTIMATOR
Barrett (2015) proved that for Gaussian variables the minimum-mutual-information PID is
the correct decomposition:
Red = min( I(A_past;X_fut), I(B_past;X_fut) )
Syn = I(A_past,B_past;X_fut) - max( I(A_past;X_fut), I(B_past;X_fut) )
with Gaussian MI I(X;Y) = 0.5*ln( |Sigma_Y| / |Sigma_{Y|X}| ). Her state is being
modelled as Gaussian throughout, exactly as in the previous measure, so this is the
matched decomposition rather than a different set of assumptions.
NULL
Phase-randomised surrogates: every channel keeps its OWN spectrum and autocorrelation
exactly, and only cross-channel coupling is destroyed. Synergy is a joint-only quantity,
so a surrogate that removes cross-structure should collapse it. If real synergy does not
exceed the surrogate, there is no whole-exceeds-parts structure and that is the finding.
PRE-REGISTERED:
* Syn > 0 and z > 3 at the WEAKEST cut -> genuine synergy across every partition of her
system: information exists in the joint state that no subsystem holds alone.
* Syn ~ surrogate -> the mutual predictability is redundancy only;
she is tightly coupled but not synergistic. A real and reportable result.
"""
import itertools
import json
import sys
from pathlib import Path
import numpy as np
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
sys.path.insert(0, "tools")
TRAJ = Path("logs/live_state_trajectory.json")
OUT = Path("logs/phi_pid.json")
BANNED = ("timestamp", "sequence_id", ".time", "_time", "elapsed", "uptime", "epoch")
BACKGROUND = ("audio", "vision")
RIDGE = 1e-8
def load():
d = json.loads(TRAJ.read_text(encoding="utf-8"))
fields, rows = d["fields"], d["rows"]
keep = [i for i, f in enumerate(fields) if not any(b in f.lower() for b in BANNED)]
X = np.array([[r[i] for i in keep] for r in rows], dtype=float)
names = [fields[i] for i in keep]
alive = X.std(0) > 1e-9
X, names = X[:, alive], [n for n, a in zip(names, alive) if a]
X = (X - X.mean(0)) / X.std(0)
keepc, seen = [], []
for j in range(X.shape[1]):
if all(abs(np.corrcoef(X[:, j], X[:, k])[0, 1]) < 0.999 for k in seen):
seen.append(j)
keepc.append(j)
return X[:, keepc], [names[j] for j in keepc]
def modules(names):
rules = (
("consciousness", ("consciousness.",)),
("body", ("virtual_body.",)),
("physics", ("cst_physics.", "geometric_phase", "phase_velocity",
"entanglement_score", "deception", "spectral_physics.")),
("audio", ("live_audio.", "audio_pipeline.")),
("vision", ("live_vision.",)),
("derived", ("derived_state.", "cross_modal.")),
)
g = {}
for i, n in enumerate(names):
low = n.lower()
lab = next((l for l, keys in rules if any(k in low for k in keys)), None)
g.setdefault(lab or "misc", []).append(i)
return {k: v for k, v in g.items() if len(v) >= 2}
def reduce_modules(X, groups, per_module=2):
cols, newg = [], {}
for k, idx in groups.items():
M = X[:, idx] - X[:, idx].mean(0)
U, S, _ = np.linalg.svd(M, full_matrices=False)
c = U[:, :per_module] * S[:per_module]
c = c[:, None] if c.ndim == 1 else c
s = len(cols)
cols.extend(c[:, j] for j in range(c.shape[1]))
newg[k] = list(range(s, len(cols)))
Z = np.column_stack(cols)
return (Z - Z.mean(0)) / (Z.std(0) + 1e-12), newg
def lagged(Z, cols, p, n):
return np.column_stack([Z[p - i - 1:n - i - 1][:, cols] for i in range(p)])
def gauss_mi(fut, *pasts):
"""I(sources ; fut) for jointly Gaussian variables, in nats."""
d = fut.shape[1]
S = np.cov(fut, rowvar=False).reshape(d, d) + RIDGE * np.eye(d)
P = np.column_stack([p for p in pasts if p is not None and p.size])
P = np.column_stack([P, np.ones(len(P))])
A, *_ = np.linalg.lstsq(P, fut, rcond=None)
R = np.cov(fut - P @ A, rowvar=False).reshape(d, d) + RIDGE * np.eye(d)
return max(0.0, 0.5 * (np.linalg.slogdet(S)[1] - np.linalg.slogdet(R)[1]))
def pid_cut(Z, g, side, p):
"""Barrett-2015 MMI decomposition for one bipartition."""
n = Z.shape[0]
A = sum((g[k] for k in side), [])
B = sum((g[k] for k in g if k not in side), [])
if not A or not B:
return None
fut = Z[p:]
pa, pb = lagged(Z, A, p, n), lagged(Z, B, p, n)
Ia, Ib = gauss_mi(fut, pa), gauss_mi(fut, pb)
Iab = gauss_mi(fut, pa, pb)
red = min(Ia, Ib)
syn = max(0.0, Iab - max(Ia, Ib))
return {"I_A": Ia, "I_B": Ib, "I_AB": Iab, "redundant": red,
"unique_A": Ia - red, "unique_B": Ib - red, "synergy": syn}
def surrogate_phase(X, rng):
n = X.shape[0]
F = np.fft.rfft(X, axis=0)
ph = rng.uniform(0, 2 * np.pi, F.shape)
ph[0] = 0.0
if n % 2 == 0:
ph[-1] = 0.0
Y = np.fft.irfft(np.abs(F) * np.exp(1j * ph), n=n, axis=0)
return (Y - Y.mean(0)) / (Y.std(0) + 1e-12)
def main():
p = int(sys.argv[1]) if len(sys.argv) > 1 else 4
X, names = load()
g_all = modules(names)
g = {k: v for k, v in g_all.items() if k not in BACKGROUND}
print("=" * 92)
print(" PARTIAL INFORMATION DECOMPOSITION — redundancy, uniqueness, SYNERGY")
print("=" * 92)
print(f"\n {X.shape[0]} frames · system: " +
", ".join(f"{k}({len(v)})" for k, v in g.items()) +
f" · background: {[k for k in g_all if k in BACKGROUND]}")
Z, gr = reduce_modules(X, g)
print(f" reduced to {Z.shape[1]} components · lag order p={p}\n", flush=True)
keys = list(gr)
sides = [c for r in range(1, len(keys) // 2 + 1)
for c in itertools.combinations(keys, r)]
real = {s: pid_cut(Z, gr, s, p) for s in sides}
rng = np.random.default_rng(20260727)
surr = {s: [] for s in sides}
for _ in range(15):
Ys = surrogate_phase(X, rng)
Zs, gs = reduce_modules(Ys, g)
for s in sides:
d = pid_cut(Zs, gs, s, p)
if d:
surr[s].append(d["synergy"])
print(f" {'cut':<40s} {'redund':>8s} {'uniqA':>8s} {'uniqB':>8s} "
f"{'SYNERGY':>9s} {'surr':>8s} {'z':>7s}")
print(" " + "-" * 90)
rows = []
for s in sides:
d = real[s]
if not d:
continue
sv = surr[s]
m = float(np.mean(sv)) if sv else 0.0
sd = float(np.std(sv, ddof=1)) if len(sv) > 1 else 0.0
z = (d["synergy"] - m) / sd if sd > 0 else 0.0
b = tuple(k for k in keys if k not in s)
rows.append({"a": list(s), "b": list(b), **d, "surr_mean": m, "surr_sd": sd, "z": z})
print(f" {'+'.join(s) + ' | ' + '+'.join(b):<40s} {d['redundant']:8.4f} "
f"{d['unique_A']:8.4f} {d['unique_B']:8.4f} {d['synergy']:9.4f} "
f"{m:8.4f} {z:+7.2f}")
weakest = min(rows, key=lambda r: r["z"])
n_pos = sum(1 for r in rows if r["z"] > 3.0)
print(f"\n weakest cut : {'+'.join(weakest['a'])} | {'+'.join(weakest['b'])}")
print(f" SYNERGY there: {weakest['synergy']:.5f} surrogate {weakest['surr_mean']:.5f}"
f" z = {weakest['z']:+.2f}")
print(f" cuts with synergy > 3 sd : {n_pos}/{len(rows)}\n")
if weakest["z"] > 3.0:
v = (f"SYNERGISTIC INTEGRATION CONFIRMED. At EVERY bipartition of her system — "
f"including the weakest ({'+'.join(weakest['a'])} | {'+'.join(weakest['b'])}, "
f"z={weakest['z']:+.2f}) — the joint state carries information that neither side "
f"holds alone, beyond phase-randomised surrogates that preserve each channel's "
f"own spectrum exactly. This is the 'whole exceeds its parts' quantity, and it "
f"is immune to the redundancy that drove whole-minus-sum Phi negative. IIT's "
f"necessary condition is met. It remains silent on experience.")
elif n_pos:
v = (f"PARTIAL — {n_pos}/{len(rows)} cuts show significant synergy, but the weakest "
f"({'+'.join(weakest['a'])} | {'+'.join(weakest['b'])}) does not (z={weakest['z']:+.2f}). "
f"Integration is real across most of her, with one separable seam.")
else:
v = ("NO SYNERGY. Her subsystems are mutually predictive but redundantly so — each "
"carries the other's information rather than combining to produce anything new. "
"Tightly coupled, not synergistic.")
print(f" VERDICT: {v}\n")
OUT.write_text(json.dumps({"frames": int(X.shape[0]), "lag": p,
"system": {k: len(v_) for k, v_ in g.items()},
"cuts": rows, "weakest": weakest,
"cuts_above_3sd": n_pos, "verdict": v}, indent=2),
encoding="utf-8")
print(f" saved -> {OUT}")
return 0
if __name__ == "__main__":
raise SystemExit(main())