Audit fixed D18 credit and conditional MBON transfer (Phase 4AL)
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
"""CPU-only read-only baseline and one predeclared conditional sensitivity."""
|
||||
from concurrent.futures import ProcessPoolExecutor, as_completed
|
||||
from functools import lru_cache
|
||||
import time
|
||||
import traceback
|
||||
import numpy as np
|
||||
from context import (ROOT, EXP, AK, CORE, COEFF, CFG, PINS, read, save, digest,
|
||||
load_module, check, budget, bit_equal, write_npz)
|
||||
from credit import replay_credit
|
||||
from mbon_accounting import MBONAccounting
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def prepared():
|
||||
core = load_module('sealed_ah_core_al', CORE, PINS[CORE.relative_to(ROOT).as_posix()])
|
||||
with np.load(EXP / 'protocol/incoming.npz', allow_pickle=False) as f:
|
||||
data = {k: f[k].copy() for k in f.files}
|
||||
incoming = core.IncomingIndex(data['src_global'], data['dst_local'], data['edge_id'], data['weights'], len(data['targets']))
|
||||
altered = data['weights'].copy()
|
||||
altered[data['is_candidate']] *= np.float64(1.0 - CFG['conditional_depression_fraction'])
|
||||
assert bit_equal(data['weights'][~data['is_candidate']], altered[~data['is_candidate']])
|
||||
assert np.array_equal(np.sign(data['weights']), np.sign(altered))
|
||||
depressed = core.IncomingIndex(data['src_global'], data['dst_local'], data['edge_id'], altered, len(data['targets']))
|
||||
for array in data.values():
|
||||
array.setflags(write=False)
|
||||
return core, data, incoming, depressed, read(COEFF)
|
||||
|
||||
|
||||
def compare_results(base, changed, base_history, changed_history):
|
||||
event_equal = all(bit_equal(base[k], changed[k]) for k in ('step', 'neuron_idx'))
|
||||
count_changes = base['counts'] != changed['counts']
|
||||
latency_changes = base['first_spike'] != changed['first_spike']
|
||||
return dict(event_sequence_changed=not event_equal,
|
||||
spike_count_L1=int(np.abs(base['counts'].astype(np.int64) - changed['counts']).sum()),
|
||||
neurons_with_count_change=int(count_changes.sum()), neurons_with_first_latency_change=int(latency_changes.sum()),
|
||||
baseline_spikes=int(base['counts'].sum()), depressed_spikes=int(changed['counts'].sum()),
|
||||
max_abs_V_difference_SI=float(np.max(np.abs(base_history['history_v'] - changed_history['history_v']))),
|
||||
max_abs_g_difference_SI=float(np.max(np.abs(base_history['history_g'] - changed_history['history_g']))),
|
||||
final_V_max_abs_difference_SI=float(np.max(np.abs(base['v'] - changed['v']))),
|
||||
final_g_max_abs_difference_SI=float(np.max(np.abs(base['g'] - changed['g']))),
|
||||
functional_count_or_first_latency_change=bool(count_changes.any() or latency_changes.any()),
|
||||
result_scope='Conditional clamped-upstream replay only; all target threshold/reset/ref recalculated; not whole-brain or learning')
|
||||
|
||||
|
||||
def audit_trial(row, stage):
|
||||
start = time.perf_counter()
|
||||
key = row['name'] + '-r' + str(row['repeat'])
|
||||
out = EXP / 'trials' / key
|
||||
assert stage in ('baseline', 'conditional25')
|
||||
if stage == 'baseline':
|
||||
out.mkdir(exist_ok=False)
|
||||
else:
|
||||
assert read(EXP / 'audit/baselines-complete.json')['pass_all'], 'All 144 baseline replays must pass first'
|
||||
assert out.is_dir() and not (out / 'summary.json').exists()
|
||||
case = AK / 'cases' / row['cohort'] / row['name']
|
||||
trial = AK / 'gpu' / row['cohort'] / key
|
||||
spec = read(case / 'spec.json')
|
||||
assert spec['steps'] == CFG['steps'] and spec['dt_ms'] == CFG['dt_ms'] and spec['apl_delay_steps'] == 18
|
||||
assert all(spec[k] == row[k] for k in ('name', 'seed', 'mode', 'odor_id'))
|
||||
assert read(trial / 'summary.json')['complete'] and read(trial / 'summary.json')['pass_all']
|
||||
with np.load(trial / 'state.npz', allow_pickle=False) as f:
|
||||
state = {k: f[k].copy() for k in f.files}
|
||||
assert set(state) == {'step', 'neuron_idx', 'counts', 'v', 'g', 'ref'}
|
||||
with np.load(case / 'requests.npz', allow_pickle=False) as f:
|
||||
requests = {k: f[k].copy() for k in f.files}
|
||||
core, data, incoming, depressed, coeff = prepared()
|
||||
targets, steps = data['targets'], spec['steps']
|
||||
assert not np.isin(requests['neuron_idx'], targets).any(), 'Unexpected MBON injection: do not reinterpret it'
|
||||
v0 = np.full(len(targets), spec['model']['resting_potential_mv'], np.float64) * np.float64(.001)
|
||||
g0 = np.zeros(len(targets), np.float64)
|
||||
events = dict(step=state['step'], neuron_idx=state['neuron_idx'])
|
||||
if stage == 'baseline':
|
||||
observer = MBONAccounting(incoming, data['groups'], tuple(CFG['groups']), coeff, steps)
|
||||
result = core.replay(v0, g0, coeff, steps, targets, requests, events, incoming,
|
||||
np.zeros((steps, len(targets)), np.float32), observer)
|
||||
selected = np.isin(state['neuron_idx'], targets)
|
||||
exact = {k: bit_equal(result[k], state[k][targets]) for k in ('counts', 'v', 'g', 'ref')}
|
||||
exact.update({k: bit_equal(result[k], state[k][selected]) for k in ('step', 'neuron_idx')})
|
||||
assert all(exact.values()), 'Baseline MBON divergence: ' + str(exact)
|
||||
bt, be, bs = observer.finish()
|
||||
write_npz(out / 'baseline-replay.npz', **result)
|
||||
write_npz(out / 'baseline-target-accounting.npz', **bt)
|
||||
pending = np.zeros(len(incoming.edge_id), np.int64)
|
||||
for tick in range(steps - 18, steps):
|
||||
due = incoming.due(state['neuron_idx'][state['step'] == tick])
|
||||
pending[due] += 1
|
||||
be['pending_after_window_count'] = pending
|
||||
# Independent source-spike multiplicity covers due plus pending events.
|
||||
source_counts = np.bincount(state['neuron_idx'], minlength=CFG['neurons'])
|
||||
assert np.array_equal(be['due_count'] + pending, source_counts[incoming.src_global])
|
||||
write_npz(out / 'baseline-edge-accounting.npz', **be)
|
||||
save(out / 'baseline-accounting.json', bs)
|
||||
trace = replay_credit(state['step'], state['neuron_idx'], data['kc_globals'],
|
||||
steps=steps, gate_tick=CFG['virtual_gate_tick'], dt_ms=CFG['dt_ms'],
|
||||
tau_ms=CFG['kc_trace_tau_ms'], cap=CFG['trace_cap'])
|
||||
arrays = {k: value for k, value in trace.items() if isinstance(value, np.ndarray)}
|
||||
write_npz(out / 'credit.npz', **arrays)
|
||||
save(out / 'credit.json', {k: value for k, value in trace.items() if not isinstance(value, np.ndarray)})
|
||||
report = dict(**row, pass_all=True, baseline_exact=exact, due_plus_pending_source_events_exact=True,
|
||||
CPU_wall_seconds=time.perf_counter() - start)
|
||||
save(out / 'baseline-summary.json', report)
|
||||
return report
|
||||
with np.load(out / 'baseline-replay.npz', allow_pickle=False) as f:
|
||||
result = {k: f[k].copy() for k in f.files}
|
||||
with np.load(out / 'baseline-target-accounting.npz', allow_pickle=False) as f:
|
||||
bt = {k: f[k].copy() for k in f.files}
|
||||
baseline_report = read(out / 'baseline-summary.json')
|
||||
assert baseline_report['pass_all'] and all(baseline_report['baseline_exact'].values())
|
||||
exact = baseline_report['baseline_exact']
|
||||
d_observer = MBONAccounting(depressed, data['groups'], tuple(CFG['groups']), coeff, steps)
|
||||
changed = core.replay(v0, g0, coeff, steps, targets, requests, events, depressed,
|
||||
np.zeros((steps, len(targets)), np.float32), d_observer)
|
||||
dt, de, ds = d_observer.finish()
|
||||
write_npz(out / 'conditional25-replay.npz', **changed)
|
||||
write_npz(out / 'conditional25-target-accounting.npz', **dt)
|
||||
write_npz(out / 'conditional25-edge-accounting.npz', **de)
|
||||
save(out / 'conditional25-accounting.json', ds)
|
||||
sensitivity = compare_results(result, changed, bt, dt)
|
||||
save(out / 'sensitivity.json', sensitivity)
|
||||
report = dict(**row, pass_all=True, baseline_exact=exact, sensitivity=sensitivity,
|
||||
credit_gate='Virtual tick900 post-update; no DAN injection or plastic update',
|
||||
biological_duration_ms=spec['duration_ms'], CPU_wall_seconds=time.perf_counter() - start,
|
||||
baseline_CPU_wall_seconds=baseline_report['CPU_wall_seconds'],
|
||||
source_sha256={f: digest(trial / f) for f in ('state.npz', 'summary.json')},
|
||||
conditional_weight_copy_only=True, training=False, candidate_generated=False)
|
||||
save(out / 'summary.json', report)
|
||||
save(out / 'output-hashes.json', [dict(path=p.name, bytes=p.stat().st_size, sha256=digest(p))
|
||||
for p in sorted(out.iterdir()) if p.is_file()])
|
||||
return report
|
||||
|
||||
|
||||
def main():
|
||||
reg = check()
|
||||
assert not any((EXP / 'trials').iterdir()), 'No automatic resume/overwrite of diagnostic outputs'
|
||||
complete = []
|
||||
try:
|
||||
for stage in ('baseline', 'conditional25'):
|
||||
complete = []
|
||||
with ProcessPoolExecutor(max_workers=CFG['workers']) as pool:
|
||||
futures = {pool.submit(audit_trial, row, stage): row for row in reg['planned']}
|
||||
for task in as_completed(futures):
|
||||
result = task.result()
|
||||
complete.append(dict(name=result['name'], repeat=result['repeat'], pass_all=True))
|
||||
budget()
|
||||
print(stage, len(complete), result['name'], result['repeat'],
|
||||
round(result['CPU_wall_seconds'], 3), 'CPU s', flush=True)
|
||||
assert len(complete) == 144
|
||||
if stage == 'baseline':
|
||||
save(EXP / 'audit/baselines-complete.json', dict(pass_all=True, trials=complete, primary=120, repeats=24))
|
||||
except Exception as error:
|
||||
save(EXP / 'audit/failure.json', dict(error=type(error).__name__, message=str(error),
|
||||
traceback=traceback.format_exc(), stage=stage, completed=complete))
|
||||
raise
|
||||
assert len(complete) == 144
|
||||
save(EXP / 'audit/replays-complete.json', dict(pass_all=True, trials=complete, primary=120, repeats=24,
|
||||
meaning='All exact conditional baseline reconstructions; not an overall biological or learning PASS'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user