Buckets:

illusion-brain/brain-domains / scripts /build_l2_missing.py
redmingwei's picture
download
raw
10 kB
#!/usr/bin/env python3
"""build_l2_missing — 补齐 L2 缺的三个成员。
1. primate_dmfc_rsg (NWB):Units 表 = ragged spike_times + 每单元 obs_intervals。
按各单元共有的观测区间拼接,20 ms 分箱,得到 (总bin, n_units)。
2. omnibrain_multi:1,409,850 × 64 × 8 窗口,按 (source, group_key) 分组算协方差类不变量。
每组最多取 5000 个窗口,控制协方差计算量。
3. counts_v4_ragged:175 段 uint8 + 索引。这是同一批数据的另一种编码,
行里打 representation=ragged_uint8 与 duplicate_of=<源文件>,避免下游重复计数。
只补模型无关量 + 协方差类;τ 只在 dmfc_rsg 上算(它有时间序列)。
"""
from __future__ import annotations
import glob
import json
import time
from pathlib import Path
import h5py
import numpy as np
import pandas as pd
ROOT = Path("/domains")
OUT = ROOT / "framework"
MAX_LAG = 400
CAP_WINDOWS = 5000
def n2(n):
k = 1
while k < n:
k <<= 1
return k
def curve_parts(x, max_lag):
T, d = x.shape
xc = x - x.mean(axis=0, keepdims=True)
N = n2(2 * T)
F = np.fft.rfft(xc, n=N, axis=0)
Fs = F.sum(axis=1)
tot = np.fft.irfft(Fs * np.conj(Fs), n=N)[:max_lag + 1]
dia = np.fft.irfft((np.abs(F) ** 2).sum(axis=1), n=N)[:max_lag + 1]
nrm = np.arange(T, T - max_lag - 1, -1)
tot = tot / np.maximum(nrm, 1)
dia = dia / np.maximum(nrm, 1)
t0 = max(tot[0], 1e-12)
return (tot - dia) / t0, dia / max(dia[0], 1e-12)
def cross_time(curve, bin_ms, frac):
if len(curve) < 3:
return None, "too_short"
y1 = float(curve[1])
if not np.isfinite(y1) or y1 <= 0:
return None, "bad_start"
thr = frac * y1
idx = np.flatnonzero(curve[2:] <= thr)
if len(idx) == 0:
return None, "censored"
l = int(idx[0]) + 2
y0, yb = float(curve[l - 1]), float(curve[l])
f = (y0 - thr) / (y0 - yb) if y0 > yb else 0.0
return float((l - 1 + f) * bin_ms), "ok"
def cov_metrics(X):
X = np.asarray(X, dtype=np.float64)
if X.ndim != 2 or X.shape[0] < 5 or X.shape[1] < 2:
return dict(pr=None, erank=None, spec_slope=None)
Xc = X - X.mean(axis=0, keepdims=True)
C = (Xc.T @ Xc) / max(X.shape[0] - 1, 1)
ev = np.clip(np.linalg.eigvalsh(C)[::-1], 0, None)
s = ev.sum()
if s <= 0:
return dict(pr=None, erank=None, spec_slope=None)
pr = float(s * s / max((ev ** 2).sum(), 1e-30))
p = ev / s
p = p[p > 0]
erank = float(np.exp(-(p * np.log(p)).sum()))
k = min(20, len(ev))
rr = np.arange(1, k + 1)
ok = ev[:k] > 0
slope = float(np.polyfit(np.log(rr[ok]), np.log(ev[:k][ok]), 1)[0]) if ok.sum() > 2 else None
return dict(pr=pr, erank=erank, spec_slope=slope)
BASE = dict(member=None, slice=None, kind=None, n_units=None, n_bins=None, bin_ms=None,
rate_hz=None, note=None, pr=None, erank=None, spec_slope=None,
tau_ms=None, rho1=None, snr=None, n_points=0, max_lag_ms=None,
neg_frac=None, tau_status="not_computed", tau_available=False,
pooled_acf_lag1=None, pooled_acf_half_ms=None, pooled_acf_zero_ms=None,
pooled_acf_half_status=None, pooled_acf_zero_status=None,
representation=None, duplicate_of=None)
rows = []
t00 = time.time()
def add(**kw):
r = dict(BASE)
r.update(kw)
r["tau_available"] = r["tau_status"] == "ok"
rows.append(r)
# ---------------------------------------------------------------- 1. NWB
print("[1] dmfc_rsg NWB", flush=True)
for f in sorted(glob.glob(str(ROOT / "primate/dmfc_rsg/*.nwb"))):
name = Path(f).stem
with h5py.File(f, "r") as h:
st = h["units/spike_times"][:]
sti = h["units/spike_times_index"][:]
oi = h["units/obs_intervals"][:]
oii = h["units/obs_intervals_index"][:]
n_units = len(sti)
starts = np.concatenate([[0], sti[:-1]])
per_unit_spikes = [st[a:b] for a, b in zip(starts, sti)]
istarts = np.concatenate([[0], oii[:-1]])
per_unit_ints = [oi[a:b] for a, b in zip(istarts, oii)]
# 取所有单元共有的区间(按 1 ms 取整比对)
def key(iv):
return (round(float(iv[0]), 3), round(float(iv[1]), 3))
common = None
for ivs in per_unit_ints:
s = {key(iv) for iv in ivs}
common = s if common is None else (common & s)
common = sorted(common) if common else []
if len(common) < 2:
print(f" {name}: common intervals={len(common)},退回全局跨度", flush=True)
lo = min(float(iv[0]) for ivs in per_unit_ints for iv in ivs)
hi = max(float(iv[1]) for ivs in per_unit_ints for iv in ivs)
common = [(lo, hi)]
bin_s = 0.020
edges_list, total = [], 0
for a, b in common:
nb = max(int(np.floor((b - a) / bin_s)), 1)
edges_list.append((a, nb))
total += nb
X = np.zeros((total, n_units), dtype=np.float32)
off = 0
for a, nb in edges_list:
e = a + np.arange(nb + 1) * bin_s
for j, sp in enumerate(per_unit_spikes):
c, _ = np.histogram(sp, bins=e)
X[off:off + nb, j] = c
off += nb
print(f" {name}: units={n_units} intervals={len(common)} bins={total} "
f"({total*20/1000:.0f}s)", flush=True)
ml = min(MAX_LAG, X.shape[0] - 2)
rho, acf = curve_parts(X, ml)
rec = dict(member="primate_dmfc_rsg", slice=name, kind="nwb_binned",
n_units=n_units, n_bins=int(total), bin_ms=20.0,
rate_hz=float(X.mean() / 0.02),
note=f"concatenated_{len(common)}_intervals", representation="nwb",
max_lag_ms=ml * 20.0, rho1=float(rho[1]),
pooled_acf_lag1=float(acf[1]))
rec.update(cov_metrics(X))
rec["pooled_acf_half_ms"], rec["pooled_acf_half_status"] = cross_time(acf, 20.0, 0.5)
rec["pooled_acf_zero_ms"], rec["pooled_acf_zero_status"] = cross_time(acf, 20.0, 0.0)
rec["tau_status"] = "not_computed" # τ 需要零模型,本轮不重算
add(**rec)
# ---------------------------------------------------------------- 2. omnibrain-multi
print("\n[2] omnibrain_multi", flush=True)
idx = pd.read_parquet(ROOT / "omnibrain-multi/index_timeseries.parquet")
ts = np.load(ROOT / "omnibrain-multi/timeseries.npy", mmap_mode="r")
rng = np.random.default_rng(0)
for (src, gk), g in idx.groupby(["source", "group_key"], sort=False):
pos = g.index.to_numpy()
if len(pos) > CAP_WINDOWS:
pos = rng.choice(pos, CAP_WINDOWS, replace=False)
X = np.asarray(ts[np.sort(pos)], dtype=np.float32).reshape(-1, 64)
rec = dict(member="omnibrain_multi", slice=f"{src}|{gk}", kind="windows64x8",
n_units=64, n_bins=8, bin_ms=100.0,
note=f"n_win={len(pos)}", representation="windowed_100ms",
tau_status="not_estimable_8bins",
pooled_acf_half_status="not_estimable_8bins",
pooled_acf_zero_status="not_estimable_8bins")
rec.update(cov_metrics(X))
add(**rec)
print(f" groups={idx.groupby(['source','group_key']).ngroups} [{time.time()-t00:.0f}s]",
flush=True)
# ---------------------------------------------------------------- 3. counts-v4
INCLUDE_COUNTS_V4 = False # 175 段是既有成员的另一种编码,不进入 L2
print("\n[3] counts_v4_ragged", "SKIPPED" if not INCLUDE_COUNTS_V4 else "", flush=True)
ci = pd.read_parquet(ROOT / "counts-v4/counts_index.parquet") if INCLUDE_COUNTS_V4 else None
flat = np.load(ROOT / "counts-v4/counts_ragged.npy", mmap_mode="r")
for _, r in (ci.iterrows() if ci is not None else []):
nb, nu = int(r.n_bins_20ms), int(r.n_units)
off, nel = int(r.offset), int(r.n_elements)
if nel != nb * nu:
print(f" skip seg {r.segment_idx}: n_elements={nel} != {nb}*{nu}", flush=True)
continue
# counts_ragged.npy 是 uint8 整数计数,不要再乘 8
x = np.asarray(flat[off:off + nel], dtype=np.float32).reshape(nb, nu)
bin_ms = float(r.bin_ms)
ml = min(MAX_LAG, nb - 2)
if ml < 20:
continue
rho, acf = curve_parts(x, ml)
rec = dict(member="counts_v4_ragged", slice=f"seg{int(r.segment_idx):03d}",
kind="ragged_uint8", n_units=nu, n_bins=nb, bin_ms=bin_ms,
rate_hz=float(x.mean() / (bin_ms / 1000.0)),
note=f"{r.source}|{Path(str(r.file)).stem}", representation="ragged_uint8",
duplicate_of=str(r.file), max_lag_ms=ml * bin_ms, rho1=float(rho[1]),
pooled_acf_lag1=float(acf[1]), tau_status="not_computed",
pooled_acf_half_ms=None, pooled_acf_zero_ms=None)
rec.update(cov_metrics(x))
rec["pooled_acf_half_ms"], rec["pooled_acf_half_status"] = cross_time(acf, bin_ms, 0.5)
rec["pooled_acf_zero_ms"], rec["pooled_acf_zero_status"] = cross_time(acf, bin_ms, 0.0)
add(**rec)
print(f" segments={0 if ci is None else len(ci)} [{time.time()-t00:.0f}s]", flush=True)
# ---------------------------------------------------------------- merge
new = pd.DataFrame(rows)
print(f"\nnew rows={len(new)} members={new.member.nunique()} [{time.time()-t00:.0f}s]")
P = OUT / "l2_invariants.parquet"
old = pd.read_parquet(P)
keep = old[~old.member.isin(new.member.unique())]
out = pd.concat([keep, new], ignore_index=True, sort=False)
out.to_parquet(P, index=False)
out.to_csv(OUT / "l2_invariants.csv", index=False)
D = OUT / "l2_decay.parquet"
d = pd.read_parquet(D)
dnew = new[["member", "slice", "pooled_acf_lag1", "pooled_acf_half_ms",
"pooled_acf_zero_ms", "pooled_acf_half_status", "pooled_acf_zero_status",
"max_lag_ms"]].copy()
dmm = pd.concat([d[~d.member.isin(new.member.unique())], dnew], ignore_index=True,
sort=False)
dmm.to_parquet(D, index=False)
print(f"l2_invariants rows={len(out)} members={out.member.nunique()}")
print(out.groupby("member").agg(n=("slice", "size"), tau_ok=("tau_available", "sum"),
pr=("pr", "median"), erank=("erank", "median")).to_string())

Xet Storage Details

Size:
10 kB
·
Xet hash:
b8d8986d45f978c1ec30c070c6ca6f703b331db7d1ce1974b0e6903e21c1eeee

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