Files
digital-drosophila/models/male-cns-lif/normal-conditioning-v1/normal_metrics.py
T
2026-09-30 21:00:33 +08:00

529 lines
36 KiB
Python

"""Read-only normal analysis, conditional on independent final integrity.
analyze(exp, registration_sha256, *, verified_inventory, final_ledger) consumes
an externally authenticated absolute-path -> {bytes, sha256} inventory and the
final append-ledger audit. Neither caller Boolean nor digest alone is treated
as an OS observation: actual ledger/start/job/parent/worker receipts and every
consumed file are rebound to bytes. SHA caching is invocation-local, never
mtime-based. The outer verifier still owns complete tree/historical integrity,
anatomical/source authority, concurrency exclusion and the final science claim.
No writes, sampling, subprocesses, follow-up assays, candidates or new gates.
"""
from __future__ import annotations
from collections import defaultdict
import hashlib
import io
import json
from pathlib import Path
import types
import numpy as np
GATES_SHA256 = '8ae9bcb77fe14dbdda164f86ffdbfc1e2976be752766ef526777577223b4dd99'
PLAN_SHA256 = 'd1a6b840a547cb10b00df40f4d8b287867d09f355c9777e5f7a8b9489f5555ea'
ARM_NAMES = dict(paired='paired', d_zero='D=0', frozen='frozen', random_reward='random reward',
unpaired='unpaired', cs_only='CS-only', dan_only='DAN-only')
ODORS = {'A': '4-ethylphenol', 'B': '3-hexanone'}
POINTS = (0, 1, 2, 5, 10, 20, 50)
def require(ok, message):
if not ok:
raise ValueError(message)
def canonical(value):
return json.dumps(value, sort_keys=True, separators=(',', ':'), ensure_ascii=False, allow_nan=False)
def sha(value):
return hashlib.sha256(canonical(value).encode()).hexdigest()
def _module(name, path, expected):
raw = path.read_bytes()
require(hashlib.sha256(raw).hexdigest() == expected, 'Analysis dependency pin differs: ' + str(path))
module = types.ModuleType(name)
module.__file__ = str(path)
exec(compile(raw, str(path), 'exec'), module.__dict__)
return module
_HERE = Path(__file__).resolve().parent
G = _module('_normal_frozen_gates', _HERE.parent / 'conditioning-execution-v1/learning_gates.py', GATES_SHA256)
P = _module('_normal_frozen_plan', _HERE / 'normal_plan.py', PLAN_SHA256)
M = G.M
class Reader:
"""Actual SHA once per unique path per invocation; not an atomic snapshot."""
def __init__(self, inventory):
require(type(inventory) is dict and inventory, 'Externally verified checksum inventory required')
self.inventory = inventory
self.seen = {}
def path(self, path):
path = Path(path)
require(path.is_absolute() and '..' not in path.parts and not any(p.is_symlink() or
(hasattr(p, 'is_junction') and p.is_junction()) for p in (path, *path.parents)), 'Unsafe artifact path')
key = str(path)
require(key in self.inventory, 'Artifact absent from external checksum authority: ' + key)
expected = self.inventory[key]
require(type(expected) is dict and set(expected) == {'bytes', 'sha256'} and
type(expected['bytes']) is int and expected['bytes'] >= 0 and M._sha(expected['sha256']), 'Invalid checksum authority')
return path, expected
def check(self, path):
path, expected = self.path(path)
key = str(path)
if key not in self.seen:
require(path.is_file(), 'Missing artifact: ' + key)
digest = hashlib.sha256()
size = 0
with path.open('rb') as stream:
for part in iter(lambda: stream.read(8 << 20), b''):
digest.update(part); size += len(part)
require(dict(bytes=size, sha256=digest.hexdigest()) == expected, 'Actual file checksum differs: ' + key)
self.seen[key] = dict(expected)
require(self.seen[key] == expected, 'Checksum authority changed during analysis')
return expected
def raw(self, path):
self.check(path)
# Parsed data are read from the now-bound file, not retained for every
# trajectory. Concurrent mutation remains an outer quiescence obligation.
return Path(path).read_bytes()
def json(self, path):
def unique(items):
result = {}
for key, value in items:
require(key not in result, 'Duplicate JSON field'); result[key] = value
return result
return json.loads(self.raw(path).decode('utf-8'), object_pairs_hook=unique,
parse_constant=lambda _: (_ for _ in ()).throw(ValueError('Nonfinite JSON')))
def arrays(self, path):
with np.load(io.BytesIO(self.raw(path)), allow_pickle=False) as archive:
return {key: archive[key].copy() for key in archive.files}
def task_key(task):
return 'task-' + hashlib.sha256(task['task_id'].encode()).hexdigest()
def expected_entries(plan):
result = ['input-generation']
for task in plan['tasks']:
result.extend(task_key(task) + suffix for suffix in
(('--brain', '--update', '--verify') if task['kind'] == 'train' else ('--brain',)))
return result
def audit_case(reader, exp, task, job, *, template, inputs, sampler, populations, population_file, output_cap):
"""Independent raw CS/US composition and reset/spec checks; no brain call.
A scheduled US request is not an accepted spike or a successful behavior.
The artificial D scalar remains the original recipe, never an event count.
"""
exp = Path(exp); case = exp / 'cases' / task_key(task)
require(job['case_dir'] == str(case), 'Case path differs from original task')
spec = reader.json(case / 'spec.json')
removed = ('validation_cohort', 'odor_index', 'expected_external_events', 'training', 'cs_input_seed', 'cs_input_seed_bits',
'seed_scope', 'odor_id', 'odor_name', 'source_panel_row', 'source_panel_split', 'rate_sum_hz', 'target_count',
'expected_CS_requests', 'CS_window', 'outer_epoch_id', 'outer_arm', 'outer_protocol')
fixed = {k: v for k, v in template.items() if k not in removed}
fixed.update(name=task_key(task), steps=1200, duration_ms=120., max_steps=1200, budget_root=str(exp),
output_budget_bytes=output_cap, mode=task['mode'], within_trial_weight_updates=False,
outer_protocol='normal-acquisition-v1', outer_task_id=task['task_id'], outer_arm=task['arm'])
require(canonical(spec) == canonical(fixed) and spec['seed'] == template['seed'] and spec['dt_ms'] == .1 and
spec['apl_delay_steps'] == 18 and spec['precision'] == 'float64' and spec['apl_precision'] == 'float32' and
spec['record_history'] is False and spec['kind'] == 'fullgraph', 'Scientific model/reset/clock spec changed')
recipe = task['recipe']
cs_recipe = recipe['cs'] if task['kind'] == 'train' else recipe
expected = None
if cs_recipe is not None:
cue = cs_recipe['cue']
seed = cs_recipe['seed'] if task['kind'] == 'train' else cs_recipe['input']['seed']
stream = cs_recipe['source_stream_id'] if task['kind'] == 'train' else cs_recipe['input']['stream_id']
require(type(seed) is int and 0 <= seed < 2**63, 'Original full63 seed required')
expected = dict(mode=task['mode'], cue=cue, seed=seed, steps=1200, cs_start_tick=0,
cs_stop_tick_exclusive=1000, source_stream_id=stream, table_id=task['mode'] + '--' + cue)
csid = None if expected is None else sha(expected)
require(inputs['task_inputs'][task['task_id']] == csid, 'Original task/CS descriptor mapping differs')
binding = reader.json(case / 'input-binding.json')
require(binding['task_id'] == task['task_id'] and binding['CS_descriptor_id'] == csid and
binding['DAN_type'] == task['dan_type'] and binding['population_file'] ==
dict(path=population_file, sha256=reader.check(population_file)['sha256']), 'Case input/population binding differs')
empty = dict(tick=np.empty(0, np.int32), neuron_idx=np.empty(0, np.int32))
source = empty
if csid is None:
require(binding['CS_source'] is None, 'No-CS epoch acquired an odor source')
else:
npzpath = exp / f'inputs/generated/{csid}.npz'; jsonpath = exp / f'inputs/generated/{csid}.json'
generated = reader.json(jsonpath); audit = generated['audit']
require(inputs['descriptors'][csid] == generated['descriptor'] == expected and
generated['requests_sha256'] == reader.check(npzpath)['sha256'] and
binding['CS_source'] == dict(path=str(npzpath), sha256=reader.check(npzpath)['sha256']) and
sampler['inputs'][csid] == dict(requests=binding['CS_source'], audit=dict(path=str(jsonpath), sha256=reader.check(jsonpath)['sha256'])),
'Saved sampler receipt/descriptor differs')
require(audit['seed'] == audit['generator_initial_seed'] == seed and audit['seed_bits'] == 63 and
audit['seed_conversion'] == 'exact integer; no truncation/modulo/offset' and audit['private_generator'] is True and
audit['matrix_draw_calls'] == 1 and audit['uniform_matrix_shape'][0] == 1200 and audit['CS_window'] == [0, 1000],
'Original sampler seed/full-shape/window evidence differs')
source = reader.arrays(npzpath)
def keys(request):
require(set(request) == {'tick', 'neuron_idx'} and all(v.dtype == np.int32 and v.ndim == 1 for v in request.values()) and
request['tick'].shape == request['neuron_idx'].shape and np.all((request['tick'] >= 0) & (request['tick'] < 1200)) and
np.all((request['neuron_idx'] >= 0) & (request['neuron_idx'] < 166700)), 'Invalid raw request arrays')
result = request['tick'].astype(np.int64) * 166700 + request['neuron_idx']
require(np.all(result[1:] > result[:-1]), 'Requests are not sorted unique')
return result
source_keys = keys(source)
require(np.all(source['tick'] < 1000), 'CS escaped original window')
cs, us, combined = (reader.arrays(case / name) for name in ('cs_requests.npz', 'us_requests.npz', 'requests.npz'))
cs_keys, us_keys, combined_keys = keys(cs), keys(us), keys(combined)
require(np.array_equal(source_keys, cs_keys), 'Actual case CS differs from original saved input')
requested = task['kind'] == 'train' and recipe['us']['requested']
dan = populations[task['dan_type']]
require(dan.dtype == np.int32 and dan.ndim == 1 and np.all(dan[1:] > dan[:-1]), 'Actual DAN mapping differs')
require(binding['DAN_membership_sha256'] == hashlib.sha256(dan.tobytes()).hexdigest(), 'DAN membership binding differs')
expected_us = 900 * 166700 + dan.astype(np.int64) if requested else np.empty(0, np.int64)
require(np.array_equal(us_keys, expected_us) and not np.intersect1d(cs_keys, us_keys).size and
np.array_equal(combined_keys, np.union1d(cs_keys, us_keys)), 'Actual scheduled US/CS composition differs')
masks = reader.arrays(case / 'source-mask.npz')
expected_mask = np.where(np.isin(combined_keys, cs_keys), 1, 2).astype(np.uint8)
require(set(masks) == {'source_mask'} and masks['source_mask'].dtype == np.uint8 and
np.array_equal(masks['source_mask'], expected_mask) and binding['composition']['policy'] == 'error' and
binding['composition']['cross_source_conflict_count'] == 0, 'Raw composition/source-mask differs')
class Evidence:
def __init__(self, reader, exp, registration_sha, expected, final):
require(final.get('schema') == 'normal-append-ledger-v1-final-audit' and
final.get('registration_sha256') == registration_sha and final.get('pass_all') is True and
final.get('entry_ids') == final.get('expected_entry_ids') == expected and
final.get('expected_order_exact') is True, 'Final ledger authority/complete order differs')
require(all(final.get(k) == [] for k in ('extra_files', 'missing_files', 'changed_files',
'unsafe_files', 'errors', 'uncommitted_entry_ids')) and final.get('uncommitted_state_unknown') is False,
'Incomplete/failed final ledger audit')
self.r, self.exp, self.regsha, self.expected = reader, Path(exp), registration_sha, expected
self.index, self.tail, self.final = 0, None, final
def process(self, entry_id, kind):
self.index += 1
require(self.expected[self.index - 1] == entry_id, 'Analysis process order differs')
entry_path = self.exp / f'.append-ledger/entries/{self.index:016d}.json'
entry = self.r.json(entry_path)
start_path = self.exp / f'.append-ledger/started/{entry_id}.json'
start = self.r.json(start_path)
job_path = self.exp / f'jobs/{entry_id}.json'
process_path = self.exp / 'receipts' / entry_id / 'process.json'
require(entry['entry_id'] == start['entry_id'] == entry_id and entry['sequence'] == start['next_sequence'] == self.index and
entry['registration_sha256'] == start['registration_sha256'] == self.regsha and
entry['prev_entry_sha256'] == start['prev_entry_sha256'] == self.tail and
entry['start_sha256'] == self.r.check(start_path)['sha256'] and start['retry_authorized'] is False,
'Ledger/start chain differs')
incoming = {row['path']: row for row in entry['consumed_inputs']}
outgoing = {row['path']: row for row in entry['new_outputs']}
require(len(incoming) == len(entry['consumed_inputs']) and len(outgoing) == len(entry['new_outputs']), 'Duplicate ledger files')
for path, row in {**incoming, **outgoing}.items():
_, bound = self.r.path(path)
require(all(row[k] == bound[k] for k in ('bytes', 'sha256')), 'Ledger/checksum authority differs')
metadata = entry['metadata']
require(metadata['job_file'] == str(job_path) and metadata['process_receipt_file'] == str(process_path) and
type(metadata['observed_returncode']) is int and metadata['observed_returncode'] == 0 and
str(process_path) in outgoing and str(job_path) in incoming and
start['job']['path'] == str(job_path) and start['job']['sha256'] == self.r.check(job_path)['sha256'],
'Process/job not owned by committed ledger entry')
process, job = self.r.json(process_path), self.r.json(job_path)
require(process['entry_id'] == entry_id and process['registration_sha256'] == job['registration_sha256'] == self.regsha and
process['job_sha256'] == self.r.check(job_path)['sha256'] and process['complete'] is True and
type(process['exit_code']) is int and process['exit_code'] == 0 and process['timed_out'] is False and
process['exit_observed_by_parent'] is True and job['kind'] == kind, 'Actual parent exit/job receipt differs')
for path, digest in job['input_sha256'].items():
require(path in incoming and self.r.check(path)['sha256'] == digest, 'Actual consumed job input differs')
for label in ('stdout', 'stderr'):
path = self.exp / f'logs/{entry_id}.{label}.log'
require(str(path) in outgoing and self.r.check(path)['sha256'] == process[label + '_sha256'], 'Parent log differs')
if kind in ('brain', 'update'):
require(job['receipt_file'] in outgoing and job['receipt_file'] + '.attempt.json' in outgoing,
'Worker/attempt receipt not ledger owned')
worker = self.r.json(job['receipt_file'])
closure = dict(job['input_sha256'])
closure[str(job_path)] = self.r.check(job_path)['sha256']
# The AP worker includes its own exact script source in its closure.
script = process['argv'][2]
closure[script] = self.r.check(script)['sha256']
for value in job['sources'].values():
require(self.r.check(value['path'])['sha256'] == value['sha256'], 'Worker source differs')
closure[value['path']] = value['sha256']
require(worker['schema'] == 'phase4ap-isolated-worker-receipt-v1' and worker['complete'] is True and
worker['job_id'] == entry_id and worker['kind'] == kind and worker['registration_sha256'] == self.regsha and
worker['job_sha256'] == self.r.check(job_path)['sha256'] and worker['verified_input_sha256'] == closure and
worker['attempt_sha256'] == self.r.check(job['receipt_file'] + '.attempt.json')['sha256'], 'Worker/attempt binding differs')
declared = {str(Path(p).relative_to(job['output_dir'])): row['sha256'] for p, row in outgoing.items()
if Path(p).is_relative_to(job['output_dir'])}
require(worker['output_files_sha256'] == declared, 'Worker output coverage differs')
self.tail = self.r.check(entry_path)['sha256']
return dict(job=job, outgoing=outgoing, process_sha256=self.r.check(process_path)['sha256'], process_path=str(process_path))
def finish(self):
require(self.index == len(self.expected) and self.tail == self.final['head_sha256'], 'Missing process or terminal ledger head differs')
def audit_records(reader, exp, reg, regsha, plan, evidence, *, template, inputs, populations):
"""Bind all 33,920 tasks and actual update chains; retain only small indexes."""
sample = evidence.process('input-generation', 'sample')
sampler = reader.json(Path(exp) / 'receipts/input-generation.json')
require(sampler['complete'] is True and sampler['registration_sha256'] == regsha and
str(Path(exp) / 'receipts/input-generation.json') in sample['outgoing'], 'Sampler receipt not ledger owned')
require(sampler['descriptors'] == len(inputs['descriptors']) == 4080 and set(sampler['inputs']) == set(inputs['descriptors']) and
sampler['input_inventory_sha256'] == reader.check(Path(exp) / 'protocol/input-inventory.json')['sha256'], 'Actual sampler inventory incomplete')
previous, probes, arms, record_hashes = {}, {}, defaultdict(list), []
for task in plan['tasks']:
key = task_key(task); result_path = Path(exp) / f'results/{key}.json'
record = reader.json(result_path)
require(record['schema'] == 'normal-task-result-v1' and record['complete'] is True and
record['registration_sha256'] == regsha and canonical(record['task']) == canonical(task), 'Actual result/task differs')
branch = task['block_id'], task['arm']
overlay = previous.get(branch, reg['initial_overlay'])
require(record['overlay_before'] == overlay, 'Delta ancestry changed or probe fed back')
brain = evidence.process(key + '--brain', 'brain')
audit_case(reader, exp, task, brain['job'], template=template, inputs=inputs, sampler=sampler,
populations=populations, population_file=reg['population_file'], output_cap=reg['budget']['output_cap_bytes'])
job = brain['job']; output = Path(exp) / 'runs' / key
require(record['brain_process_receipt_path'] == brain['process_path'] and
record['checkpoint_dir'] == job['output_dir'] == str(output) and
all(job[k] == overlay[k] for k in ('overlay_file', 'overlay_manifest')) and
job['diagnostic_mode'] == 'light', 'Brain result/overlay path differs')
manifest = reader.json(output / 'checkpoint-manifest.json')
require(record['checkpoint_manifest_sha256'] == record['brain_audit']['checkpoint_manifest_sha256'] ==
reader.check(output / 'checkpoint-manifest.json')['sha256'] and
record['brain_audit']['task_id'] == task['task_id'], 'Checkpoint binding differs')
for name, digest in manifest['files_sha256'].items():
require(reader.check(output / name)['sha256'] == digest, 'Checkpoint payload differs')
record_hashes.append([task['task_id'], reader.check(result_path)['sha256']])
if task['kind'] == 'probe':
require(str(result_path) in brain['outgoing'], 'Probe result not ledger owned')
probes[(task['block_id'], task['arm'], task['checkpoint'], task['recipe']['cue'])] = record
continue
update = evidence.process(key + '--update', 'update')
cpu = evidence.process(key + '--verify', 'cpu_verify')
require(record['update_process_receipt_path'] == update['process_path'] and record['cpu_process_receipt_path'] == cpu['process_path'] and
str(result_path) in cpu['outgoing'], 'Training result/parent process ownership differs')
authority = reader.json(record['epoch_authority']['path'])
require(reader.check(record['epoch_authority']['path'])['sha256'] == record['epoch_authority']['sha256'] ==
update['job']['authority_sha256'] == cpu['job']['authority_sha256'] and authority['epoch_id'] == task['task_id'] and
authority['checkpoint_dir'] == str(output) and authority['checkpoint_manifest_sha256'] == record['checkpoint_manifest_sha256'],
'Original E authority differs')
command = dict(steps=1200, compartment=task['compartment_verbatim'], us_tick=task['recipe']['us']['tick'],
d_command=task['recipe']['D_command'], frozen=not task['recipe']['update_enabled'], trace_backend='torch')
require(authority['command'] == command, 'Original E command changed')
valid = reader.json(record['cpu_validation']['path'])
after = dict(overlay_file=str(Path(exp) / 'updates' / key / 'delta-after.npz'),
overlay_manifest=str(Path(exp) / 'updates' / key / 'overlay-manifest.json'))
require(record['update_dir'] == update['job']['output_dir'] == cpu['job']['update_dir'] == str(Path(exp) / 'updates' / key) and
record['cpu_validation']['path'] == cpu['job']['receipt_file'] and record['cpu_validation']['path'] in cpu['outgoing'] and
record['epoch_authority']['path'] == update['job']['authority_file'] == cpu['job']['authority_file'] and
authority['files']['state'] == dict(path=str(output / 'state.npz'), sha256=reader.check(output / 'state.npz')['sha256']) and
authority['files']['overlay_before'] == dict(path=overlay['overlay_file'], sha256=reader.check(overlay['overlay_file'])['sha256']) and
authority['files']['overlay_manifest_before'] == dict(path=overlay['overlay_manifest'], sha256=reader.check(overlay['overlay_manifest'])['sha256']) and
record['after_overlay'] == after and reader.check(record['cpu_validation']['path'])['sha256'] == record['cpu_validation']['sha256'] and
valid['complete'] is True and valid['frozen_U_verify_receipt_recomputed'] is True and valid['actual_sealed_events_used'] is True and
valid['epoch_authority_sha256'] == record['epoch_authority']['sha256'] and
valid['after_overlay_sha256'] == reader.check(after['overlay_file'])['sha256'], 'Independent CPU update validation differs')
if task['arm'] in ('d_zero', 'frozen', 'cs_only'):
require(not reader.arrays(after['overlay_file'])['plastic_delta_mv'].view(np.uint32).any(), 'Zero control contains nonzero delta')
previous[branch] = after
arms[branch].append(dict(result=record_hashes[-1], brain=brain['process_sha256'],
update=update['process_sha256'], cpu=cpu['process_sha256'], requested=task['recipe']['us']['requested']))
evidence.finish()
require(len(probes) == 3920 and len(arms) == 280 and len(record_hashes) == 33920, 'Incomplete actual task inventory')
return probes, arms, sha(record_hashes)
def build_registry(trial, plan, *, nodes, populations, population_binding_sha, regsha, input_hashes):
membership = M.validate_populations(nodes, populations)
blocks = {}
inputs = {(r['block_id'], r['cue']): r for r in plan['evaluation_inputs']}
for block in trial['plan']['blocks']:
identity = block['block_id']
source_rows = [r for r in trial['seed_manifest']['independent_streams']
if r['key'][0] == identity and r['key'][1] == 'normal' and r['split'] in ('training', 'design')]
sources = [dict(stream_id=P._FROZEN['trial_plan']._stream_id(P.MASTER, r['split'], r['key']),
seed=r['seed'], split=r['split']) for r in source_rows]
blocks[identity] = dict(mode=block['mode'], dan_type=block['dan_type'], compartment_verbatim=block['compartment_verbatim'],
conditioned_cue=ODORS[block['initial_reinforced_cue']], replicate=block['replicate'], training_sources=sources,
evaluation_sources={ODORS[c]: {k: inputs[(identity, c)][k] for k in ('stream_id', 'seed', 'split')} for c in ODORS},
evaluation_input_sha256={ODORS[c]: input_hashes[(identity, c)] for c in ODORS})
registry = dict(schema=G.REGISTRY_SCHEMA, protocol_sha256=regsha, population_binding_sha256=population_binding_sha,
nodes_raw_sha256=M._hash(nodes), population_membership_sha256=membership['membership_sha256'], blocks=blocks)
G.validate_registry(registry, expected_sha256=G.descriptor_sha256(registry))
return registry
def compact_metric(metric):
return dict(rawS=metric['rawS'], rawB=metric['rawB'], separation=metric['separation'],
cues={cue: dict(state_pre=summary['pre_state_raw_sha256'], state_post=summary['post_state_raw_sha256'],
populations={name: {k: v for k, v in group.items() if not isinstance(v, list)}
for name, group in summary['populations'].items()}) for cue, summary in metric['cues'].items()})
def scientific_reports(probes, arms, *, registry, authority, nodes, populations, load_state):
"""One cell's terminal vectors at a time; curve outputs contain no raw vectors."""
curves, compact_reports = [], []
def cells():
for mode in M.MODES:
for dan in M.DAN_TYPES:
for conditioned in M.CUES:
rows = []
selected = [(key, b) for key, b in registry['blocks'].items()
if (b['mode'], b['dan_type'], b['conditioned_cue']) == (mode, dan, conditioned)]
for identity, block in selected:
arm_values = {}
for name, legacy_name in ARM_NAMES.items():
pre = {ODORS[c]: load_state(probes[(identity, name, 0, c)]) for c in ODORS}
terminal = None
for point in POINTS:
post = pre if point == 0 else {ODORS[c]: load_state(probes[(identity, name, point, c)]) for c in ODORS}
metric = M.arm_metrics(pre, post, conditioned_cue=conditioned, nodes=nodes, populations=populations)
curves.append(dict(block_id=identity, arm=name, checkpoint=point, metrics=compact_metric(metric)))
if point == 50:
terminal = metric
history = arms[(identity, name)]
require(len(history) == (150 if name == 'unpaired' else 100), 'Missing actual training/control epochs')
execution = dict(complete=True, recording_verified=True, receipt_sha256=sha(history), assay='normal',
epoch_duration_ms=120., cs_duration_ms=100., us_tick=900, completed_epochs=len(history),
reinforcement_opportunities=sum(r['requested'] for r in history), terminal_pairings=50)
arm_values[legacy_name] = dict(metrics=terminal, execution=execution)
rows.append(dict(replicate_id=identity, replicate_unit='independent_training',
training_stream_ids=[x['stream_id'] for x in block['training_sources']],
evaluation_stream_ids=[block['evaluation_sources'][c]['stream_id'] for c in M.CUES],
assay='normal', mode=mode, dan_type=dan, conditioned_cue=conditioned, arms=arm_values))
report = G.evaluate_acquisition(rows, assay='normal', mode=mode, dan_type=dan,
conditioned_cue=conditioned, registry=registry, authority=authority)
compact_reports.append({k: v for k, v in report.items()
if k not in ('acquisition_metric_inputs', 'frozen_acquisition_result')})
yield report
cohort = G.summarize_cohort(cells(), registry=registry, authority=authority, required_assays=('normal',))
require(cohort['complete_inventory'], 'Missing normal cells')
return dict(cells=compact_reports, cohort=cohort, curves=curves)
def legacy_audit(reports, declared_mapping):
"""G.H_MAPPING source layout; call the unchanged M scalar twice per group."""
require(declared_mapping == G.H_MAPPING, 'Explicit original registered H mapping required')
lookup = {(r['mode'], r['dan_type'], r['conditioned_cue']): r for r in reports}
require(len(reports) == len(lookup) == 8, 'Exact eight-cell legacy input required')
groups = []
for mode in M.MODES:
for cue in M.CUES:
selected = {d: lookup[(mode, d, cue)] for d in M.DAN_TYPES}
outcomes = {}
for source in M.DAN_TYPES:
gains = {label: [r['population_separation_gains']['MBON'] for r in selected[dan]['replicates']]
for dan, label in (('PAM15', 'appetitive-conditioning'), ('PPL103', 'aversive-conditioning'))}
for name, label in (('D=0', 'd-zero-control'), ('random reward', 'random-reward-control'), ('frozen', 'frozen-brain-control')):
controls = selected[source]['legacy_control_MBON_gains'][name]
require(set(controls) == {r['replicate_id'] for r in selected[source]['replicates']}, 'Legacy replicate control source differs')
gains[label] = [controls[r['replicate_id']] for r in selected[source]['replicates']]
outcomes[source] = M.legacy_h_mean_control_audit(gains)
groups.append(dict(mode=mode, conditioned_cue=cue, control_sources=outcomes,
both_pass=all(v['predicate_pass'] for v in outcomes.values())))
return dict(schema=G.SCHEMA, status='CONDITIONAL-AUDIT', declared_mapping=declared_mapping, groups=groups,
both_pass=all(g['both_pass'] for g in groups), original_H_data_layout_reproduced=False,
new_stricter_control_source_mapping=True, **G._scope())
def analyze(exp, registration_sha256, *, verified_inventory, final_ledger):
exp, reader = Path(exp), Reader(verified_inventory)
regpath = exp / 'protocol/preregistered.json'
require(reader.check(regpath)['sha256'] == registration_sha256, 'Registration authority differs')
reg = reader.json(regpath)
require(reg['schema'] == 'normal-conditioning-registration-v1' and reg['scientific_scope'] == 'normal-acquisition-only' and
reg['subsequent_assays_authorized'] is False, 'Wrong scientific authority')
for collection in ('source_sha256', 'input_sha256', 'prepared_sha256'):
for path, digest in reg[collection].items():
require(reader.check(path)['sha256'] == digest, 'Registered dependency differs')
plan = reader.json(exp / 'protocol/normal-plan.json')
full = reader.json(reg['original_full_trial_plan']['path'])
require(reader.check(reg['original_full_trial_plan']['path'])['sha256'] == reg['original_full_trial_plan']['sha256'] == P.AO_TRIAL_FILE_SHA256,
'Original AO full plan differs')
evaluation = P.expand_pinned_evaluation(full)
P.validate_normal_plan(plan, full, evaluation)
gates = reader.json(exp / 'protocol/gates.json')
require(gates['H_mapping'] == G.H_MAPPING and gates['all_eight_cells_required'] is True and
gates['same_replicate_conjunction_at_least'] == 3 and gates['training_replicates'] == 5 and gates['terminal_checkpoint'] == 50 and
gates['intermediate_checkpoints_cannot_rescue_terminal'] is True, 'Registered scientific policy differs')
template = reader.json(exp / 'protocol/case-template.json')
nodes_path = Path(template['graph_dir']) / 'nodes.npy'
nodes = np.load(io.BytesIO(reader.raw(nodes_path)), allow_pickle=False)
require(nodes.dtype == np.int32 and len(nodes) == 166700, 'Full original graph nodes required')
require('readout_population_file' in reg, 'Registered full legacy/non-MBON readout populations missing')
populations = reader.arrays(reg['readout_population_file'])
original = reader.arrays(reg['population_file'])
for name in ('MBON', 'MBON01', 'MBON12'):
require(np.array_equal(original[name], populations[name]), 'Original anatomical population changed')
M.validate_populations(nodes, populations)
require('readout_population_binding_file' in reg, 'Registered anatomical binding file missing')
population_binding_sha = reader.check(reg['readout_population_binding_file'])['sha256']
evidence = Evidence(reader, exp, registration_sha256, expected_entries(plan), final_ledger)
inputs = reader.json(exp / 'protocol/input-inventory.json')
probes, arms, inventory_sha = audit_records(reader, exp, reg, registration_sha256, plan, evidence,
template=template, inputs=inputs, populations=original)
input_hashes, seen_receipts = {}, set()
def load_state(record):
task = record['task']; key = task_key(task)
receipt = record['brain_process_receipt_path']
require(receipt not in seen_receipts, 'Probe execution reused across snapshots')
seen_receipts.add(receipt)
csid = inputs['task_inputs'][task['task_id']]
recipe = task['recipe']
expected_descriptor = dict(mode=task['mode'], cue=recipe['cue'], seed=recipe['input']['seed'], steps=1200,
cs_start_tick=0, cs_stop_tick_exclusive=1000, source_stream_id=recipe['input']['stream_id'],
table_id=task['mode'] + '--' + recipe['cue'])
require(csid == sha(expected_descriptor) and inputs['descriptors'][csid] == expected_descriptor,
'Evaluation seed/stream/clock mapping differs from original plan')
source = exp / f'inputs/generated/{csid}.npz'
generated = reader.json(exp / f'inputs/generated/{csid}.json')
require(generated['descriptor'] == inputs['descriptors'][csid] and generated['requests_sha256'] == reader.check(source)['sha256'],
'Actual evaluation input descriptor differs')
case = exp / 'cases' / key
binding = reader.json(case / 'input-binding.json')
require(binding['CS_source'] == dict(path=str(source), sha256=reader.check(source)['sha256']) and
binding['CS_descriptor_id'] == csid and binding['task_id'] == task['task_id'], 'Probe shared input binding differs')
requests, cs, us = (reader.arrays(path) for path in (case / 'requests.npz', source, case / 'us_requests.npz'))
require(set(requests) == set(cs) == set(us) == {'tick', 'neuron_idx'} and
all(not len(v) for v in us.values()) and all(np.array_equal(requests[k], cs[k]) for k in requests), 'Probe includes US or changed CS')
state = reader.arrays(Path(record['checkpoint_dir']) / 'state.npz')
actual = M.validate_state(state, nodes=nodes, steps=1200)
require(actual['hashes'] == record['brain_audit']['state_raw_sha256'], 'Actual probe raw arrays differ')
return state
for item in plan['evaluation_inputs']:
record = probes[(item['block_id'], 'paired', 0, item['cue'])]
csid = inputs['task_inputs'][record['task']['task_id']]
input_hashes[(item['block_id'], item['cue'])] = reader.check(exp / f'inputs/generated/{csid}.npz')['sha256']
registry = build_registry(full, plan, nodes=nodes, populations=populations, population_binding_sha=population_binding_sha,
regsha=registration_sha256, input_hashes=input_hashes)
authority = dict(schema=G.AUTHORITY_SCHEMA, registry_sha256=G.descriptor_sha256(registry),
verification_receipt_sha256=sha(final_ledger), execution_inventory_sha256=inventory_sha,
population_binding_sha256=population_binding_sha)
result = scientific_reports(probes, arms, registry=registry, authority=authority, nodes=nodes, populations=populations, load_state=load_state)
require(len(seen_receipts) == 3920, 'Not every independently executed probe was analyzed')
result['legacy_H'] = legacy_audit(result['cells'], gates['H_mapping'])
return dict(schema='normal-scientific-analysis-v1', status='CONDITIONAL-PENDING-OUTER-INTEGRITY',
registration_sha256=registration_sha256, normal_plan_sha256=plan['payload_sha256'], registry=registry, authority=authority,
analysis=result, analyzed_probe_calls=3920, curve_points=1960, consumed_file_count=len(reader.seen),
subsequent_assays_status='NOT-RUN', subsequent_assays_authorized=False, candidate_generated=False,
learning_pass_claimed=False, scientific_learning_pass_claimed=False, data_backed_claimed=False,
external_full_tree_anatomy_historical_integrity_verified_here=False,
retained_raw_evidence='original state and receipt files; terminal raw metric vectors recomputable, not duplicated in report')