Audit frozen LIF semantics and real recurrent motifs without learning changes
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
# Phase 4AC — upstream LIF semantics audit
|
||||
|
||||
Goal context: usable stimulus-specific associative learning remains unachieved.
|
||||
This stage is a prerequisite diagnosis, never a substitute for that goal.
|
||||
|
||||
All raw, base CSR, old models/experiments and signs remain read-only. No eta,
|
||||
threshold, gain, mask or APL calibration is changed. No conditioning, games,
|
||||
decoder, visual stimulation, or new formal runtime is produced.
|
||||
|
||||
## New evidence and hypotheses
|
||||
|
||||
The official Shiu paper links philshiu/Drosophila_brain_model. Commit
|
||||
91bdd1e7dcf193f3e7ca5a8933497fcef63b7960 (2024-09-14) model.py explicitly resets g
|
||||
on a spike, declares both v and g `(unless refractory)`, uses linear integration
|
||||
and strict `v > v_th`, and removes refractory time on Poisson-target neurons.
|
||||
Current frozen Phase 4B/P instead retains g, decays/accepts arrivals during
|
||||
refractory, uses explicit Euler, `>=`, and force requests respecting refractory.
|
||||
The Phase 4B config documents its refractory-g policy, so this is a documented
|
||||
adaptation difference, NOT evidence that deterministic CUDA regressed its oracle.
|
||||
Neither implementation is automatically a more accurate MaleCNS physiology model.
|
||||
|
||||
## Scope of work allowed without replacing frozen brain dynamics
|
||||
|
||||
1. Preserve official code/license at a pinned commit, with hashes; do not execute
|
||||
downloaded code. Extract exact source statements into an evidence table.
|
||||
2. Synthetic one-cell mathematical fixtures compare the frozen g policy with
|
||||
reset-only, refractory-only, and combined component ablations. Other arithmetic
|
||||
stays frozen. These are NOT a Brian2 reproduction (integration and scheduling
|
||||
also differ), and are not deployed to the MaleCNS brain.
|
||||
3. Simulate only the UNCHANGED frozen dynamics on actual induced 2/3/5-neuron
|
||||
circuits selected from the already observed Phase 4Z path. One force request at
|
||||
t=0, then no input, for 1,000 ms. Include no-input and real-edge feedforward
|
||||
controls. Missing external network inhibition/feedback is explicitly excluded;
|
||||
a sustained isolated response is not proof of whole-brain necessity.
|
||||
4. Validate every frozen CPU small-circuit event/state against the unchanged CUDA
|
||||
kernel, using exact float32 ordered accumulation and delay=18 steps.
|
||||
5. Recheck the five archived deterministic reference trials and all old hashes.
|
||||
|
||||
All single-cell amplitudes derive from the complete unique contact-count set in
|
||||
the selected real subgraph, plus a one-contact unit fixture. Do not choose a
|
||||
successful amplitude after observing outcomes. Add a refractory-arrival fixture
|
||||
at t=0/1/2/2.2/2.3 ms and a sustained-input-then-off fixture (100 ms then100 ms).
|
||||
These describe operator consequences, not measured cell physiology or tuning.
|
||||
|
||||
The full learning gate still requires >=5 training seeds, independent evaluation,
|
||||
A-vs-neutral-B specificity in MBON/downstream, appropriate controls, acquisition,
|
||||
reversal, retention and delayed-reward tests. Existing transfer PASS is not that.
|
||||
|
||||
## Next-stage authorization boundary
|
||||
|
||||
Testing alternative semantics or measured local physiology on the full MaleCNS
|
||||
network would be a NEW model experiment, not an exact runtime optimization.
|
||||
User confirmation is requested separately. This stage does not silently execute
|
||||
or promote that experiment, even if synthetic ablations reduce firing.
|
||||
|
||||
## Caveats to record
|
||||
|
||||
W_syn=0.275 is the original paper's free parameter selected on its feeding circuit,
|
||||
not a universal measured MaleCNS contact efficacy. g reset by itself does not make
|
||||
the model equivalent to the author's Brian2 implementation. Old reference PASS
|
||||
means consistency with our old oracle, not matching the original author software.
|
||||
@@ -0,0 +1,256 @@
|
||||
"""Evidence-only audit and unchanged-dynamics real motif tests; no learning."""
|
||||
import argparse
|
||||
import ast
|
||||
from datetime import datetime, timezone
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
import urllib.request
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import yaml
|
||||
from math_reference import params, single, state, cpu_motif
|
||||
|
||||
CFG = yaml.safe_load(Path(__file__).with_name('config.yaml').read_text())
|
||||
ROOT = Path(CFG['root']); MODEL = ROOT/CFG['model']; EXP = ROOT/CFG['experiment']
|
||||
sys.path.insert(0, str(ROOT/'models/male-cns-lif/apl-pn-scope-audit-v1'))
|
||||
from audit import save, digest, immutable_check, seal
|
||||
sys.path.insert(0, str(ROOT/'models/male-cns-lif/terminal-scope-diagnostic-v1'))
|
||||
from diagnostic import manifest_check, queues
|
||||
|
||||
|
||||
def integrity():
|
||||
result = {'original': immutable_check(ROOT, ROOT/CFG['old_snapshot'], 4)}
|
||||
for name in CFG['additional_frozen']: result[name] = manifest_check(ROOT/name, digest)
|
||||
assert all(x['pass'] for x in result.values()), result
|
||||
return result
|
||||
|
||||
|
||||
def prepare():
|
||||
if EXP.exists() and any(EXP.iterdir()): raise RuntimeError('Refusing nonempty output')
|
||||
EXP.mkdir(exist_ok=True)
|
||||
for d in ['protocol', 'external-resources', 'audit', 'synthetic', 'motifs', 'regression', 'performance']:
|
||||
(EXP/d).mkdir()
|
||||
save(EXP/'protocol/preregistered.json', {'config': CFG, 'before_analysis': True,
|
||||
'timestamp_utc': datetime.now(timezone.utc).isoformat(),
|
||||
'parent_commit': subprocess.check_output(['git', '-C', str(ROOT), 'rev-parse', 'HEAD'], text=True).strip(),
|
||||
'sources': {p.name: digest(p) for p in sorted(MODEL.iterdir()) if p.is_file()}})
|
||||
save(EXP/'regression/preflight.json', integrity())
|
||||
external = EXP/'external-resources'; commit = CFG['author']['commit']; records = []
|
||||
for filename in ['model.py', 'LICENSE', 'environment_full.yml']:
|
||||
url = f'https://raw.githubusercontent.com/philshiu/Drosophila_brain_model/{commit}/{filename}'
|
||||
with urllib.request.urlopen(url, timeout=60) as response: data = response.read()
|
||||
target = external/filename
|
||||
with target.open('xb') as out: out.write(data)
|
||||
records.append({'path': filename, 'url': url, 'bytes': len(data), 'sha256': digest(target), 'license': 'MIT'})
|
||||
assert digest(external/'model.py') == CFG['author']['model_sha256']
|
||||
assert 'MIT License' in (external/'LICENSE').read_text()
|
||||
text = (external/'model.py').read_text(); ast.parse(text)
|
||||
clauses = ['dg/dt = -g / tau', '(unless refractory)', 'v = v_rst; w = 0; g = 0 * mV',
|
||||
"method='linear'", "'v > v_th'", 'neu[i].rfc = 0 * ms']
|
||||
assert all(clause in text for clause in clauses)
|
||||
lines = text.splitlines()
|
||||
save(EXP/'audit/author-source-clauses.json', [{'clause': clause, 'line_numbers_1based': [i+1 for i, line in enumerate(lines) if clause in line]} for clause in clauses])
|
||||
save(external/'provenance.json', {'files': records, 'commit': commit, 'paper': CFG['author']['paper'],
|
||||
'downloaded_code_executed': False, 'new_dependencies_installed': False})
|
||||
can = ROOT/CFG['canonical']; metadata = pd.read_parquet(can/'neuron-index/unified-metadata.parquet').sort_values('neuron_idx').reset_index(drop=True)
|
||||
ptr = np.load(can/'gpu/csr-indptr.int64.npy', mmap_mode='r'); dst = np.load(can/'gpu/csr-indices.int32.npy', mmap_mode='r'); raw = np.load(can/'gpu/csr-weights.int64.npy', mmap_mode='r')
|
||||
assert len(metadata) == 166700 and len(dst) == 25582938 and int(raw.sum()) == 124177617
|
||||
nodes = np.array(CFG['selected_nodes'], np.int32)
|
||||
meta = metadata.iloc[nodes].copy(); assert np.array_equal(meta.neuron_idx, nodes)
|
||||
assert meta.neurotransmitter.eq('acetylcholine').all(), 'No sign inference or reassignment'
|
||||
alln = meta[meta.annotation_class.eq('ALLN')]; assert alln.annotation_type.eq('lLN1_bc').all() and alln.nt_ground_truth.eq('acetylcholine').all()
|
||||
ids = np.concatenate([np.arange(ptr[n], ptr[n+1], dtype=np.int64) for n in nodes])
|
||||
src = np.concatenate([np.full(ptr[n+1]-ptr[n], n, np.int32) for n in nodes])
|
||||
keep = np.isin(dst[ids], nodes); ids = ids[keep]; src = src[keep]
|
||||
edges = pd.DataFrame({'edge_id': ids, 'source_neuron_idx': src, 'target_neuron_idx': dst[ids], 'raw_count': raw[ids]})
|
||||
edges['source_body_id'] = metadata.body_id.to_numpy()[src]; edges['target_body_id'] = metadata.body_id.to_numpy()[dst[ids]]
|
||||
assert len(edges) == 13
|
||||
meta.to_parquet(EXP/'audit/selected-metadata.parquet', index=False); edges.to_csv(EXP/'audit/selected-real-edges.csv', index=False)
|
||||
# Explicit metadata mapping only, not physiological parameter assignment.
|
||||
graded = metadata[metadata.annotation_type.isin(['lLN2P_a', 'lLN2P_b', 'lLN2P_c', 'lLN2F_b'])].copy()
|
||||
cols = ['neuron_idx','body_id','annotation_type','annotation_hemibrainType','annotation_flywireType','annotation_matchingNotes','neurotransmitter','nt_ground_truth']
|
||||
graded[cols].to_csv(EXP/'audit/local-inhibition-crosswalk.csv', index=False)
|
||||
frozen_yaml = ROOT/CFG['runtime']/'phase4p.yaml'; pc = yaml.safe_load(frozen_yaml.read_text())
|
||||
save(EXP/'protocol/frozen-input-hashes.json', {'runtime_config': digest(frozen_yaml),
|
||||
'runtime_cpp': digest(ROOT/CFG['runtime']/'phase4p_ext.cpp'), 'runtime_cuda': digest(ROOT/CFG['runtime']/'phase4p_ext.cu'),
|
||||
'baseline_phase4b': digest(ROOT/'models/male-cns-lif/phase4b.py'),
|
||||
'metadata': digest(can/'neuron-index/unified-metadata.parquet')})
|
||||
save(EXP/'protocol/dynamics.json', pc['model'])
|
||||
evidence = [
|
||||
['spike reset g', 'reset to zero', 'retain g'],
|
||||
['g during refractory', 'ODE paused; synaptic writes blocked', 'decay and accept arrivals'],
|
||||
['integration', 'linear exact state updater', 'forward Euler with decayed g'],
|
||||
['threshold', 'strict >', '>='],
|
||||
['within-step arrivals', 'after integration, before reset', 'before integration'],
|
||||
['stimulated neurons', 'voltage Poisson input; refractory=0', 'forced spikes subject to 22-step refractory'],
|
||||
['W_syn', 'free parameter calibrated on FlyWire feeding response', 'same value transferred to MaleCNS; not independently measured']]
|
||||
pd.DataFrame(evidence, columns=['item', 'author', 'frozen_adapted']).to_csv(EXP/'audit/semantics-differences.csv', index=False)
|
||||
prepared = ['protocol/dynamics.json','audit/selected-real-edges.csv','audit/selected-metadata.parquet',
|
||||
'protocol/frozen-input-hashes.json','audit/local-inhibition-crosswalk.csv']
|
||||
save(EXP/'protocol/prepared-input-hashes.json', {name:digest(EXP/name) for name in prepared})
|
||||
print('PREPARED: pinned author source, 13 actual motif edges, frozen integrity PASS', flush=True)
|
||||
|
||||
|
||||
def synthetic():
|
||||
m = json.loads((EXP/'protocol/dynamics.json').read_text()); p = params(m)
|
||||
edges = pd.read_csv(EXP/'audit/selected-real-edges.csv'); counts = np.unique(np.r_[1, edges.raw_count.to_numpy(np.int64)])
|
||||
results, traces = [], {}
|
||||
cases = {}
|
||||
for count in counts:
|
||||
a = np.zeros(round(100/m['dt_ms']), np.float32); a[0] = np.float32(count)*np.float32(m['w_syn_mv_per_anatomical_synapse'])
|
||||
cases[f'one_impulse_contacts_{count}'] = a
|
||||
# The separate refractory boundary fixture is a synthetic unit test, not a biological stimulation.
|
||||
a = np.zeros(round(20/m['dt_ms']), np.float32)
|
||||
a[0] = np.float32(2000) # enough for a spike at t=0 in every component policy
|
||||
a[[1,10,20,22,23]] += np.float32(10)
|
||||
cases['refractory_boundary'] = a
|
||||
a = np.zeros(round(200/m['dt_ms']), np.float32); a[:round(100/m['dt_ms'])] = np.float32(10)
|
||||
cases['sustained_then_off'] = a
|
||||
cases['zero_input'] = np.zeros(round(100/m['dt_ms']), np.float32)
|
||||
for name, a in cases.items():
|
||||
for policy in CFG['synthetic_policies']:
|
||||
events, history = single(a, p, policy)
|
||||
repeat = single(a, p, policy)
|
||||
assert np.array_equal(events, repeat[0]) and np.array_equal(history, repeat[1])
|
||||
if policy in ('reset_g_only', 'reset_and_refractory'): assert np.all(history[events,1] == 0)
|
||||
if name.startswith('one_impulse') and policy in ('reset_g_only', 'reset_and_refractory'): assert len(events) <= 1
|
||||
if name == 'zero_input': assert not len(events)
|
||||
if name == 'refractory_boundary' and policy == 'reset_and_refractory':
|
||||
assert np.all(history[:22,1] == 0) and history[22,1] > 0
|
||||
results.append({'case': name, 'policy': policy, 'spikes': len(events),
|
||||
'spike_times_ms': json.dumps((events.astype(float)*m['dt_ms']).tolist()),
|
||||
'post_input_spikes': int((events >= 1000).sum()) if name == 'sustained_then_off' else None,
|
||||
'final_g': float(history[-1,1]), 'finite': bool(np.isfinite(history).all()), 'exact_repeat': True,
|
||||
'not_full_Brian2_replication': True})
|
||||
traces[name+'__'+policy] = history
|
||||
traces[name+'__input'] = a
|
||||
pd.DataFrame(results).to_csv(EXP/'synthetic/results.csv', index=False)
|
||||
np.savez_compressed(EXP/'synthetic/traces.npz', **traces)
|
||||
save(EXP/'regression/synthetic.json', {'pass': True, 'cases': len(cases), 'policies': 4,
|
||||
'proves_only_operator_difference': True, 'brain_model_modified': False, 'Brian2_executed': False})
|
||||
|
||||
|
||||
def gpu_motif(rt, nodes, edges, weights, p, steps, seed):
|
||||
import torch
|
||||
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)
|
||||
d = rt.device; qlen = p['delay_steps']+1
|
||||
v = torch.full((n,), float(p['rest']), dtype=torch.float32, device=d); g = torch.zeros_like(v)
|
||||
ref = torch.zeros(n, dtype=torch.int32, device=d); spike = torch.zeros(n, dtype=torch.uint8, device=d); counts = torch.zeros_like(ref)
|
||||
queue = torch.zeros((qlen,n), dtype=torch.float32, device=d); wg = torch.as_tensor(weights, device=d)
|
||||
history = np.empty((steps,3,n), np.float32); ts, ns = [], []
|
||||
for t in range(steps):
|
||||
rt.ext.state_step(v,g,ref,queue[t%qlen],spike,counts,float(p['gd']),float(p['alpha']),float(p['rest']),float(p['reset']),float(p['threshold']),p['ref_steps'])
|
||||
if t == 0 and seed is not None:
|
||||
targets = torch.tensor([lookup[seed]], dtype=torch.int64, device=d); force = torch.ones(1,dtype=torch.uint8,device=d)
|
||||
rt.ext.force_step(v,ref,spike,counts,targets,force,float(p['reset']),p['ref_steps'])
|
||||
active = spike.cpu().numpy().astype(bool)
|
||||
indices = np.flatnonzero(active); ts.extend([t]*len(indices)); ns.extend(nodes[indices].tolist())
|
||||
selected = np.flatnonzero(active[src]); selected = selected[np.argsort(dst[selected],kind='stable')]
|
||||
if len(selected):
|
||||
st = dst[selected]; starts = np.flatnonzero(np.r_[True,st[1:] != st[:-1]])
|
||||
rt.ext.segmented_sum(torch.as_tensor(selected,dtype=torch.int64,device=d),
|
||||
torch.as_tensor(starts,dtype=torch.int64,device=d), torch.as_tensor(st[starts],dtype=torch.int32,device=d),
|
||||
wg,queue[(t+p['delay_steps'])%qlen])
|
||||
history[t] = torch.stack([v,g,ref.float()]).cpu().numpy()
|
||||
return {'step': np.asarray(ts,np.int32),'neuron_idx':np.asarray(ns,np.int32),'counts':counts.cpu().numpy(),
|
||||
'v':v.cpu().numpy(),'g':g.cpu().numpy(),'ref':ref.cpu().numpy(),'history':history,'queue':queue.cpu().numpy()}
|
||||
|
||||
|
||||
def run():
|
||||
pre = json.loads((EXP/'protocol/preregistered.json').read_text())
|
||||
assert all(digest(MODEL/name) == h for name,h in pre['sources'].items())
|
||||
assert not (EXP/'regression/run-start.json').exists(), 'Refusing overwrite'
|
||||
prepared = json.loads((EXP/'protocol/prepared-input-hashes.json').read_text())
|
||||
assert all(digest(EXP/name) == h for name,h in prepared.items()), 'Prepared-input drift'
|
||||
runtime = ROOT/CFG['runtime']; pc = yaml.safe_load((runtime/'phase4p.yaml').read_text())
|
||||
assert pc['model'] == json.loads((EXP/'protocol/dynamics.json').read_text())
|
||||
suite = unittest.defaultTestLoader.discover(str(MODEL), pattern='test_math.py')
|
||||
test_result = unittest.TextTestRunner(verbosity=2).run(suite)
|
||||
assert test_result.wasSuccessful() and test_result.testsRun == 7
|
||||
save(EXP/'regression/unit-tests.json', {'pass': True, 'tests': test_result.testsRun})
|
||||
queues(); os.environ['CUDA_VISIBLE_DEVICES'] = pc['device']['visible_gpu_uuid']
|
||||
os.environ['CUDA_HOME'] = '/usr/local/cuda'; os.environ['MAX_JOBS'] = '2'; os.environ['TORCH_CUDA_ARCH_LIST'] = '8.9'
|
||||
os.environ['PATH'] = str(ROOT/'models/male-cns-lif/.venv/bin')+':/usr/local/cuda/bin:'+os.environ['PATH']
|
||||
sys.path.insert(0,str(ROOT/'models/male-cns-lif')); sys.path.insert(0,str(runtime))
|
||||
import torch, phase4p
|
||||
free,total = torch.cuda.mem_get_info(); assert free > 2*1024**3
|
||||
save(EXP/'regression/run-start.json', {'free_VRAM': free,'device':torch.cuda.get_device_name(),
|
||||
'whole_brain_uses_only_frozen_reference':True,'alternative_policies_only_synthetic_single_cell':True})
|
||||
start = time.perf_counter(); torch.cuda.reset_peak_memory_stats()
|
||||
rt = phase4p.CanonicalRuntime(pc,runtime,EXP); p = params(pc['model'])
|
||||
synthetic()
|
||||
edges = pd.read_csv(EXP/'audit/selected-real-edges.csv').sort_values('edge_id')
|
||||
eid=edges.edge_id.to_numpy(np.int64); src=edges.source_neuron_idx.to_numpy(np.int32)
|
||||
assert np.all(eid >= rt.indptr_np[src]) and np.all(eid < rt.indptr_np[src+1])
|
||||
assert np.array_equal(rt.indices_np[eid],edges.target_neuron_idx)
|
||||
assert np.array_equal(rt.raw_np[eid],edges.raw_count)
|
||||
save(EXP/'regression/anatomical-edge-validation.json', {'pass':True,'edges':len(edges),
|
||||
'checks':['canonical edge_id in source CSR row','exact target','exact raw int64 count','exact effective float32 weight']})
|
||||
effective = rt.weights.cpu().numpy(); rows, regress = [], []
|
||||
assert np.array_equal(effective[edges.edge_id], edges.raw_count.to_numpy(np.float32)*np.float32(pc['model']['w_syn_mv_per_anatomical_synapse']))
|
||||
for spec in CFG['motifs']:
|
||||
queues(); nodes = np.sort(np.array(spec['nodes'],np.int32)); subset = edges[edges.source_neuron_idx.isin(nodes)&edges.target_neuron_idx.isin(nodes)].copy()
|
||||
if 'pairs' in spec:
|
||||
pairs = {tuple(x) for x in spec['pairs']}
|
||||
subset = subset[[tuple(x) in pairs for x in subset[['source_neuron_idx','target_neuron_idx']].to_numpy()]]
|
||||
assert len(subset) == len(pairs)
|
||||
w = effective[subset.edge_id]; seed = CFG['seed_node'] if spec.get('force',True) else None
|
||||
steps = round(CFG['duration_ms']/pc['model']['dt_ms']); t0 = time.perf_counter()
|
||||
cpu = cpu_motif(nodes,subset,w,p,steps,seed); gpu = gpu_motif(rt,nodes,subset,w,p,steps,seed)
|
||||
eq = {key:bool(np.array_equal(value,gpu[key])) for key,value in cpu.items()}
|
||||
save(EXP/'regression'/(spec['name']+'.json'), {'exact':eq,'pass':all(eq.values())}); assert all(eq.values()),eq
|
||||
if seed is None: assert len(cpu['step']) == 0
|
||||
subset.to_csv(EXP/'motifs'/(spec['name']+'-edges.csv'),index=False)
|
||||
pd.DataFrame({'step':cpu['step'],'neuron_idx':cpu['neuron_idx']}).to_parquet(EXP/'motifs'/(spec['name']+'-events.parquet'),index=False)
|
||||
np.savez_compressed(EXP/'motifs'/(spec['name']+'-state.npz'), nodes=nodes, **{k:v for k,v in cpu.items() if k not in ('step','neuron_idx')})
|
||||
late = cpu['step'] >= round(CFG['late_start_ms']/pc['model']['dt_ms'])
|
||||
for node in nodes:
|
||||
chosen = cpu['neuron_idx'] == node
|
||||
rows.append({'motif':spec['name'],'neuron_idx':int(node),'spikes':int(chosen.sum()),
|
||||
'late_spikes':int((chosen&late).sum()),'first_ms':float(cpu['step'][chosen][0]*pc['model']['dt_ms']) if chosen.any() else None,
|
||||
'last_ms':float(cpu['step'][chosen][-1]*pc['model']['dt_ms']) if chosen.any() else None,
|
||||
'external_force_requests':int(seed == node),'frozen_math_unchanged':True,'whole_brain_necessity_proven':False})
|
||||
regress.append({'motif':spec['name'],'CPU_GPU_exact':True,'wall_seconds':time.perf_counter()-t0})
|
||||
print(json.dumps({'motif':spec['name'],'events':len(cpu['step']),'late_events':int(late.sum()),'exact':True}),flush=True)
|
||||
pd.DataFrame(rows).to_csv(EXP/'motifs/summary.csv',index=False)
|
||||
save(EXP/'regression/motifs.json',regress)
|
||||
refs=[]
|
||||
for spec in pc['reference_trials']:
|
||||
queues(); base = ROOT/'experiments/phase4p-deterministic-runtime/phase4c-v2/reference'/spec['name']
|
||||
ev=pd.read_parquet(base/'spike-events.parquet')
|
||||
expected={'step':ev.step.to_numpy(),'neuron_idx':ev.neuron_idx.to_numpy(),
|
||||
'counts':np.load(base/'per-neuron-spike-counts.int32.npy'),'v':np.load(base/'final-voltage.float32.npy'),'g':np.load(base/'final-g.float32.npy')}
|
||||
r=rt.run(np.flatnonzero(rt.pops[spec['population']]),spec['rate_hz'],spec['seed'],spec['duration_ms'])
|
||||
eq={k:bool(np.array_equal(r[k],v)) for k,v in expected.items()};assert all(eq.values()),eq
|
||||
refs.append({'trial':spec['name'],'exact':eq,'wall_seconds':r['metrics']['wall_seconds']})
|
||||
print(json.dumps({'frozen_trial':spec['name'],'exact':True}),flush=True)
|
||||
save(EXP/'regression/frozen-five.json',{'pass':True,'trials':refs,'oracle':'frozen deterministic-v2, not Brian2'})
|
||||
save(EXP/'performance/summary.json',{'wall_seconds':time.perf_counter()-start,'peak_allocated_VRAM':int(torch.cuda.max_memory_allocated()),
|
||||
'not_runtime_benchmark':True,'ComfyUI_unloaded':False})
|
||||
save(EXP/'audit/run-complete.json',{'synthetic_policies':4,'real_motifs_frozen_only':5,'all_CPU_GPU_exact':True,'learning_achieved':False,
|
||||
'alternative_MaleCNS_dynamics_run':False,'original_author_code_executed':False})
|
||||
|
||||
|
||||
def finalize():
|
||||
assert (EXP/'audit/run-complete.json').exists()
|
||||
post=integrity();assert post==json.loads((EXP/'regression/preflight.json').read_text())
|
||||
save(EXP/'regression/final-integrity.json',post)
|
||||
pre=json.loads((EXP/'protocol/preregistered.json').read_text());assert all(digest(MODEL/n)==h for n,h in pre['sources'].items())
|
||||
from report import write_report
|
||||
write_report(EXP)
|
||||
save(EXP/'audit/final-status.json',{'status':'COMPLETE-SEMANTICS-AUDIT / LEARNING-NOT-ACHIEVED','frozen_integrity_pass':True,
|
||||
'documented_adaptation_not_GPU_regression':True,'new_model_authorization_separate':True,'goal_remains_active':True})
|
||||
for folder in (MODEL,EXP):
|
||||
print('SEALED',folder,seal(folder),flush=True);assert manifest_check(folder,digest)['pass']
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
parser=argparse.ArgumentParser();parser.add_argument('task',choices=['prepare','run','finalize']);a=parser.parse_args();globals()[a.task]()
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"version": "Brian2 2.5.1",
|
||||
"source_commit": "577bb35ec677947d6f3d4af367a8a4b3b88815ee",
|
||||
"method": "Read official source and tests; no executable oracle run",
|
||||
"claims": [
|
||||
{
|
||||
"claim": "Every differential variable flagged unless refractory has writes guarded by not_refractory; this includes g. Reset explicitly bypasses the guard.",
|
||||
"source": "https://github.com/brian-team/brian2/blob/577bb35ec677947d6f3d4af367a8a4b3b88815ee/brian2/groups/neurongroup.py",
|
||||
"lines_1based": [[794,802],[334,351]]
|
||||
},
|
||||
{
|
||||
"claim": "Default non-legacy refractory release compares integer elapsed steps >= refractory steps; with 2.2 ms and dt 0.1 ms release is n+22.",
|
||||
"source": "https://github.com/brian-team/brian2/blob/577bb35ec677947d6f3d4af367a8a4b3b88815ee/brian2/groups/neurongroup.py",
|
||||
"lines_1based": [[178,198],[215,239]]
|
||||
},
|
||||
{
|
||||
"claim": "Default schedule places groups before thresholds, then synapses, then resets. Newly accepted g at n+22 is integrated in the next groups slot; this is not a 23-step refractory duration.",
|
||||
"source": "https://github.com/brian-team/brian2/blob/577bb35ec677947d6f3d4af367a8a4b3b88815ee/brian2/core/network.py",
|
||||
"lines_1based": [[34,42]]
|
||||
},
|
||||
{
|
||||
"claim": "Official tests assert ten-dt refractory gives ten-dt spike spacing and reject same-tick writes on spiking refractory targets.",
|
||||
"source": "https://github.com/brian-team/brian2/blob/577bb35ec677947d6f3d4af367a8a4b3b88815ee/brian2/tests/test_refractory.py",
|
||||
"lines_1based": [[120,127],[232,248]]
|
||||
}
|
||||
],
|
||||
"caveats": [
|
||||
"Assumes default schedule/clocks and legacy.refractory_timing=False.",
|
||||
"The version 2.5.1 prose refractory documentation uses an inconsistent <= example; use pinned implementation and tests for the release boundary.",
|
||||
"Synthetic reset/refractory ablations in this audit retain frozen Euler and arrival timing; they are not a Brian2 reproduction."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
484296e132a836900efaa336d387b6974aa39c6af2dee211b7ba668cf71be257 PROTOCOL.md
|
||||
f20724387ca7ed63b9e35fb9ce3d2964da44f85318856b7403b5536a442422b8 audit_semantics.py
|
||||
bebd4dfa2dda1aca601a52b24e3eabdd887ce261c3548e377198ab154d81f16e brian2-evidence.json
|
||||
433151183a4755f5fdfec33f6a9404aa5fb186ec45866ebb58be4ba3e4ebb568 config.yaml
|
||||
5166366251f99359b1301f514f5d9ae5f0f041fd3a3f98902386192117f01aea math_reference.py
|
||||
76a21eca32ff8caba88ab84616e226e687dd0d2df5da62816c81b5ba08297394 report.py
|
||||
9ee96ed78bfa692b12f0db6862087a19dc351c4afd9bb94cf02733ec6a002f01 test_math.py
|
||||
@@ -0,0 +1,33 @@
|
||||
phase: 4AC
|
||||
root: /data/digital-drosophila
|
||||
model: models/male-cns-lif/lif-semantics-audit-v1
|
||||
experiment: experiments/phase4ac-lif-semantics
|
||||
runtime: models/male-cns-lif/deterministic-runtime-v2
|
||||
canonical: datasets/male-cns-v1.0/processed/neuron-only
|
||||
old_snapshot: registry/snapshots/bootstrap-20260929
|
||||
additional_frozen:
|
||||
- models/male-cns-lif/apl-pn-scope-audit-v1
|
||||
- experiments/phase4aa-apl-pn-scope
|
||||
- models/male-cns-lif/terminal-scope-diagnostic-v1
|
||||
- experiments/phase4ab-terminal-scope
|
||||
author:
|
||||
repository: https://github.com/philshiu/Drosophila_brain_model
|
||||
commit: 91bdd1e7dcf193f3e7ca5a8933497fcef63b7960
|
||||
model_sha256: fc45837d7122c6ce2a7f3f2f23c515992e4b232aadb919efabb72337fac88e4e
|
||||
paper: https://www.nature.com/articles/s41586-024-07763-9
|
||||
selected_nodes: [1197, 1381, 1511, 82312, 133666]
|
||||
seed_node: 1381
|
||||
duration_ms: 1000
|
||||
late_start_ms: 500
|
||||
motifs:
|
||||
- {name: pair, nodes: [1381, 1511]}
|
||||
- {name: trio, nodes: [1197, 1381, 1511]}
|
||||
- {name: five, nodes: [1197, 1381, 1511, 82312, 133666]}
|
||||
- {name: feedforward, nodes: [1197, 1381, 1511, 82312, 133666], pairs: [[1381,1511],[1511,1197],[1197,133666],[133666,82312]]}
|
||||
- {name: no_input, nodes: [1197, 1381, 1511, 82312, 133666], force: false}
|
||||
synthetic_policies: [frozen, reset_g_only, refractory_gate_only, reset_and_refractory]
|
||||
scope:
|
||||
full_brain_alternative_dynamics: false
|
||||
conditioning: false
|
||||
learned_candidate: false
|
||||
requires_separate_authorization_for_new_brain_model: true
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"created_utc": "2026-09-29T16:58:20.616795+00:00",
|
||||
"files": [
|
||||
{
|
||||
"bytes": 3661,
|
||||
"path": "PROTOCOL.md",
|
||||
"sha256": "484296e132a836900efaa336d387b6974aa39c6af2dee211b7ba668cf71be257"
|
||||
},
|
||||
{
|
||||
"bytes": 18769,
|
||||
"path": "audit_semantics.py",
|
||||
"sha256": "f20724387ca7ed63b9e35fb9ce3d2964da44f85318856b7403b5536a442422b8"
|
||||
},
|
||||
{
|
||||
"bytes": 1963,
|
||||
"path": "brian2-evidence.json",
|
||||
"sha256": "bebd4dfa2dda1aca601a52b24e3eabdd887ce261c3548e377198ab154d81f16e"
|
||||
},
|
||||
{
|
||||
"bytes": 1464,
|
||||
"path": "config.yaml",
|
||||
"sha256": "433151183a4755f5fdfec33f6a9404aa5fb186ec45866ebb58be4ba3e4ebb568"
|
||||
},
|
||||
{
|
||||
"bytes": 3768,
|
||||
"path": "math_reference.py",
|
||||
"sha256": "5166366251f99359b1301f514f5d9ae5f0f041fd3a3f98902386192117f01aea"
|
||||
},
|
||||
{
|
||||
"bytes": 5237,
|
||||
"path": "report.py",
|
||||
"sha256": "76a21eca32ff8caba88ab84616e226e687dd0d2df5da62816c81b5ba08297394"
|
||||
},
|
||||
{
|
||||
"bytes": 2902,
|
||||
"path": "test_math.py",
|
||||
"sha256": "9ee96ed78bfa692b12f0db6862087a19dc351c4afd9bb94cf02733ec6a002f01"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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}
|
||||
@@ -0,0 +1,49 @@
|
||||
"""Derive a descriptive report, never pick a model winner from activity reduction."""
|
||||
import json
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def write_report(exp):
|
||||
synthetic = pd.read_csv(exp/'synthetic/results.csv')
|
||||
motifs = pd.read_csv(exp/'motifs/summary.csv')
|
||||
one = synthetic[synthetic['case'].str.startswith('one_impulse')]
|
||||
combined = one.pivot(index='case',columns='policy',values='spikes')
|
||||
lines = [
|
||||
'# Phase 4AC — LIF semantics and unchanged-dynamics motif audit', '',
|
||||
'Status: COMPLETE-SEMANTICS-AUDIT / LEARNING-NOT-ACHIEVED.',
|
||||
'No alternative dynamics deployed to MaleCNS; no conditioning, learned candidate or new formal runtime.', '',
|
||||
'## Author model versus frozen adapted model', '',
|
||||
'Pinned author model resets g on a spike and declares v/g `(unless refractory)`, uses linear integration, strict > threshold and voltage Poisson input with zero refractory on stimulated cells.',
|
||||
'The frozen adapted model retains g, decays/accepts events during refractory, uses explicit Euler and >= threshold, and forced sensory spikes respect refractory.',
|
||||
'These are documented model choices, not a newly established GPU regression. g is an additive voltage-valued drive, not a measured conductance.',
|
||||
'W_syn=0.275 is transferred from a free parameter calibrated in the original FlyWire feeding model, not a measured universal MaleCNS synaptic efficacy.', '',
|
||||
'## Synthetic single-cell component tests', '',
|
||||
'All impulse amplitudes derive from the complete unique contact counts of the predeclared 13 real motif edges plus a one-contact fixture; other boundary/sustained cases are explicitly synthetic unit tests.',
|
||||
'These ablations retain frozen Euler arithmetic/scheduling and are NOT complete Brian2 replication. No successful amplitude or policy was selected after seeing results.', '',
|
||||
'| Single-impulse fixture | Frozen | Reset g only | Refractory gate only | Reset + gate |',
|
||||
'|---|---:|---:|---:|---:|']
|
||||
for name,row in combined.iterrows():
|
||||
lines.append(f"| {name} | {int(row.frozen)} | {int(row.reset_g_only)} | {int(row.refractory_gate_only)} | {int(row.reset_and_refractory)} |")
|
||||
lines += ['', 'Reset-only and combined policies can produce at most one spike from a single isolated input by construction. This does not establish biological correctness or whole-brain learning improvement.', '',
|
||||
'## Real anatomical motifs — frozen mathematics ONLY', '',
|
||||
'One forced seed spike at step 0 except the no-input control; no subsequent input; observed for 1 s. Late window=500–1,000 ms.',
|
||||
'The recurrent pair/trio/five are induced subgraphs. The feedforward control selects four existing edges and removes feedback only within this isolated diagnostic harness.',
|
||||
'External inhibition, graded APL and other whole-brain inputs are absent. A sustained isolated motif is evidence of sufficiency in this harness, NOT necessity in the complete brain.', '',
|
||||
'| Motif | neuron_idx | Total spikes | Late spikes | Last ms |', '|---|---:|---:|---:|---:|']
|
||||
for r in motifs.itertuples():
|
||||
lines.append(f'| {r.motif} | {r.neuron_idx} | {r.spikes} | {r.late_spikes} | {r.last_ms if pd.notna(r.last_ms) else "none"} |')
|
||||
perf=json.loads((exp/'performance/summary.json').read_text())
|
||||
lines += ['', '## Verification and boundaries', '',
|
||||
'Seven unit tests; all five real motifs CPU/GPU event, count, final V/g/refractory, queue and timestep history exact; five archived deterministic-v2 full-brain trials exact.',
|
||||
'Every selected edge_id, source CSR row, destination and original weight validated; generated prepared inputs hashed. All original snapshot files and Phase 4AA/4AB model/experiment manifests checked before and after.',
|
||||
f"Diagnostic wall time including compilation and observation: {perf['wall_seconds']:.3f} s. Peak allocated process VRAM: {perf['peak_allocated_VRAM']} bytes. Not a runtime-speed benchmark.",
|
||||
'Original author code was inspected, not executed; no Brian2 oracle run and no new package installation. ComfyUI was not stopped or unloaded.', '',
|
||||
'## Decision', '',
|
||||
'The usable-learning gate remains unmet: independent evaluation, A-vs-neutral-B specificity in MBON/downstream, multiple training seeds and conditioning controls still required.',
|
||||
'Before any full-network alternative, obtain explicit separate model authorization and distinguish each semantic component; do not claim g reset alone reproduces the author implementation.',
|
||||
'No evidence here permits changing eta, thresholds, gain, frozen APL calibration, transmitter signs or the plastic mask.', '',
|
||||
'## Primary provenance', '',
|
||||
'[Shiu et al. 2024](https://www.nature.com/articles/s41586-024-07763-9)',
|
||||
'[Pinned author implementation](https://github.com/philshiu/Drosophila_brain_model/blob/91bdd1e7dcf193f3e7ca5a8933497fcef63b7960/model.py)',
|
||||
'Downloaded source, MIT license, environment specification, exact commit, SHA-256 and source clauses retained under external-resources and audit.', '']
|
||||
with (exp/'REPORT.md').open('x',encoding='utf-8') as f: f.write('\n'.join(lines))
|
||||
@@ -0,0 +1,59 @@
|
||||
"""Independent fixtures; no access to the MaleCNS raw data or experiment outputs."""
|
||||
import unittest
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from math_reference import params, state, single, cpu_motif
|
||||
|
||||
|
||||
class Operators(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.p = params(dict(dt_ms=.1, synaptic_tau_ms=5., membrane_tau_ms=20.,
|
||||
resting_potential_mv=-52., reset_potential_mv=-52., threshold_mv=-45.,
|
||||
refractory_ms=2.2, synaptic_delay_ms=1.8))
|
||||
|
||||
def test_zero_input_and_exact_repeat(self):
|
||||
for policy in ('frozen','reset_g_only','refractory_gate_only','reset_and_refractory'):
|
||||
a, h = single(np.zeros(1000,np.float32),self.p,policy)
|
||||
self.assertEqual(len(a),0)
|
||||
np.testing.assert_array_equal(h[:,0],np.full(1000,-52,np.float32))
|
||||
self.assertTrue(np.isfinite(h).all())
|
||||
np.testing.assert_array_equal(h,single(np.zeros(1000,np.float32),self.p,policy)[1])
|
||||
|
||||
def test_single_spike_reset(self):
|
||||
a = np.zeros(1000,np.float32); a[0] = 2000
|
||||
for policy in ('reset_g_only','reset_and_refractory'):
|
||||
ev, h = single(a,self.p,policy)
|
||||
np.testing.assert_array_equal(ev,[0]); self.assertEqual(h[0,1],0)
|
||||
|
||||
def test_refractory_release_at_22(self):
|
||||
a = np.zeros(30,np.float32); a[0] = 2000; a[1:23] = 10
|
||||
ev, h = single(a,self.p,'reset_and_refractory')
|
||||
self.assertEqual(ev[0],0)
|
||||
np.testing.assert_array_equal(h[:22,1],np.zeros(22))
|
||||
self.assertEqual(h[21,2],1); self.assertEqual(h[22,2],0)
|
||||
self.assertEqual(h[22,1],10)
|
||||
|
||||
def test_inhibitory_arrival_sign_preserved(self):
|
||||
v,g,r,f = state(np.array([-52],np.float32),np.zeros(1,np.float32),
|
||||
np.zeros(1,np.int32),np.array([-10],np.float32),self.p)
|
||||
self.assertLess(v[0],-52); self.assertEqual(g[0],-10); self.assertFalse(f[0])
|
||||
|
||||
def test_exact_delay_and_no_duplicate_count(self):
|
||||
edges = pd.DataFrame(dict(edge_id=[17],source_neuron_idx=[100],target_neuron_idx=[200]))
|
||||
r = cpu_motif(np.array([100,200],np.int32),edges,np.array([1],np.float32),self.p,40,100)
|
||||
np.testing.assert_array_equal(r['step'],[0]); np.testing.assert_array_equal(r['counts'],[1,0])
|
||||
self.assertTrue((r['history'][:18,1,1] == 0).all())
|
||||
self.assertEqual(r['history'][18,1,1],1)
|
||||
np.testing.assert_array_equal(r['queue'],np.zeros((19,2),np.float32))
|
||||
|
||||
def test_ref_gate_is_not_identical_to_reset(self):
|
||||
a = np.zeros(60,np.float32); a[0] = 2000
|
||||
_, frozen = single(a,self.p,'frozen')
|
||||
_, gate = single(a,self.p,'refractory_gate_only')
|
||||
self.assertLess(frozen[1,1],frozen[0,1]); self.assertEqual(gate[1,1],gate[0,1])
|
||||
|
||||
def test_unknown_policy_rejected(self):
|
||||
with self.assertRaises(ValueError): single(np.zeros(1,np.float32),self.p,'guess')
|
||||
|
||||
|
||||
if __name__ == '__main__': unittest.main(verbosity=2)
|
||||
Reference in New Issue
Block a user