Files
resonance-engine/_analyze_varisol.py
T
2026-06-06 21:34:30 +07:00

271 lines
12 KiB
Python

"""_analyze_varisol.py — analyse inject_variable_isolation output.
Reads /mnt/d/Resonance_Engine/traj/varisol_<RUN_ID>/injections.jsonl
Outputs the four tests Claude Desktop specified:
1. S/N test — peak_delta_asym / pre_std_asym per (var, quantile)
2. Directionality — does sign(response) flip with sign(strength)?
3. Stress symmetry — stress_xx vs yy isotropic vs anisotropic
4. Control test — mid_price near-zero check
Plus per-variable signature table aggregated over reps.
Plus the new test enabled by adding calibration runs:
5. Calibration regression — is each variable response explained by
amplitude alone (does it fall on the raw-strength response curve)?
If yes → variable is "just amplitude". If no → variable has structure.
Usage:
python3 _analyze_varisol.py latest
python3 _analyze_varisol.py <RUN_ID>
"""
from __future__ import annotations
import json, sys
from collections import defaultdict
from pathlib import Path
import numpy as np
import pandas as pd
ROOT = Path("/mnt/d/Resonance_Engine/traj")
def find_run(arg: str) -> Path:
if arg == "latest":
runs = sorted([p for p in ROOT.glob("varisol_*") if p.is_dir()])
if not runs:
sys.exit("no varisol_* runs in " + str(ROOT))
return runs[-1]
p = ROOT / (arg if arg.startswith("varisol_") else f"varisol_{arg}")
if not p.exists():
sys.exit(f"missing: {p}")
return p
def load_records(run_dir: Path) -> list[dict]:
jp = run_dir / "injections.jsonl"
if not jp.exists():
sys.exit(f"no injections.jsonl in {run_dir}")
out = []
with jp.open() as f:
for line in f:
line = line.strip()
if line:
out.append(json.loads(line))
return out
def fmt(v, w=8, p=3, plus=False):
if v is None:
return f"{'--':>{w}}"
try:
s = f"{v:+.{p}f}" if plus else f"{v:.{p}f}"
except Exception:
return f"{str(v):>{w}}"
return f"{s:>{w}}"
def aggregate(recs: list[dict]) -> pd.DataFrame:
rows = []
for r in recs:
lbl = r["label"]
row = {
"kind": lbl.get("kind"),
"var": lbl.get("var", ""),
"quantile": lbl.get("quantile_label", ""),
"rep": lbl.get("rep"),
"strength": r["strength"],
"pre_asym_mean": r.get("pre_stats", {}).get("asymmetry_mean"),
"pre_asym_std": r.get("pre_stats", {}).get("asymmetry_std"),
"pre_coh_mean": r.get("pre_stats", {}).get("coherence_mean"),
}
sig = r.get("signature", {})
for ch in ["asymmetry", "coherence", "stress_xx", "stress_yy",
"stress_xy", "vel_mean", "vorticity_mean"]:
row[f"{ch}_peak_delta"] = sig.get(f"{ch}_peak_delta")
row[f"{ch}_sn"] = sig.get(f"{ch}_sn")
row[f"{ch}_halflife_s"] = sig.get(f"{ch}_halflife_s")
row[f"{ch}_peak_time_s"] = sig.get(f"{ch}_peak_time_s")
row["stress_iso_ratio"] = sig.get("stress_iso_ratio")
rows.append(row)
return pd.DataFrame(rows)
def main():
arg = sys.argv[1] if len(sys.argv) > 1 else "latest"
run = find_run(arg)
print(f"=== analyzing {run.name} ===\n")
meta = json.loads((run / "meta.json").read_text())
print(f"run_id={meta['run_id']} started={meta.get('wall_iso_start')}")
print(f"variables={meta['variables']}")
print(f"quantiles={meta['quantiles']} str_cap={meta['str_cap']}")
print(f"calibration_strengths={meta['calibration_strengths']}")
print(f"n_repeats={meta['n_repeats']} total={meta['n_total_injections']}")
print()
base_p = run / "baseline_clean_stats.json"
if base_p.exists():
b = json.loads(base_p.read_text())
print("=== verified-stable baseline ===")
print(f" baseline_anchor_asym = {b.get('baseline_anchor_asym'):.3f}")
print(f" asymmetry: mean={b.get('asymmetry_mean'):.3f} std={b.get('asymmetry_std'):.4f}")
print(f" coherence: mean={b.get('coherence_mean'):.4f} std={b.get('coherence_std'):.4f}")
print(f" stress_xy: mean={b.get('stress_xy_mean'):.6f} std={b.get('stress_xy_std'):.6f}")
print(f" vel_mean : mean={b.get('vel_mean_mean'):.4f} std={b.get('vel_mean_std'):.4f}")
print(f" n={b.get('n')}")
print()
recs = load_records(run)
df = aggregate(recs)
print(f"loaded {len(df)} injection records "
f"({(df['kind']=='variable').sum()} variable, {(df['kind']=='calibration').sum()} calibration)\n")
var_df = df[df["kind"] == "variable"].copy()
cal_df = df[df["kind"] == "calibration"].copy()
# ─── TEST 1: S/N per (variable, quantile) ───
print("=== TEST 1: SIGNAL/NOISE (peak_asym_delta / pre_asym_std) ===")
print(f"{'variable':<18}{'quantile':>10}{'strength':>12}{'sn(mean3reps)':>17}{'peak_Δasym':>14}{'halflife_s':>13}")
grp = var_df.groupby(["var", "quantile", "strength"]).agg(
sn_mean=("asymmetry_sn", "mean"),
peak_mean=("asymmetry_peak_delta", "mean"),
halflife_mean=("asymmetry_halflife_s", "mean"),
).reset_index().sort_values(["var", "quantile"])
for _, r in grp.iterrows():
sn_str = "BELOW NOISE" if (r["sn_mean"] is not None and r["sn_mean"] < 2.0) else ""
print(f"{r['var']:<18}{r['quantile']:>10}{r['strength']:>+12.5f}"
f"{r['sn_mean']:>17.3f}{r['peak_mean']:>+14.4f}"
f"{r['halflife_mean']!s:>13} {sn_str}")
print(" (S/N < 2.0 = below the noise floor; that variable at that quantile is poison.)\n")
# ─── TEST 2: Directionality ───
print("=== TEST 2: DIRECTIONALITY (does response sign flip with strength sign?) ===")
print(f"{'variable':<18}{'q01_strength':>14}{'q01_peakΔ':>14}{'q99_strength':>14}{'q99_peakΔ':>14}{'verdict':>20}")
for v in meta["variables"]:
sub = var_df[var_df["var"] == v]
q01 = sub[sub["quantile"] == "q01"]
q99 = sub[sub["quantile"] == "q99"]
if q01.empty or q99.empty:
continue
s01 = float(q01["strength"].iloc[0]); s99 = float(q99["strength"].iloc[0])
p01 = float(q01["asymmetry_peak_delta"].mean()); p99 = float(q99["asymmetry_peak_delta"].mean())
verdict = "directional" if (np.sign(p01) != np.sign(p99) and np.sign(s01) != np.sign(s99)) else "scalar/null"
print(f"{v:<18}{s01:>+14.4f}{p01:>+14.4f}{s99:>+14.4f}{p99:>+14.4f}{verdict:>20}")
print()
# ─── TEST 3: Stress symmetry (isotropic vs anisotropic) ───
print("=== TEST 3: STRESS SYMMETRY (xx vs yy peak — isotropic if ratio→1) ===")
print(f"{'variable':<18}{'quantile':>10}{'iso_ratio(mean)':>18}{'stress_xy_peak':>17}{'verdict':>18}")
for v in meta["variables"]:
sub = var_df[var_df["var"] == v]
for q in ["q01", "q50", "q99"]:
ss = sub[sub["quantile"] == q]
if ss.empty:
continue
iso = float(ss["stress_iso_ratio"].mean())
xy = float(ss["stress_xy_peak_delta"].mean())
if iso > 0.8:
verdict = "isotropic"
elif iso < 0.5:
verdict = "anisotropic"
else:
verdict = "mixed"
print(f"{v:<18}{q:>10}{iso:>18.3f}{xy:>+17.6f}{verdict:>18}")
print()
# ─── TEST 4: Control (mid_price should be near-zero) ───
print("=== TEST 4: CONTROL (mid_price should produce near-zero response) ===")
sub = var_df[var_df["var"] == "mid_price"]
if not sub.empty:
for q in ["q01", "q50", "q99"]:
ss = sub[sub["quantile"] == q]
if ss.empty:
continue
strength = float(ss["strength"].iloc[0])
sn = float(ss["asymmetry_sn"].mean())
peak = float(ss["asymmetry_peak_delta"].mean())
print(f" mid_price {q}: strength={strength:+.5f} sn={sn:.3f} peakΔ={peak:+.4f}")
print(" Interpretation: SN should track strength (small strength → small SN). If "
"mid_price q50 has SN>>2.0, the encoding is wrong.\n")
# ─── TEST 5: Calibration regression ───
print("=== TEST 5: CALIBRATION REGRESSION (lattice impulse response vs amplitude) ===")
if not cal_df.empty:
cal_grp = cal_df.groupby("strength").agg(
asym_peak=("asymmetry_peak_delta", "mean"),
asym_sn =("asymmetry_sn", "mean"),
xy_peak =("stress_xy_peak_delta", "mean"),
half =("asymmetry_halflife_s", "mean"),
).reset_index().sort_values("strength")
print(f"{'strength':>12}{'asym_peakΔ':>14}{'asym_sn':>12}{'xy_peakΔ':>14}{'halflife_s':>13}")
for _, r in cal_grp.iterrows():
print(f"{r['strength']:>+12.4f}{r['asym_peak']:>+14.4f}{r['asym_sn']:>12.3f}"
f"{r['xy_peak']:>+14.6f}{r['half']!s:>13}")
# Fit asym_peak ~ strength (linear)
s = cal_grp["strength"].values
p = cal_grp["asym_peak"].values
if len(s) >= 3:
coef = np.polyfit(s, p, 1)
r2_num = np.sum((p - (coef[0]*s + coef[1]))**2)
r2_den = np.sum((p - p.mean())**2)
r2 = 1 - (r2_num / r2_den) if r2_den > 0 else 0
print(f"\n asym_peak = {coef[0]:+.4f} * strength + {coef[1]:+.4f} R²={r2:.4f}")
print(f" → calibration slope: {coef[0]:+.4f} (asym units per unit strength)")
print()
# For each variable, predict expected peak from strength * cal_slope,
# compare to observed. Residual = variable-specific structure.
if len(s) >= 3:
slope, intercept = coef
print(" PER-VARIABLE EXCESS RESPONSE (observed_peak - calibration_predicted):")
print(f" {'variable':<18}{'quantile':>10}{'strength':>12}{'predicted':>12}"
f"{'observed':>12}{'residual':>12}{'rel_excess%':>13}")
for v in meta["variables"]:
sub = var_df[var_df["var"] == v]
for q in ["q01", "q50", "q99"]:
ss = sub[sub["quantile"] == q]
if ss.empty:
continue
st = float(ss["strength"].iloc[0])
pred = slope * st + intercept
obs = float(ss["asymmetry_peak_delta"].mean())
resid = obs - pred
rel = (resid / abs(pred) * 100) if abs(pred) > 1e-6 else float("nan")
print(f" {v:<18}{q:>10}{st:>+12.5f}{pred:>+12.4f}"
f"{obs:>+12.4f}{resid:>+12.4f}{rel:>+13.1f}")
print("\n Interpretation: |relative excess| < 20% → variable response is "
"explained by amplitude alone (no special structure). |excess| > 50%"
"variable has a signature beyond pure amplitude (interesting/worth using).")
else:
print(" no calibration records found\n")
# ─── Per-variable summary table ───
print("\n=== PER-VARIABLE SUMMARY (q99 vs q01, mean of reps) ===")
print(f"{'variable':<18}{'q99_strength':>14}{'q99_peakΔ':>12}{'q99_sn':>10}"
f"{'q01_strength':>14}{'q01_peakΔ':>12}{'q01_sn':>10}{'directional':>13}")
summary_rows = []
for v in meta["variables"]:
sub = var_df[var_df["var"] == v]
q01 = sub[sub["quantile"] == "q01"]
q99 = sub[sub["quantile"] == "q99"]
if q01.empty or q99.empty:
continue
s01, s99 = float(q01["strength"].iloc[0]), float(q99["strength"].iloc[0])
p01, p99 = float(q01["asymmetry_peak_delta"].mean()), float(q99["asymmetry_peak_delta"].mean())
sn01, sn99 = float(q01["asymmetry_sn"].mean()), float(q99["asymmetry_sn"].mean())
direc = "yes" if np.sign(p01) != np.sign(p99) and np.sign(s01) != np.sign(s99) else "no"
print(f"{v:<18}{s99:>+14.5f}{p99:>+12.4f}{sn99:>10.2f}"
f"{s01:>+14.5f}{p01:>+12.4f}{sn01:>10.2f}{direc:>13}")
summary_rows.append({"var": v, "s99": s99, "p99": p99, "sn99": sn99,
"s01": s01, "p01": p01, "sn01": sn01, "directional": direc})
print()
# save aggregated CSV for further work
df.to_csv(run / "_aggregated.csv", index=False)
print(f"aggregated CSV: {run/'_aggregated.csv'}")
print("\n=== DONE ===")
if __name__ == "__main__":
main()