Buckets:

redmingwei's picture
download
raw
7.16 kB
#!/usr/bin/env python3
"""exp52 — exp43 的逐试次 tau 用积分时间重算(不依赖指数拟合阈值)。
exp51 发现:502 个试次里只有 83 个能拟合出指数 tau。
其余 419 个的 trace-ACF 在 4 个 lag(16 ms)内就低于 2%,
指数拟合无从下手。这说明逐试次 tau 在 600 ms 的 spike 窗口上
大多不可辨识,而不是"值很小"。
改用 Sokal 积分时间 tau_int = 1 + 2*sum(rho),对加权 ACF 施加,
它对快速衰减仍然给出有限值,不需要 2% 阈值。
同时报告:
- 可辨识比例(指数能拟合的试次数 / 总试次)
- 两种估计量的中位数
- 与 |计时误差| 的 Spearman 与置换检验
- 对照:把 tau 与行为随机配对
"""
from __future__ import annotations
import json
import time
from pathlib import Path
import numpy as np
import pandas as pd
from scipy.optimize import curve_fit
from scipy.stats import spearmanr
NWB = Path("/domains/primate/dmfc_rsg/sub-Haydn_desc-train_ecephys.nwb")
OUT = Path("/domains/runs/tau-fixed")
OUT.mkdir(parents=True, exist_ok=True)
BIN_MS = 4.0
WIN_AFTER_SET_MS = 600.0
N_PCS = 16
MAX_LAG_MS = 400.0
def acf_cols(x, max_lag):
T = x.shape[0]
xc = x - x.mean(axis=0, keepdims=True)
n = 1
while n < 2 * T:
n <<= 1
f = np.fft.rfft(xc, n=n, axis=0)
a = np.fft.irfft(f * np.conj(f), n=n, axis=0)[:max_lag + 1]
norms = np.arange(T, T - max_lag - 1, -1)[:, None]
a = a / np.maximum(norms, 1)
return a / np.maximum(a[0:1], 1e-12)
def weighted_acf(x, max_lag):
T = x.shape[0]
xc = x - x.mean(axis=0, keepdims=True)
n = 1
while n < 2 * T:
n <<= 1
f = np.fft.rfft(xc, n=n, axis=0)
a = np.fft.irfft(f * np.conj(f), n=n, axis=0)[:max_lag + 1]
norms = np.arange(T, T - max_lag - 1, -1)[:, None]
a = a / np.maximum(norms, 1)
return a.sum(axis=1) / max(a[0].sum(), 1e-12)
def tau_int(a, dt, c=5.0, floor=1e-9):
"""Sokal: tau = 1 + 2*sum(rho),窗口 M >= c*tau。"""
taus = 1.0 + 2.0 * np.cumsum(a[1:])
Ms = np.arange(1, len(a))
m = Ms >= c * np.maximum(taus, floor)
if np.any(m):
return float(taus[int(np.argmax(m))] * dt)
return float(taus[-1] * dt)
def fit_tau(a, dt, lo=2.0, hi=5000.0):
ok = np.isfinite(a) & (a > 0.02) & (a < 0.995)
if ok.sum() < 5:
return None
t = np.arange(len(a))[ok] * dt
y = a[ok]
f = lambda tt, T: np.exp(-tt / np.maximum(T, 1e-3))
try:
popt, _ = curve_fit(f, t, y, p0=[100.0], bounds=([lo], [hi]), maxfev=6000)
return float(popt[0])
except Exception:
return None
def main():
import h5py
t0 = time.time()
with h5py.File(NWB, "r") as f:
tr = f["intervals"]["trials"]
d = {k: tr[k][:] for k in ["ready_time", "set_time", "go_time",
"is_outlier", "is_short"]}
st = f["units"]["spike_times"][:]
si = f["units"]["spike_times_index"][:]
bounds = np.concatenate([[0], si])
units = [st[bounds[i]:bounds[i + 1]] for i in range(len(si))]
rt, s_, gt = d["ready_time"], d["set_time"], d["go_time"]
t1 = s_ - rt
t2 = gt - s_
ok = (np.isfinite(t1) & np.isfinite(t2) & (t1 > 0.1) & (t2 > 0.05)
& (~d["is_outlier"]) & (~d["is_short"]))
err = (t2 - t1) * 1000.0
print(f"trials {len(gt)} | units {len(units)} | valid {ok.sum()}", flush=True)
bin_s = BIN_MS / 1000.0
win_s = WIN_AFTER_SET_MS / 1000.0
ml = int(MAX_LAG_MS / BIN_MS)
rows = []
for i in np.flatnonzero(ok):
a0, a1 = s_[i], s_[i] + win_s
edges = np.arange(a0, a1 + bin_s, bin_s)
M = np.zeros((len(edges) - 1, len(units)), dtype=np.float32)
for j, sp in enumerate(units):
if len(sp) == 0:
continue
sel = sp[(sp >= a0) & (sp < a1)]
if len(sel) == 0:
continue
bi = np.clip(((sel - a0) / bin_s).astype(int), 0, M.shape[0] - 1)
np.add.at(M[:, j], bi, 1.0)
if M.shape[0] < 20 or M.sum() < 10:
continue
mu = M.mean(axis=0, keepdims=True)
sd = M.std(axis=0, keepdims=True) + 1e-6
Z = (M - mu) / sd
dd = min(N_PCS, Z.shape[1] - 1)
U, S, _ = np.linalg.svd(Z, full_matrices=False)
P = (U[:, :dd] * S[:dd]).astype(np.float64)
lam = S ** 2
aw = weighted_acf(Z, ml)
ao = np.median(acf_cols(P, ml), axis=1)
rows.append({
"trial": int(i),
"abs_err_ms": float(abs(err[i])),
"rel_err": float(abs(err[i]) / (t1[i] * 1000)),
"tau_int_inv": tau_int(aw, BIN_MS),
"tau_int_old": tau_int(ao, BIN_MS),
"tau_fit_inv": fit_tau(aw, BIN_MS),
"tau_fit_old": fit_tau(ao, BIN_MS),
"acf1_inv": float(aw[1]),
"ev_top1": float(lam[0] / lam[:dd].sum()),
"n_spikes": int(M.sum()),
})
if len(rows) % 100 == 0:
print(f" {len(rows)} [{time.time()-t0:.0f}s]", flush=True)
df = pd.DataFrame(rows)
df.to_csv(OUT / "exp43_int.csv", index=False)
def corr(col):
dd = df.dropna(subset=[col, "abs_err_ms"])
r, p = spearmanr(dd[col], dd.abs_err_ms)
rng = np.random.default_rng(0)
perm = np.array([spearmanr(dd[col], rng.permutation(dd.abs_err_ms.to_numpy()))[0]
for _ in range(2000)])
return {"n": int(len(dd)), "rho": round(float(r), 4), "p": float(p),
"p_perm": float((np.abs(perm) >= abs(r)).mean())}
res = {
"task": "exp43_fixed_tau_int",
"n_trials": int(len(df)),
"estimable_fraction": {
"tau_fit_inv": round(float(df.tau_fit_inv.notna().mean()), 3),
"tau_fit_old": round(float(df.tau_fit_old.notna().mean()), 3),
"tau_int_inv": round(float(df.tau_int_inv.notna().mean()), 3),
},
"medians_ms": {
"tau_int_inv": round(float(df.tau_int_inv.median()), 2),
"tau_int_old": round(float(df.tau_int_old.median()), 2),
"tau_fit_inv": (round(float(df.tau_fit_inv.median()), 2)
if df.tau_fit_inv.notna().any() else None),
"tau_fit_old": (round(float(df.tau_fit_old.median()), 2)
if df.tau_fit_old.notna().any() else None),
},
"acf1_inv_median": round(float(df.acf1_inv.median()), 4),
"ev_top1_median": round(float(df.ev_top1.median()), 4),
"correlations": {
"tau_int_inv": corr("tau_int_inv"),
"tau_int_old": corr("tau_int_old"),
},
}
# 与单位数 / spike 总数的混淆
for c in ["n_spikes", "ev_top1"]:
r, p = spearmanr(df[c], df.tau_int_inv)
res[f"confound_{c}_vs_tau_int_inv"] = {"rho": round(float(r), 3),
"p": round(float(p), 4)}
(OUT / "report_exp43_int.json").write_text(
json.dumps(res, indent=2, ensure_ascii=False), encoding="utf-8")
print("\n" + json.dumps(res, indent=2, ensure_ascii=False), flush=True)
if __name__ == "__main__":
main()

Xet Storage Details

Size:
7.16 kB
·
Xet hash:
f112660abbb038921a4055f6f1c786104aef8554c4f4f0d3b7b4a15212b157f6

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.