Audit frozen LIF semantics and real recurrent motifs without learning changes
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
"""Exact float32 frozen state arithmetic plus explicitly labelled synthetic ablations."""
|
||||
import numpy as np
|
||||
|
||||
|
||||
def params(model):
|
||||
f = np.float32
|
||||
return {'gd': f(1-model['dt_ms']/model['synaptic_tau_ms']),
|
||||
'alpha': f(model['dt_ms']/model['membrane_tau_ms']),
|
||||
'rest': f(model['resting_potential_mv']), 'reset': f(model['reset_potential_mv']),
|
||||
'threshold': f(model['threshold_mv']),
|
||||
'ref_steps': round(model['refractory_ms']/model['dt_ms']),
|
||||
'delay_steps': round(model['synaptic_delay_ms']/model['dt_ms'])}
|
||||
|
||||
|
||||
def state(v, g, ref, arrival, p, policy='frozen'):
|
||||
if policy not in ('frozen', 'reset_g_only', 'refractory_gate_only', 'reset_and_refractory'):
|
||||
raise ValueError('Unknown synthetic policy: '+policy)
|
||||
r = np.maximum(ref-1, 0).astype(np.int32)
|
||||
available = r == 0
|
||||
gi = np.add(np.multiply(g, p['gd'], dtype=np.float32), arrival, dtype=np.float32)
|
||||
if policy in ('refractory_gate_only', 'reset_and_refractory'):
|
||||
gi = np.where(available, gi, g).astype(np.float32)
|
||||
leak = np.subtract(v, p['rest'], dtype=np.float32)
|
||||
dv = np.multiply(p['alpha'], np.subtract(gi, leak, dtype=np.float32), dtype=np.float32)
|
||||
vi = np.where(available, np.add(v, dv, dtype=np.float32), p['reset']).astype(np.float32)
|
||||
fire = available & (vi >= p['threshold'])
|
||||
vi[fire] = p['reset']; r[fire] = p['ref_steps']
|
||||
if policy in ('reset_g_only', 'reset_and_refractory'): gi[fire] = np.float32(0)
|
||||
return vi, gi, r, fire
|
||||
|
||||
|
||||
def single(arrivals, p, policy='frozen'):
|
||||
v = np.array([p['rest']], np.float32); g = np.zeros(1, np.float32); r = np.zeros(1, np.int32)
|
||||
history = np.empty((len(arrivals), 3), np.float32); events = []
|
||||
for t, a in enumerate(arrivals):
|
||||
v, g, r, fire = state(v, g, r, np.array([a], np.float32), p, policy)
|
||||
if fire[0]: events.append(t)
|
||||
history[t] = v[0], g[0], r[0]
|
||||
return np.asarray(events, np.int32), history
|
||||
|
||||
|
||||
def cpu_motif(nodes, edges, weights, p, steps, seed):
|
||||
assert np.all(np.diff(nodes) > 0), 'Canonical nodes must be sorted and unique'
|
||||
assert np.all(np.diff(edges.edge_id.to_numpy()) > 0), 'Canonical edge order required'
|
||||
n = len(nodes); lookup = {int(node): i for i, node in enumerate(nodes)}
|
||||
src = np.array([lookup[int(x)] for x in edges.source_neuron_idx], np.int32)
|
||||
dst = np.array([lookup[int(x)] for x in edges.target_neuron_idx], np.int32)
|
||||
qlen = p['delay_steps']+1; queue = np.zeros((qlen, n), np.float32)
|
||||
v = np.full(n, p['rest'], np.float32); g = np.zeros(n, np.float32); ref = np.zeros(n, np.int32)
|
||||
counts = np.zeros(n, np.int32); history = np.empty((steps, 3, n), np.float32)
|
||||
ts, ns = [], []
|
||||
for t in range(steps):
|
||||
slot = t % qlen
|
||||
v, g, ref, fire = state(v, g, ref, queue[slot], p); queue[slot] = 0
|
||||
if t == 0 and seed is not None:
|
||||
i = lookup[seed]
|
||||
assert not fire[i] and ref[i] == 0
|
||||
fire[i] = True; v[i] = p['reset']; ref[i] = p['ref_steps']
|
||||
counts += fire
|
||||
active = np.flatnonzero(fire)
|
||||
ts.extend([t]*len(active)); ns.extend(nodes[active].tolist())
|
||||
selected = np.flatnonzero(fire[src]); selected = selected[np.argsort(dst[selected], kind='stable')]
|
||||
for target in np.unique(dst[selected]):
|
||||
ix = selected[dst[selected] == target]; acc = np.float32(0)
|
||||
for e in ix: acc = np.float32(acc+weights[e])
|
||||
outslot = (t+p['delay_steps']) % qlen
|
||||
queue[outslot, target] = np.float32(queue[outslot, target]+acc)
|
||||
history[t] = v, g, ref
|
||||
return {'step': np.asarray(ts, np.int32), 'neuron_idx': np.asarray(ns, np.int32), 'counts': counts,
|
||||
'v': v, 'g': g, 'ref': ref, 'history': history, 'queue': queue}
|
||||
Reference in New Issue
Block a user