Seal Phase 4AM whole-brain plastic interface audit without learning claim
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# Pre-registration engineering checks
|
||||
|
||||
No new brain trajectory or conditioning result existed during these repairs.
|
||||
Only the new, unregistered AM code was changed; all prior models remained
|
||||
read-only. The new experiment root was provisioned empty and owned by the
|
||||
existing project user before preparation.
|
||||
|
||||
The first server-only unit test run had 74 tests: 73 passed, one failed
|
||||
(`test_static25_repeat_and_single_validation`, copied-input SHA mismatch).
|
||||
The local machine had not reproduced that failure. A direct synthetic probe
|
||||
established the cause, rather than changing a tolerance or expected result:
|
||||
|
||||
- A temporary NPZ was overwritten with different, same-size (556-byte) data.
|
||||
- Its device, inode, length, mtime_ns and ctime_ns were identical before/after.
|
||||
- The fresh SHA changed from
|
||||
`38219e908f1b500423c9fe1a70b6070103f878903c5f005c79e387fca8b449c4` to
|
||||
`fa45373dd04bf5f9d574a5329d42f8259c99c70aa69b077673aaef8c95bf0a02`.
|
||||
- The timestamp-keyed digest cache returned the former SHA for the latter
|
||||
bytes. Both timestamps were `1790749632756475909` in the observed probe.
|
||||
|
||||
The new comparator was corrected to read fresh bytes for SHA verification and
|
||||
to include content hashes in static cache identity. A deliberate same-stat,
|
||||
same-length rewrite test guards this issue. No waiting/sleep workaround, relaxed
|
||||
hash equality, modified old data, or scientific rerun is used to hide it.
|
||||
|
||||
This note is a human-readable preparation record, not a saved raw unit-test
|
||||
transcript. The subsequent complete server test transcript is recorded during
|
||||
formal preparation and must pass without skipped Torch compatibility tests.
|
||||
@@ -0,0 +1,85 @@
|
||||
# Phase 4AM — D18 whole-brain plastic interface
|
||||
|
||||
This is a separate, non-trained branch. All Phase 1–4AL files remain read-only.
|
||||
The user authorized independent new-model dynamics hypothesis tests. No base
|
||||
CSR, transmitter sign, W_syn, neuron threshold, APL calibration, eligibility-v2,
|
||||
or 14,232-edge mask is changed. No game, decoder, RL, or visual input is used.
|
||||
|
||||
## Fixed test order and scope
|
||||
|
||||
1. All 120 sealed AK inputs and their 24 predeclared repeats run with exactly
|
||||
zero plastic_delta. Compare every recorded state bit, event, count, delayed
|
||||
edge queue, request acceptance, and APL generated/applied/pending sample
|
||||
against AK. All must pass before any nonzero overlay runs.
|
||||
2. The same complete input inventory runs with one fixed diagnostic overlay:
|
||||
the original float32 eligibility-v2 floor, `-.25 * base_candidate_mv`.
|
||||
All 14,232 mask edges are included; all other weights remain identical.
|
||||
Twenty-four technical repeats must be exact. This is not learned memory.
|
||||
All presynaptic events are recomputed by the whole brain, unlike AL's
|
||||
clamped-source MBON replay. No outcome selects an odor, compartment, dose,
|
||||
parameter, or number of training pairings.
|
||||
3. Pure-function compatibility tests validate the frozen eligibility trace and
|
||||
original additive apply_v2 rule. No reinforcement or weight update occurs
|
||||
in any of these GPU trials. Fixed delta is installed only before tick zero.
|
||||
|
||||
Report both cohorts, both modes, all odors and all seeds. Technical repeats do
|
||||
not increase the statistical sample. Matched-energy retains its inherited
|
||||
meaning: expected stochastic input, not exactly matched realized event counts.
|
||||
Simulation remains 100 ms, including its unadvanced delay/APL tail.
|
||||
|
||||
## Explicit precision bridge — a new interface assumption
|
||||
|
||||
The existing D18 brain uses float64 SI voltage weights. Frozen eligibility-v2
|
||||
uses float32 mV deltas and float32 candidate base amplitudes. The adapter keeps
|
||||
the original D18 base bytes and adds `float64(delta32) * float64(.001)` using
|
||||
separate non-fused operations, only on nonzero delta rows. Zero delta must not
|
||||
reconstruct the base through float32. Positive original mask weights must
|
||||
remain positive. This is not a claim of bitwise nonzero equivalence to the old
|
||||
float32 brain, nor of precisely 25% of float64 D18 amplitudes. The base candidate
|
||||
is reproduced from the pinned original Phase 4C NumPy expression, not converted
|
||||
back from the float64 brain. The mask and all units are recorded.
|
||||
|
||||
Only the pre-loop weight-install hook is inserted in an AST-derived copy of
|
||||
the SHA-pinned AJ function. Its timestep loop AST and original CUDA operators
|
||||
remain identical. The old module is not patched and its original __file__ is
|
||||
not fabricated. Inherited AJ source metadata describes the parent; additional
|
||||
AM adapter provenance explicitly describes the new overlay. The original spike
|
||||
delay queue stores spike identities and reads fixed weights at arrival. No
|
||||
mid-trial update or implicit weight-snapshot delay rule is introduced.
|
||||
|
||||
## Interpretation and next experiment boundary
|
||||
|
||||
Record MBON, KC, DAN, actual MBON downstream, descending and motor output
|
||||
changes separately, as well as first divergence and recruited/lost KC cells.
|
||||
Static transfer is neither associative acquisition nor a maximal functional
|
||||
effect bound. A weak static response does not prove all sparse overlays fail.
|
||||
The full-brain response is not compared with AL as a strict arithmetic oracle:
|
||||
AL clamps feedback and its float64 0.75 multiplication is a different fixed
|
||||
precision intervention. Any side-by-side descriptive result states both.
|
||||
|
||||
Conditioning must be separately preregistered. Keep the original 4L static
|
||||
ODOR_A (4-ethylphenol) and ODOR_B (3-hexanone), reciprocal cue assignments,
|
||||
type/compartment-specific US and frozen eligibility-v2. PAM/PPL1 alone never
|
||||
defines valence. The original D scalar is an artificial unconditioned-signal
|
||||
interface, not measured dopamine or the number of actually emitted DAN spikes.
|
||||
Log requests, acceptance, and actual DAN spikes separately. Multiplying D by
|
||||
actual accepted spikes would be a different explicitly registered hypothesis,
|
||||
not an unnoticed repair of eligibility-v2. No such change is made here.
|
||||
|
||||
Later functional learning requires independent evaluation seeds, actual paired
|
||||
and control trajectories, MBON and downstream specificity, reversal and real
|
||||
time retention. Neither file validity, delta change nor a static response
|
||||
completes the learning goal. This stage creates no learned candidate.
|
||||
|
||||
## Integrity
|
||||
|
||||
Exclusive-create outputs, a pre-outcome source/input/config manifest, fixed
|
||||
trial inventory, unit-test records, complete per-trial recording hashes,
|
||||
pre/post frozen integrity, and the five unchanged deterministic-v2 reference
|
||||
trials are required. Partial outputs are preserved and never overwritten.
|
||||
No queued ComfyUI job is evicted. Only the declared RTX 4090 is used. The old
|
||||
references are regression checks of the old runtime, not proof of D18 biology.
|
||||
|
||||
The biological limitations and primary-paper evidence are inherited unchanged
|
||||
from sealed AL EVIDENCE.md; this precision/interface implementation introduces
|
||||
no new single-cell physiological measurement or universal dopamine rule.
|
||||
@@ -0,0 +1,248 @@
|
||||
"""One pre-loop overlay hook in the SHA-pinned AJ function; no old global patch.
|
||||
|
||||
run_gpu_overlay(case_dir, out_dir, build_root, overlay_file, overlay_manifest)
|
||||
retains AJ's output/summary schema. overlay-audit.json and adapter-provenance.json
|
||||
are mandatory additional evidence. The inherited source-audit.json describes
|
||||
the unchanged AJ source only, NOT the complete Phase 4AM adapter semantics.
|
||||
Delta is fixed during the whole trial; this interface never trains or updates
|
||||
eligibility. The original CUDA arrival operator still samples current weights
|
||||
at arrival, and no emission-time weight snapshot is introduced.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import copy
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
import overlay
|
||||
|
||||
|
||||
AJ_SHA256 = 'af1975ba5e5e700208c0a598d3cbe648a3cf10885c6231239b6e4b28f76d173b'
|
||||
PINS = {
|
||||
'apl-timing-lab-v1/gpu_timing.py': AJ_SHA256,
|
||||
'apl-timing-lab-v1/timing_queue.py': '54df6d4818e2a0c26e96fb0aaf5d9b71cd964981706da937562d48af79aca14e',
|
||||
'author-apl-lab-v2/gpu_apl.py': '69f5d893f47dafff4ef7c3186c3df0b9014666b32c8f4e943b57016bec92b18a',
|
||||
'author-apl-lab-v2/legacy_apl_worker.py': '21ceec65fe42581dc55872010e0025d660e80358905f1cf4df1dadf341a3b291',
|
||||
'author-runtime-lab-v1/gpu_backend.py': '03758d07572f4b44d272059d9308fac534268ad201b57bdd77d69fee95e67278',
|
||||
'author-runtime-lab-v1/author_ext.cpp': '208a937d17bbb041b9bf6b25b89264ee566830b60609e58491b44583d8541f15',
|
||||
'author-runtime-lab-v1/author_ext.cu': '669f166e18ce9a3f63fc723db8b0a8b74f7fa4757194b1bd65b4cf6008a7787e',
|
||||
'author-fullgraph-lab-v1/oracle_full.py': 'd72003d98f0f1624b12126eff50bd361c43d888f4a64d90ba9247a58976f1961',
|
||||
}
|
||||
HOOK_NAME = '_phase4am_install_overlay'
|
||||
HOOK_CODE = HOOK_NAME + '(weights, graph, apl_out_mask, upload, out_dir, source_provenance)'
|
||||
_LOADED = {}
|
||||
|
||||
|
||||
def _save(path, value):
|
||||
with Path(path).open('x', encoding='utf-8') as stream:
|
||||
json.dump(value, stream, ensure_ascii=False, sort_keys=True, indent=2, allow_nan=False)
|
||||
stream.write('\n')
|
||||
|
||||
|
||||
def verify_sources(aj_model_dir):
|
||||
directory = Path(aj_model_dir).absolute()
|
||||
overlay.require(directory.name == 'apl-timing-lab-v1', 'AJ must be loaded from its actual original model directory')
|
||||
paths = {key: directory.parent / key for key in PINS}
|
||||
for key, path in paths.items():
|
||||
overlay.require(overlay.file_hash(path) == PINS[key], 'Sealed source SHA mismatch: ' + str(path))
|
||||
return {key: {'path': str(paths[key]), 'sha256': value} for key, value in PINS.items()}
|
||||
|
||||
|
||||
def _load_checked(name, path, expected):
|
||||
path = Path(path).absolute()
|
||||
data = path.read_bytes()
|
||||
overlay.require(hashlib.sha256(data).hexdigest() == expected, 'Source changed before loading: ' + str(path))
|
||||
if name in sys.modules:
|
||||
existing = sys.modules[name]
|
||||
overlay.require(_LOADED.get(name) == (path, expected, existing), 'Refusing an existing unverified module binding: ' + name)
|
||||
return existing
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
overlay.require(spec is not None and spec.loader is not None, 'No source loader')
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[name] = module
|
||||
try:
|
||||
# Execute precisely the checked source bytes; do not write frozen pyc.
|
||||
exec(compile(data, str(path), 'exec'), module.__dict__)
|
||||
except BaseException:
|
||||
sys.modules.pop(name, None)
|
||||
raise
|
||||
_LOADED[name] = (path, expected, module)
|
||||
return module
|
||||
|
||||
|
||||
def _dump(node):
|
||||
return ast.dump(node, annotate_fields=True, include_attributes=False)
|
||||
|
||||
|
||||
def derive_ast(source_bytes):
|
||||
"""Pure AST adapter: one inserted Expr, every other original node unchanged."""
|
||||
overlay.require(hashlib.sha256(source_bytes).hexdigest() == AJ_SHA256, 'Cannot derive from unpinned AJ source')
|
||||
module = ast.parse(source_bytes)
|
||||
matches = [node for node in module.body if isinstance(node, ast.FunctionDef) and node.name == 'run_gpu']
|
||||
overlay.require(len(matches) == 1, 'Expected exactly one sealed AJ run_gpu')
|
||||
original = matches[0]
|
||||
adapted = copy.deepcopy(original)
|
||||
tries = [node for node in adapted.body if isinstance(node, ast.Try)]
|
||||
overlay.require(len(tries) == 1, 'Unexpected run_gpu try structure')
|
||||
statements = tries[0].body
|
||||
anchor = ast.parse('effective_hash = hashlib.sha256()').body[0]
|
||||
positions = [i for i, node in enumerate(statements) if _dump(node) == _dump(anchor)]
|
||||
overlay.require(len(positions) == 1, 'Effective-weight hash insertion anchor is not unique')
|
||||
position = positions[0]
|
||||
previous = ast.parse('if bool(np.any(apl_out_mask)):\n weights[upload(apl_out_mask, torch.bool)] = 0.0').body[0]
|
||||
overlay.require(position > 0 and _dump(statements[position - 1]) == _dump(previous),
|
||||
'Overlay hook must immediately follow frozen fast-APL zeroing')
|
||||
hook = ast.copy_location(ast.parse(HOOK_CODE).body[0], statements[position])
|
||||
statements.insert(position, hook)
|
||||
tick_loops = lambda tree: [node for node in ast.walk(tree) if isinstance(node, ast.For) and
|
||||
isinstance(node.target, ast.Name) and node.target.id == 'tick']
|
||||
old_loops, new_loops = tick_loops(original), tick_loops(adapted)
|
||||
overlay.require(len(old_loops) == len(new_loops) == 1 and _dump(old_loops[0]) == _dump(new_loops[0]),
|
||||
'Entire simulation loop AST must remain identical')
|
||||
restored = copy.deepcopy(adapted)
|
||||
restored_try = next(node for node in restored.body if isinstance(node, ast.Try))
|
||||
restored_try.body.pop(position)
|
||||
overlay.require(_dump(restored) == _dump(original), 'Adapter changed more than its single pre-loop hook')
|
||||
ast.fix_missing_locations(adapted)
|
||||
sha = lambda value: hashlib.sha256(value.encode('utf-8')).hexdigest()
|
||||
audit = {'original_run_ast_sha256': sha(_dump(original)), 'adapted_run_ast_sha256': sha(_dump(adapted)),
|
||||
'loop_ast_sha256': sha(_dump(old_loops[0])), 'entire_tick_loop_ast_identical': True,
|
||||
'only_ast_change': HOOK_CODE, 'inserted_ast_statements': 1,
|
||||
'hook_after_apl_zero_before_effective_hash': True}
|
||||
return adapted, audit
|
||||
|
||||
|
||||
def compile_derived(aj_module, install_hook):
|
||||
"""Compile only the derived function into a NEW namespace, never old globals."""
|
||||
source_path = Path(aj_module.__file__).absolute()
|
||||
function_ast, audit = derive_ast(source_path.read_bytes())
|
||||
namespace = dict(aj_module.__dict__)
|
||||
# Real adapter location, not AJ's __file__ masquerading as a new module.
|
||||
namespace.update(__name__=__name__ + '.derived_run', __file__=str(Path(__file__).absolute()))
|
||||
overlay.require(HOOK_NAME not in namespace, 'Hook name collides with sealed globals')
|
||||
namespace[HOOK_NAME] = install_hook
|
||||
tree = ast.fix_missing_locations(ast.Module(body=[function_ast], type_ignores=[]))
|
||||
exec(compile(tree, str(Path(__file__).absolute()) + '::AST-derived-AJ-run_gpu', 'exec'), namespace)
|
||||
audit.update(derived_namespace_file=namespace['__file__'], original_module_file=str(source_path),
|
||||
original_module_globals_patched=False, fake_original_file_used=False)
|
||||
return namespace['run_gpu'], audit
|
||||
|
||||
|
||||
def _copy_input(source, destination, expected):
|
||||
with Path(source).open('rb') as incoming, Path(destination).open('xb') as outgoing:
|
||||
for block in iter(lambda: incoming.read(8 << 20), b''):
|
||||
outgoing.write(block)
|
||||
overlay.require(overlay.file_hash(destination) == expected, 'Copied input hash mismatch')
|
||||
|
||||
|
||||
def run_gpu_overlay(case_dir, out_dir, build_root, overlay_file, overlay_manifest):
|
||||
"""Fresh D18 constant-weight interface run; no learning/update inside trial.
|
||||
|
||||
This is the ONLY GPU entry point; importing/testing the module is CPU-only.
|
||||
Output is exclusive. The caller still owns preregistration, case/graph and
|
||||
frozen-mask authority, resource checks, budgets, and scientific gate choice.
|
||||
Success requires the added complete adapter-provenance.json, not merely an
|
||||
inherited AJ summary that may precede a failed final wrapper verification.
|
||||
"""
|
||||
case_dir, out_dir = Path(case_dir).absolute(), Path(out_dir).absolute()
|
||||
overlay.require(not out_dir.exists(), 'Refusing an existing output directory')
|
||||
overlay.require(sys.dont_write_bytecode, 'Invoke with python -B so inherited loaders cannot write frozen __pycache__')
|
||||
own_sources = {'adapter': overlay.file_hash(__file__), 'overlay': overlay.file_hash(overlay.__file__)}
|
||||
plan = overlay.load_overlay(overlay_file, overlay_manifest)
|
||||
spec = json.loads((case_dir / 'spec.json').read_text(encoding='utf-8'))
|
||||
overlay.require(type(spec.get('apl_delay_steps')) is int and spec['apl_delay_steps'] == 18,
|
||||
'Phase 4AM interface requires fixed D18')
|
||||
overlay.require(spec.get('training', False) is False and spec.get('learning', False) is False,
|
||||
'This interface does not authorize a learning run')
|
||||
aj_dir = Path(plan['metadata']['aj_model_dir'])
|
||||
sources = verify_sources(aj_dir)
|
||||
_load_checked('timing_queue', aj_dir / 'timing_queue.py', PINS['apl-timing-lab-v1/timing_queue.py'])
|
||||
aj = _load_checked('phase4am_sealed_aj_gpu', aj_dir / 'gpu_timing.py', AJ_SHA256)
|
||||
base_file = Path(spec['graph_dir']) / 'weights.npy'
|
||||
base_file_sha = overlay.file_hash(base_file)
|
||||
calls = []
|
||||
|
||||
def install(weights, graph, apl_out_mask, upload, output, inherited_provenance):
|
||||
overlay.require(not calls, 'Plastic overlay hook must run exactly once')
|
||||
mapping = overlay.map_installation(graph['weights'], graph['edge_id'], apl_out_mask, plan)
|
||||
if len(graph['nodes']) == 166700:
|
||||
overlay.require(int(apl_out_mask.sum()) == 5845, 'Frozen fullgraph fast-APL mask changed')
|
||||
torch = aj.torch
|
||||
overlay.require(weights.dtype == torch.float64 and weights.ndim == 1 and weights.numel() == len(graph['edge_id']),
|
||||
'Runtime weight tensor must retain float64 SI edge order')
|
||||
rows = mapping['rows']
|
||||
if len(rows):
|
||||
local = upload(rows, torch.int64)
|
||||
# Separate eager f64 multiply and addition, never base->f32 roundtrip.
|
||||
delta32 = plan['plastic_delta_mv'][plan['plastic_delta_mv'] != np.float32(0)]
|
||||
delta_si = upload(delta32, torch.float64) * 0.001
|
||||
weights[local] = weights[local] + delta_si
|
||||
actual = weights[local].cpu().numpy().copy()
|
||||
overlay.require(overlay.bit_equal(actual, mapping['expected_si']), 'GPU installation differs from independent CPU SI arithmetic')
|
||||
# With zero delta no indexing assignment or +0 arithmetic happens.
|
||||
base_raw_sha, expected_sha = overlay.expected_hashes(graph['weights'], apl_out_mask, mapping)
|
||||
actual_hash = hashlib.sha256()
|
||||
for start in range(0, weights.numel(), 1 << 20):
|
||||
actual_hash.update(weights[start:start + (1 << 20)].cpu().numpy().astype('<f8', copy=False).tobytes())
|
||||
effective_sha = actual_hash.hexdigest()
|
||||
overlay.require(effective_sha == expected_sha, 'Full effective weight bytes differ from base+APLzero+sparse overlay')
|
||||
overlay.verify_inputs(plan)
|
||||
overlay.require(overlay.file_hash(base_file) == base_file_sha, 'Original graph base file changed')
|
||||
_copy_input(plan['input']['overlay_file'], output / 'overlay-input.npz', plan['input']['overlay_sha256'])
|
||||
_copy_input(plan['input']['manifest_file'], output / 'overlay-manifest.json', plan['input']['manifest_sha256'])
|
||||
audit = {'schema': 'phase4am-overlay-audit-v1', 'complete': True,
|
||||
'overlay_name': plan['metadata']['overlay_name'], 'input': plan['input'],
|
||||
'arrays': {key: overlay.array_metadata(plan[key]) for key in
|
||||
('edge_id', 'plastic_delta_mv', 'base_candidate_mv') if plan[key] is not None},
|
||||
'candidate_count': len(plan['edge_id']), 'nonzero_delta_count': int(np.count_nonzero(plan['plastic_delta_mv'])),
|
||||
**{key: mapping[key] for key in ('mapped_candidate_count', 'missing_candidate_count', 'installed_nonzero_count', 'apl_overlap_count')},
|
||||
'delta_dtype': 'float32', 'delta_units': 'mV', 'state_dtype': 'float64', 'bridge': overlay.BRIDGE,
|
||||
'installation_schedule': 'before tick 0; after fast APL zeroing',
|
||||
'weights_updated_during_run': False, 'learning': False, 'zero_delta_no_write': True,
|
||||
'frozen_mask_exact': True, 'bounds_pass': True, 'apl_fast_edges_zeroed': int(apl_out_mask.sum()),
|
||||
'base_graph_files_modified': False, 'untouched_weights_bitwise_preserved': True,
|
||||
'base_weights_file_sha256': base_file_sha, 'base_weights_raw_sha256': base_raw_sha,
|
||||
'effective_weights_raw_sha256': effective_sha, 'expected_effective_weights_raw_sha256': expected_sha,
|
||||
'copied_overlay_file': 'overlay-input.npz', 'copied_manifest_file': 'overlay-manifest.json',
|
||||
'inherited_source_audit_scope': 'AJ source provenance only; AM overlay semantics are recorded separately',
|
||||
'inherited_aj_source_provenance': inherited_provenance,
|
||||
'static25_is_exact_fraction_of_float64_base_claimed': False,
|
||||
'arrival_weight_sampling': 'arrival time; delta fixed for entire trial'}
|
||||
_save(output / 'overlay-audit.json', audit)
|
||||
calls.append(audit)
|
||||
|
||||
derived, ast_audit = compile_derived(aj, install)
|
||||
try:
|
||||
summary = derived(case_dir, out_dir, Path(build_root))
|
||||
overlay.require(len(calls) == 1, 'Exactly one overlay installation was required')
|
||||
overlay.verify_inputs(plan)
|
||||
overlay.require(verify_sources(aj_dir) == sources and overlay.file_hash(base_file) == base_file_sha,
|
||||
'Frozen sources or base graph changed during the run')
|
||||
overlay.require(own_sources == {'adapter': overlay.file_hash(__file__), 'overlay': overlay.file_hash(overlay.__file__)},
|
||||
'Adapter source changed during invocation')
|
||||
overlay.require(summary.get('complete') is True and summary.get('effective_weights_raw_sha256') ==
|
||||
calls[0]['effective_weights_raw_sha256'], 'Inherited run summary does not bind installed weights')
|
||||
receipt = {'schema': 'phase4am-adapter-provenance-v1', 'complete': True,
|
||||
'scope': 'constant sparse overlay installation only; no within-trial plasticity or learning',
|
||||
'adapter_file': str(Path(__file__).absolute()), 'adapter_sha256': own_sources['adapter'],
|
||||
'overlay_module_file': str(Path(overlay.__file__).absolute()), 'overlay_module_sha256': own_sources['overlay'],
|
||||
'sealed_sources': sources, 'ast': ast_audit, 'installation_calls': len(calls),
|
||||
'weights_updated_during_run': False, 'learning': False, 'apl_delay_steps': 18,
|
||||
'original_module_globals_patched': False, 'delta_constant_for_entire_trial': True,
|
||||
'inherited_source_audit_describes_AM_semantics': False,
|
||||
'overlay_audit_sha256': overlay.file_hash(out_dir / 'overlay-audit.json'),
|
||||
'inherited_summary_sha256': overlay.file_hash(out_dir / 'summary.json'),
|
||||
'effective_weights_raw_sha256': calls[0]['effective_weights_raw_sha256']}
|
||||
_save(out_dir / 'adapter-provenance.json', receipt)
|
||||
return summary
|
||||
except Exception as error:
|
||||
if out_dir.is_dir() and not (out_dir / 'adapter-error.json').exists():
|
||||
_save(out_dir / 'adapter-error.json', {'complete': False, 'type': type(error).__name__,
|
||||
'message': str(error), 'installation_calls': len(calls), 'no_complete_adapter_receipt': True})
|
||||
raise
|
||||
@@ -0,0 +1,19 @@
|
||||
85a3253ceb4e4c5d7bcf17b8b3294dbbf72bd1139415a892f6a90e8037785c89 PREPARATION.md
|
||||
e03273995f4d5f1fc18d0af481374b8688ae735d83ae718895ffb741f8ac9399 PROTOCOL.md
|
||||
3daabe771d3eef6c6189827c0ee5159b266c3a058e07da5781bb863be428aef9 adapter.py
|
||||
4d64020af05e7fc9e535ac8532dd0e7b614b200f695ed8c6d486c6151de57175 comparison.py
|
||||
c19f00c81f12c4c3503d3d557bd73d0722d4d3fed7279449408c67a89a6ffcf9 config.json
|
||||
0fa2e0892c973fe70ecbc0613e3cb508c256b7745042f8936edf23dedc31d166 context.py
|
||||
174fb8d14619189e74b095e77a6db18b14c93046dc431c0b55710a5aa2dd2e61 eligibility_bridge.py
|
||||
b8e4fb59d4efa261a8615f1456bc2ad1f1822e941af558b27074b8c41dba15a1 finalize.py
|
||||
7f891d0ba79b393c96e75fd2dee2eddc51e57d8213eaac2779703bc2c18712b4 overlay.py
|
||||
40976f0ed0b01fbf275c245de0f0a907592d23f5ca4733293b41e1d011c7ee62 prepare.py
|
||||
6a6fcf4cc64d27234ab69ce5a9dcdb8bbd8b3b4965c9c08c2f9d20747025e9e7 run_suite.py
|
||||
b4f3cacf3ab60f70d63713d5bbc3e03658f38e357a47ef219b1050003176df01 summarize.py
|
||||
a6274f338307a6961e514f565ab3e731051846caf62901e6843037755adc6fd8 test_comparison.py
|
||||
113435c0f3528e3901e47421570a5f906d192826df9fc4ee600429a87ab904c5 test_eligibility_bridge.py
|
||||
1c0536e0d0c27de6ab152ebb5aa0015ee31325417b17d3cfedc5f43bafa2ee79 test_overlay_adapter.py
|
||||
351a0c2887d657e1942b849d768d7efe8c52c0c3c67d0cf4d55658549c3bfe32 test_workflow.py
|
||||
f1453a8814339ec7860ef4dee20549cc811d13cefd7aee2d308ebdfd51a839ca test_workflow_identity.py
|
||||
5b121184ec3e666814dd440356604e96520aecdd8902d5b68c7d61caa7f224c5 verify_frozen.py
|
||||
7edeb251ecbeac5a64805fefe171a0a0bf8bba2675919ef922cc1a329c203ea8 manifest.json
|
||||
@@ -0,0 +1,603 @@
|
||||
"""Exact, read-only Phase 4AM output comparison; never a learning gate.
|
||||
|
||||
The sealed AJ comparator is loaded from verified bytes without modifying its
|
||||
globals. Full-graph queue identities and refractory acceptance are additionally
|
||||
reconstructed from actual events, including zero-weight edges. Only declared
|
||||
performance fields may differ. New AM provenance is checked separately, not
|
||||
mistaken for the inherited AJ implementation provenance.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import copy
|
||||
from functools import lru_cache
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
N = 166700
|
||||
E = 25582938
|
||||
K = 4064
|
||||
CANDIDATES = 14232
|
||||
STEPS = 1000
|
||||
AJ_SHA = 'af1975ba5e5e700208c0a598d3cbe648a3cf10885c6231239b6e4b28f76d173b'
|
||||
HELPER_SHA = '9010f070fba9b7c9cf2d86187ee2abc57f3ac606ebc08a42baad4f3922b51734'
|
||||
CPP_SHA = '208a937d17bbb041b9bf6b25b89264ee566830b60609e58491b44583d8541f15'
|
||||
CUDA_SHA = '669f166e18ce9a3f63fc723db8b0a8b74f7fa4757194b1bd65b4cf6008a7787e'
|
||||
SEALED = {
|
||||
'ag_gpu': '69f5d893f47dafff4ef7c3186c3df0b9014666b32c8f4e943b57016bec92b18a',
|
||||
'ag_worker': '21ceec65fe42581dc55872010e0025d660e80358905f1cf4df1dadf341a3b291',
|
||||
'ae_backend': '03758d07572f4b44d272059d9308fac534268ad201b57bdd77d69fee95e67278',
|
||||
'af_queue': 'd72003d98f0f1624b12126eff50bd361c43d888f4a64d90ba9247a58976f1961',
|
||||
}
|
||||
ADAPTER_PINS = {
|
||||
'apl-timing-lab-v1/gpu_timing.py': AJ_SHA,
|
||||
'apl-timing-lab-v1/timing_queue.py': '54df6d4818e2a0c26e96fb0aaf5d9b71cd964981706da937562d48af79aca14e',
|
||||
'author-apl-lab-v2/gpu_apl.py': SEALED['ag_gpu'],
|
||||
'author-apl-lab-v2/legacy_apl_worker.py': SEALED['ag_worker'],
|
||||
'author-runtime-lab-v1/gpu_backend.py': SEALED['ae_backend'],
|
||||
'author-runtime-lab-v1/author_ext.cpp': CPP_SHA,
|
||||
'author-runtime-lab-v1/author_ext.cu': CUDA_SHA,
|
||||
'author-fullgraph-lab-v1/oracle_full.py': SEALED['af_queue'],
|
||||
}
|
||||
HOOK_CODE = '_phase4am_install_overlay(weights, graph, apl_out_mask, upload, out_dir, source_provenance)'
|
||||
PERFORMANCE = frozenset(('wall_seconds', 'diagnostic_simulation_seconds', 'build_or_cache_seconds',
|
||||
'peak_allocated_vram_bytes', 'peak_reserved_vram_bytes'))
|
||||
BRIDGE = 'baseSI64 + (delta32 widened to float64) * float64(0.001); nonzero rows only'
|
||||
SCHEDULE = 'before tick 0; after fast APL zeroing'
|
||||
GRAPH_FIELDS = ('nodes', 'edge_id', 'src', 'dst', 'ptr', 'signed_raw', 'weights')
|
||||
_WEIGHT_CACHE = {}
|
||||
_BASE_RAW_CACHE = {}
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def _fingerprint(path):
|
||||
path = Path(path).resolve()
|
||||
stat = path.stat()
|
||||
require(path.is_file(), 'Not a regular evidence file: ' + str(path))
|
||||
return str(path), stat.st_dev, stat.st_ino, stat.st_size, stat.st_mtime_ns, stat.st_ctime_ns
|
||||
|
||||
|
||||
def _digest_fingerprint(identity):
|
||||
# Do not cache byte hashes by stat metadata. Same-size writes can share
|
||||
# inode, mtime_ns and ctime_ns even on the actual server filesystem.
|
||||
value = hashlib.sha256()
|
||||
with Path(identity[0]).open('rb') as source:
|
||||
for block in iter(lambda: source.read(8 << 20), b''):
|
||||
value.update(block)
|
||||
require(_fingerprint(identity[0]) == identity, 'Evidence changed while hashing')
|
||||
return value.hexdigest()
|
||||
|
||||
|
||||
def file_hash(path):
|
||||
return _digest_fingerprint(_fingerprint(path))
|
||||
|
||||
|
||||
def raw_hash(array):
|
||||
return hashlib.sha256(np.ascontiguousarray(array).tobytes()).hexdigest()
|
||||
|
||||
|
||||
@lru_cache(maxsize=2)
|
||||
def _helper_from_bytes(identity):
|
||||
require(file_hash(identity[0]) == HELPER_SHA, 'Sealed AJ comparator hash changed')
|
||||
spec = importlib.util.spec_from_file_location('phase4am_readonly_sealed_aj_compare', identity[0])
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
exec(compile(Path(identity[0]).read_bytes(), identity[0], 'exec'), module.__dict__)
|
||||
return module
|
||||
|
||||
|
||||
def _helper(used):
|
||||
path = Path(__file__).resolve().parent.parent / 'apl-timing-lab-v1/compare_timing.py'
|
||||
used.add(path)
|
||||
sha = file_hash(path)
|
||||
require(sha == HELPER_SHA, 'Sealed AJ comparator hash changed')
|
||||
return _helper_from_bytes(_fingerprint(path) + (sha,))
|
||||
|
||||
|
||||
def _json(path, used):
|
||||
path = Path(path)
|
||||
used.add(path)
|
||||
def unique(pairs):
|
||||
result = {}
|
||||
for key, value in pairs:
|
||||
require(key not in result, 'Duplicate JSON key: ' + key)
|
||||
result[key] = value
|
||||
return result
|
||||
return json.loads(path.read_text(encoding='utf-8'), object_pairs_hook=unique,
|
||||
parse_constant=lambda value: (_ for _ in ()).throw(ValueError('Nonfinite JSON: ' + value)))
|
||||
|
||||
|
||||
def _npz(path, used):
|
||||
path = Path(path)
|
||||
used.add(path)
|
||||
with np.load(path, allow_pickle=False) as data:
|
||||
require(len(data.files) == len(set(data.files)), 'Duplicate NPZ key')
|
||||
return {key: data[key].copy() for key in data.files}
|
||||
|
||||
|
||||
@lru_cache(maxsize=2)
|
||||
def _static_arrays(identities):
|
||||
arrays = {Path(identity[0]).name: np.load(identity[0], mmap_mode='r', allow_pickle=False)
|
||||
for identity in identities}
|
||||
require(all(_fingerprint(identity[0]) == identity[:-1] and file_hash(identity[0]) == identity[-1]
|
||||
for identity in identities), 'Static inputs changed while loading')
|
||||
return arrays
|
||||
|
||||
|
||||
def _load_case(case_dir, used):
|
||||
spec = _json(case_dir / 'spec.json', used)
|
||||
require(type(spec['steps']) is int and spec['steps'] == STEPS and spec['dt_ms'] == .1 and
|
||||
spec['duration_ms'] == 100. and spec['record_history'] is False and
|
||||
type(spec['apl_delay_steps']) is int and spec['apl_delay_steps'] == 18,
|
||||
'Phase 4AM comparison requires the unchanged full 100-ms D18 case')
|
||||
model = spec['model']
|
||||
for key, value in (('synaptic_delay_ms', 1.8), ('refractory_ms', 2.2),
|
||||
('w_syn_mv_per_anatomical_synapse', .275), ('resting_potential_mv', -52.),
|
||||
('reset_potential_mv', -52.), ('threshold_mv', -45.)):
|
||||
require(type(model[key]) in (int, float) and model[key] == value, 'Frozen model field changed: ' + key)
|
||||
graph_dir, bundle_dir = Path(spec['graph_dir']), Path(spec['apl_bundle_dir'])
|
||||
require(graph_dir.is_absolute() and bundle_dir.is_absolute(), 'Static input paths must be absolute')
|
||||
paths = [graph_dir / (key + '.npy') for key in GRAPH_FIELDS]
|
||||
paths += [bundle_dir / key for key in ('input_matrix.npy', 'output_matrix.npy', 'kc_globals.npy', 'apl_globals.npy')]
|
||||
used.update(paths)
|
||||
# Caches for parsed/derived immutable graph data are keyed by FRESH content
|
||||
# SHA, not merely a filesystem timestamp. Re-read hashes on every call.
|
||||
token = tuple(_fingerprint(p) + (file_hash(p),) for p in paths)
|
||||
arrays = _static_arrays(token)
|
||||
graph = {key: arrays[key + '.npy'] for key in GRAPH_FIELDS}
|
||||
bundle = {key: arrays[key] for key in ('input_matrix.npy', 'output_matrix.npy', 'kc_globals.npy', 'apl_globals.npy')}
|
||||
_validate_static(graph, bundle, token)
|
||||
requests = _npz(case_dir / 'requests.npz', used)
|
||||
require(set(requests) == {'tick', 'neuron_idx'}, 'Unexpected request arrays')
|
||||
ticks, nodes = requests['tick'], requests['neuron_idx']
|
||||
require(ticks.dtype == nodes.dtype == np.int32 and ticks.ndim == 1 and ticks.shape == nodes.shape and
|
||||
np.all((ticks >= 0) & (ticks < STEPS)) and np.all((nodes >= 0) & (nodes < N)), 'Invalid sparse requests')
|
||||
keys = ticks.astype(np.int64) * N + nodes
|
||||
require(np.all(keys[1:] > keys[:-1]), 'Requests must be sorted unique; no silently deduplicated requests')
|
||||
coeff_path = Path(spec['coefficients_path'])
|
||||
require(coeff_path.is_absolute(), 'Coefficient path must be absolute')
|
||||
coeff = _json(coeff_path, used)
|
||||
for key in ('gd', 'c0', 'cg', 'cv', 'threshold', 'reset', 'seed_volt'):
|
||||
require(type(coeff[key]) in (int, float) and math.isfinite(coeff[key]), 'Invalid author coefficient: ' + key)
|
||||
require(coeff['threshold'] == model['threshold_mv'] * .001 and
|
||||
coeff['reset'] == model['reset_potential_mv'] * .001 and coeff['seed_volt'] > coeff['threshold'],
|
||||
'Author coefficient units/threshold mismatch')
|
||||
input_hashes = {'spec.json': file_hash(case_dir / 'spec.json'),
|
||||
'requests.npz': file_hash(case_dir / 'requests.npz'), 'base_weights.npy': file_hash(graph_dir / 'weights.npy'),
|
||||
'coefficients.json': file_hash(coeff_path)}
|
||||
if (case_dir / 'arrays.npz').exists():
|
||||
used.add(case_dir / 'arrays.npz')
|
||||
input_hashes['arrays.npz'] = file_hash(case_dir / 'arrays.npz')
|
||||
return dict(spec=spec, graph=graph, bundle=bundle, token=token, input_hashes=input_hashes,
|
||||
requested=list(zip(map(int, ticks), map(int, nodes))), graph_dir=graph_dir, bundle_dir=bundle_dir)
|
||||
|
||||
|
||||
_VALIDATED_STATIC = set()
|
||||
|
||||
|
||||
def _validate_static(graph, bundle, token):
|
||||
if token in _VALIDATED_STATIC:
|
||||
return
|
||||
for key, dtype, shape in (('nodes', np.int32, (N,)), ('ptr', np.int64, (N + 1,)),
|
||||
('edge_id', np.int64, (E,)), ('src', np.int32, (E,)), ('dst', np.int32, (E,)),
|
||||
('signed_raw', np.int64, (E,)), ('weights', np.float64, (E,))):
|
||||
require(graph[key].dtype == dtype and graph[key].shape == shape, 'Canonical graph schema changed: ' + key)
|
||||
require(np.array_equal(graph['nodes'], np.arange(N, dtype=np.int32)), 'Global neuron identity changed')
|
||||
ptr = graph['ptr']
|
||||
require(ptr[0] == 0 and ptr[-1] == E and np.all(np.diff(ptr) >= 0), 'Invalid canonical CSR')
|
||||
for start in range(0, E, 1 << 20):
|
||||
stop = min(E, start + (1 << 20))
|
||||
expected_ids = np.arange(start, stop, dtype=np.int64)
|
||||
require(np.array_equal(graph['edge_id'][start:stop], expected_ids), 'Original edge identity changed')
|
||||
require(np.array_equal(graph['src'][start:stop], np.searchsorted(ptr[1:], expected_ids, side='right')),
|
||||
'CSR source order changed')
|
||||
require(np.all((graph['dst'][start:stop] >= 0) & (graph['dst'][start:stop] < N)), 'Invalid edge targets')
|
||||
original = graph['weights'][start:stop]
|
||||
expected = graph['signed_raw'][start:stop] * (.275 * .001)
|
||||
require(np.isfinite(original).all() and raw_hash(original) == raw_hash(expected), 'Original signed SI weights changed')
|
||||
for name, dtype, shape in (('kc_globals.npy', np.int32, (K,)), ('apl_globals.npy', np.int32, (2,)),
|
||||
('input_matrix.npy', np.float32, (2, N)), ('output_matrix.npy', np.float32, (2, K))):
|
||||
a = bundle[name]
|
||||
require(a.dtype == dtype and a.shape == shape and np.isfinite(a).all(), 'Invalid APL bundle ' + name)
|
||||
if dtype == np.int32:
|
||||
require(np.all((a >= 0) & (a < N)) and np.all(a[1:] > a[:-1]), 'Invalid APL/KC IDs')
|
||||
else:
|
||||
require(np.all(a >= 0), 'Negative normalized APL matrix')
|
||||
require(not np.intersect1d(bundle['kc_globals.npy'], bundle['apl_globals.npy']).size, 'APL/KC overlap')
|
||||
_VALIDATED_STATIC.add(token)
|
||||
|
||||
|
||||
def _effective_hash(case, ids=None, delta=None):
|
||||
"""Independent static bridge, cached per immutable graph/delta identity."""
|
||||
key = (case['token'], None if ids is None else raw_hash(ids), None if delta is None else raw_hash(delta))
|
||||
if key in _WEIGHT_CACHE:
|
||||
return _WEIGHT_CACHE[key]
|
||||
graph, apl = case['graph'], case['bundle']['apl_globals.npy']
|
||||
baseline, effective = hashlib.sha256(), hashlib.sha256()
|
||||
masked = 0
|
||||
for start in range(0, len(graph['edge_id']), 1 << 20):
|
||||
stop = min(len(graph['edge_id']), start + (1 << 20))
|
||||
value = graph['weights'][start:stop].copy()
|
||||
fast = np.isin(graph['src'][start:stop], apl)
|
||||
masked += int(fast.sum())
|
||||
value[fast] = np.float64(0)
|
||||
baseline.update(value.astype('<f8', copy=False).tobytes())
|
||||
if ids is not None:
|
||||
lo, hi = np.searchsorted(ids, (start, stop))
|
||||
ix, change = ids[lo:hi] - start, delta[lo:hi]
|
||||
require(not fast[ix].any(), 'Plastic mask overlaps fast APL outputs')
|
||||
nz = change != 0
|
||||
value[ix[nz]] = np.add(value[ix[nz]], np.multiply(change[nz].astype(np.float64), np.float64(.001)))
|
||||
require(np.isfinite(value[ix[nz]]).all() and np.all(value[ix[nz]] > 0), 'Static overlay changes candidate sign')
|
||||
effective.update(value.astype('<f8', copy=False).tobytes())
|
||||
result = (baseline.hexdigest(), effective.hexdigest(), masked)
|
||||
_WEIGHT_CACHE[key] = result
|
||||
return result
|
||||
|
||||
|
||||
def _base_raw_hash(case):
|
||||
key = case['token']
|
||||
if key not in _BASE_RAW_CACHE:
|
||||
h = hashlib.sha256()
|
||||
values = case['graph']['weights']
|
||||
for start in range(0, len(values), 1 << 20):
|
||||
h.update(values[start:start + (1 << 20)].astype('<f8', copy=False).tobytes())
|
||||
_BASE_RAW_CACHE[key] = h.hexdigest()
|
||||
return _BASE_RAW_CACHE[key]
|
||||
|
||||
|
||||
def _self_consistency(run, graph, requested, steps, helper, role, checks, differences):
|
||||
"""Reconstruct every queue hash, request gate, and ref hash from events.
|
||||
|
||||
No edge weight, claimed queue digest, or stored PASS determines the result.
|
||||
Expansion touches only the current delayed spike sources, not all edges.
|
||||
"""
|
||||
state, ptr, nodes = run['state'], graph['ptr'], graph['nodes']
|
||||
n = len(nodes)
|
||||
local = np.searchsorted(nodes, state['neuron_idx'])
|
||||
require(np.all(local < n) and np.array_equal(nodes[local], state['neuron_idx']), 'Spike outside graph')
|
||||
keys = state['step'].astype(np.int64) * N + state['neuron_idx']
|
||||
require(np.all(keys[1:] > keys[:-1]), 'Duplicate or unordered actual events')
|
||||
require(np.array_equal(np.bincount(local, minlength=n), state['counts']), 'Event counts do not match per-neuron counts')
|
||||
require([(r['tick'], r['neuron_idx']) for r in run['requests']] == requested, 'Request input identity differs')
|
||||
bounds = np.searchsorted(state['step'], np.arange(steps + 1))
|
||||
rq_bounds = np.searchsorted([r['tick'] for r in run['requests']], np.arange(steps + 1))
|
||||
last = np.full(n, -1_000_000_000, np.int64)
|
||||
failures = {}
|
||||
expected_due_total = expected_accepted_total = 0
|
||||
for tick in range(steps):
|
||||
fired = local[bounds[tick]:bounds[tick + 1]]
|
||||
if np.any(tick - last[fired] < 22):
|
||||
failures.setdefault('refractory_spike_violation', tick)
|
||||
for row in run['requests'][rq_bounds[tick]:rq_bounds[tick + 1]]:
|
||||
target = int(np.searchsorted(nodes, row['neuron_idx']))
|
||||
require(target < n and nodes[target] == row['neuron_idx'], 'Request outside canonical graph')
|
||||
accepted = tick - last[target] >= 22
|
||||
if row['accepted'] != int(accepted) or row['rejected'] != int(not accepted):
|
||||
failures.setdefault('request_refractory_gate', tick)
|
||||
if bool(np.any(fired == target)) != bool(accepted):
|
||||
failures.setdefault('request_actual_spike', tick)
|
||||
last[fired] = tick
|
||||
ref = np.maximum(22 - (tick - last), 0).astype(np.int32)
|
||||
if helper.raw_hash(ref, '<i4') != run['hashes'][tick]['ref_sha256']:
|
||||
failures.setdefault('all_tick_ref_hash', tick)
|
||||
sources = local[bounds[tick - 18]:bounds[tick - 17]] if tick >= 18 else np.empty(0, np.int64)
|
||||
pieces = [np.arange(ptr[source], ptr[source + 1], dtype=np.int64) for source in sources]
|
||||
rows = np.concatenate(pieces) if pieces else np.empty(0, np.int64)
|
||||
accepted = (ref[graph['dst'][rows]] == 0).astype(np.uint8)
|
||||
expected = helper._queue_digest(tick, graph['edge_id'][rows], accepted)
|
||||
expected_due_total += len(rows)
|
||||
expected_accepted_total += int(accepted.sum())
|
||||
if expected != run['queues'][tick]:
|
||||
failures.setdefault('full_queue_edge_order_and_acceptance', tick)
|
||||
if not np.array_equal(ref, state['ref']):
|
||||
failures['final_ref_from_events'] = steps - 1
|
||||
summary = run['summary']
|
||||
if summary.get('arriving_edges') != expected_due_total or summary.get('accepted_arriving_edges') != expected_accepted_total:
|
||||
failures['queue_summary_counts'] = 'mismatch'
|
||||
checks[role + '.full_event_queue_request_accounting'] = not failures
|
||||
if failures:
|
||||
differences[role + '.independent_accounting'] = failures
|
||||
|
||||
|
||||
def _overlay(run, case, used, zero_required=False):
|
||||
folder = run['directory']
|
||||
audit = _json(folder / 'overlay-audit.json', used)
|
||||
require(audit['schema'] == 'phase4am-overlay-audit-v1' and audit['complete'] is True, 'Incomplete overlay audit')
|
||||
source = audit['input']
|
||||
inputs = {}
|
||||
for label in ('overlay', 'manifest', 'mask', 'base_candidate'):
|
||||
filename, sha = source[label + '_file'], source[label + '_sha256']
|
||||
if filename is None:
|
||||
require(label == 'base_candidate' and sha is None, 'Required overlay input absent')
|
||||
inputs[label] = None
|
||||
continue
|
||||
path = Path(filename)
|
||||
require(path.is_absolute(), 'Overlay provenance input must be absolute')
|
||||
used.add(path)
|
||||
require(file_hash(path) == sha, 'Overlay input hash mismatch: ' + label)
|
||||
inputs[label] = path
|
||||
manifest = _json(inputs['manifest'], used)
|
||||
require(manifest['schema'] == 'phase4am-overlay-v1' and manifest['overlay_sha256'] == source['overlay_sha256'] and
|
||||
manifest['mask_sha256'] == source['mask_sha256'] and Path(manifest['mask_path']).resolve() == inputs['mask'].resolve(),
|
||||
'Overlay manifest linkage mismatch')
|
||||
require(manifest['base_candidate_sha256'] == source['base_candidate_sha256'] and
|
||||
(manifest['base_candidate_path'] is None if inputs['base_candidate'] is None else
|
||||
Path(manifest['base_candidate_path']).resolve() == inputs['base_candidate'].resolve()), 'Candidate floor provenance mismatch')
|
||||
require(Path(manifest['aj_model_dir']).resolve() == Path(__file__).resolve().parent.parent / 'apl-timing-lab-v1',
|
||||
'Overlay is not bound to the sealed AJ model')
|
||||
require(audit['copied_overlay_file'] == 'overlay-input.npz' and audit['copied_manifest_file'] == 'overlay-manifest.json',
|
||||
'Unexpected copied overlay filenames')
|
||||
for label, name in (('overlay', 'overlay-input.npz'), ('manifest', 'overlay-manifest.json')):
|
||||
used.add(folder / name)
|
||||
require(file_hash(folder / name) == source[label + '_sha256'], 'Copied input mismatch')
|
||||
arrays = _npz(inputs['overlay'], used)
|
||||
require(set(arrays) in ({'edge_id', 'plastic_delta_mv'}, {'edge_id', 'plastic_delta_mv', 'base_candidate_mv'}), 'Overlay NPZ schema changed')
|
||||
ids, delta = arrays['edge_id'], arrays['plastic_delta_mv']
|
||||
require(ids.dtype == np.int64 and ids.shape == (CANDIDATES,) and np.all(ids[1:] > ids[:-1]) and
|
||||
np.all((ids >= 0) & (ids < len(case['graph']['edge_id']))), 'Invalid original plastic edge IDs')
|
||||
mask = np.load(inputs['mask'], allow_pickle=False)
|
||||
require(mask.dtype == np.int64 and mask.shape == ids.shape and np.array_equal(mask, ids), 'Frozen plastic mask identity mismatch')
|
||||
require(delta.dtype == np.float32 and delta.shape == ids.shape and np.isfinite(delta).all() and np.all(delta <= 0), 'Invalid LTD delta')
|
||||
audit_arrays = dict(arrays)
|
||||
name = manifest['overlay_name']
|
||||
require(name in ('zero', 'static25') and audit['overlay_name'] == name, 'Unknown overlay intervention')
|
||||
if zero_required:
|
||||
require(name == 'zero', 'Zero-regression cannot use a nonzero intervention')
|
||||
if name == 'zero':
|
||||
require(not np.any(delta != 0), 'Zero overlay contains nonzero delta')
|
||||
else:
|
||||
require(inputs['base_candidate'] is not None, 'Static25 requires its frozen float32 mV floor')
|
||||
if inputs['base_candidate'] is not None:
|
||||
base = np.load(inputs['base_candidate'], allow_pickle=False)
|
||||
require(base.dtype == np.float32 and base.shape == ids.shape and np.isfinite(base).all() and np.all(base > 0), 'Invalid old float32 base')
|
||||
floor = np.multiply(base, np.float32(-.25))
|
||||
require(np.all(delta >= floor), 'Delta exceeds the existing depression bound')
|
||||
if 'base_candidate_mv' in arrays:
|
||||
require(arrays['base_candidate_mv'].dtype == np.float32 and raw_hash(arrays['base_candidate_mv']) == raw_hash(base), 'Embedded old base differs')
|
||||
if name == 'static25':
|
||||
require(raw_hash(delta) == raw_hash(floor), 'Static25 is not the declared old float32 floor')
|
||||
audit_arrays['base_candidate_mv'] = base
|
||||
else:
|
||||
require('base_candidate_mv' not in arrays, 'Embedded unanchored old base is forbidden')
|
||||
expected_arrays = {key: dict(dtype=str(value.dtype), shape=list(value.shape), raw_sha256=raw_hash(value))
|
||||
for key, value in audit_arrays.items()}
|
||||
require(audit['arrays'] == expected_arrays, 'Overlay array audit differs from actual bytes')
|
||||
require(np.isin(case['graph']['src'][ids], case['bundle']['kc_globals.npy']).all() and
|
||||
np.all(case['graph']['weights'][ids] > 0), 'Plastic edge source/sign changed')
|
||||
baseline_sha, effective_sha, apl_count = _effective_hash(case, ids, delta)
|
||||
graph = case['graph']
|
||||
for key, value in {'candidate_count': CANDIDATES, 'nonzero_delta_count': int(np.count_nonzero(delta)),
|
||||
'mapped_candidate_count': CANDIDATES, 'missing_candidate_count': 0,
|
||||
'installed_nonzero_count': int(np.count_nonzero(delta)), 'delta_dtype': 'float32', 'delta_units': 'mV',
|
||||
'state_dtype': 'float64', 'bridge': BRIDGE, 'installation_schedule': SCHEDULE,
|
||||
'weights_updated_during_run': False, 'learning': False, 'zero_delta_no_write': True,
|
||||
'frozen_mask_exact': True, 'bounds_pass': True, 'apl_overlap_count': 0,
|
||||
'apl_fast_edges_zeroed': apl_count, 'base_graph_files_modified': False,
|
||||
'untouched_weights_bitwise_preserved': True,
|
||||
'base_weights_file_sha256': file_hash(case['graph_dir'] / 'weights.npy'),
|
||||
'base_weights_raw_sha256': _base_raw_hash(case),
|
||||
'effective_weights_raw_sha256': effective_sha, 'expected_effective_weights_raw_sha256': effective_sha}.items():
|
||||
require(type(audit.get(key)) is type(value) and audit[key] == value, 'Overlay audit mismatch: ' + key)
|
||||
require(run['summary']['effective_weights_raw_sha256'] == effective_sha, 'Installed effective-weight hash differs from independent bridge')
|
||||
if zero_required:
|
||||
require(effective_sha == baseline_sha, 'Zero overlay changed the baseline effective weights')
|
||||
require(audit['inherited_aj_source_provenance'] == run['summary']['source_provenance'] and
|
||||
audit['inherited_source_audit_scope'] == 'AJ source provenance only; AM overlay semantics are recorded separately' and
|
||||
audit['static25_is_exact_fraction_of_float64_base_claimed'] is False and
|
||||
audit['arrival_weight_sampling'] == 'arrival time; delta fixed for entire trial', 'Overlay semantic scope mismatch')
|
||||
return audit
|
||||
|
||||
|
||||
def _expected_ast(source_path, adapter_path):
|
||||
"""Independently derive just the declared Expr insertion; no adapter import."""
|
||||
tree = ast.parse(Path(source_path).read_bytes())
|
||||
original = [node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name == 'run_gpu']
|
||||
require(len(original) == 1, 'Pinned AJ run_gpu not unique')
|
||||
original = original[0]
|
||||
adapted = copy.deepcopy(original)
|
||||
tries = [node for node in adapted.body if isinstance(node, ast.Try)]
|
||||
require(len(tries) == 1, 'Pinned run_gpu try not unique')
|
||||
statements = tries[0].body
|
||||
dump = lambda node: ast.dump(node, annotate_fields=True, include_attributes=False)
|
||||
anchor = dump(ast.parse('effective_hash = hashlib.sha256()').body[0])
|
||||
indices = [i for i, node in enumerate(statements) if dump(node) == anchor]
|
||||
require(len(indices) == 1, 'Pinned hook anchor not unique')
|
||||
index = indices[0]
|
||||
previous = ast.parse('if bool(np.any(apl_out_mask)):\n weights[upload(apl_out_mask, torch.bool)] = 0.0').body[0]
|
||||
require(index > 0 and dump(statements[index - 1]) == dump(previous), 'Hook is not immediately after APL zeroing')
|
||||
statements.insert(index, ast.parse(HOOK_CODE).body[0])
|
||||
loops = lambda node: [item for item in ast.walk(node) if isinstance(item, ast.For) and
|
||||
isinstance(item.target, ast.Name) and item.target.id == 'tick']
|
||||
before, after = loops(original), loops(adapted)
|
||||
require(len(before) == len(after) == 1 and dump(before[0]) == dump(after[0]), 'Hook changed loop AST')
|
||||
sha = lambda node: hashlib.sha256(dump(node).encode('utf-8')).hexdigest()
|
||||
return dict(original_run_ast_sha256=sha(original), adapted_run_ast_sha256=sha(adapted),
|
||||
loop_ast_sha256=sha(before[0]), entire_tick_loop_ast_identical=True, only_ast_change=HOOK_CODE,
|
||||
inserted_ast_statements=1, hook_after_apl_zero_before_effective_hash=True,
|
||||
derived_namespace_file=str(adapter_path.absolute()), original_module_file=str(Path(source_path).absolute()),
|
||||
original_module_globals_patched=False, fake_original_file_used=False)
|
||||
|
||||
|
||||
def _adapter_receipt(run, used):
|
||||
folder = run['directory']
|
||||
require(not (folder / 'adapter-error.json').exists(), 'Adapter recorded an error')
|
||||
receipt = _json(folder / 'adapter-provenance.json', used)
|
||||
require(receipt['schema'] == 'phase4am-adapter-provenance-v1' and receipt['complete'] is True,
|
||||
'Completed inherited AJ summary is insufficient without completed AM receipt')
|
||||
model = Path(__file__).resolve().parent
|
||||
for label, filename in (('adapter', 'adapter.py'), ('overlay_module', 'overlay.py')):
|
||||
path = Path(receipt[label + '_file'])
|
||||
require(path.is_absolute() and path.resolve() == model / filename, 'Unexpected new wrapper source path')
|
||||
used.add(path)
|
||||
require(file_hash(path) == receipt[label + '_sha256'], 'Wrapper source hash changed')
|
||||
source_rows = {}
|
||||
for relative, sha in ADAPTER_PINS.items():
|
||||
path = model.parent / relative
|
||||
used.add(path)
|
||||
require(file_hash(path) == sha, 'Frozen source changed: ' + relative)
|
||||
source_rows[relative] = {'path': str(path.absolute()), 'sha256': sha}
|
||||
require(receipt['sealed_sources'] == source_rows, 'Wrapper sealed-source inventory differs')
|
||||
require(receipt['ast'] == _expected_ast(model.parent / 'apl-timing-lab-v1/gpu_timing.py', model / 'adapter.py'),
|
||||
'Wrapper AST does not match independently reconstructed single pre-loop hook')
|
||||
for key, value in dict(installation_calls=1, weights_updated_during_run=False, learning=False, apl_delay_steps=18,
|
||||
original_module_globals_patched=False, delta_constant_for_entire_trial=True,
|
||||
inherited_source_audit_describes_AM_semantics=False).items():
|
||||
require(type(receipt.get(key)) is type(value) and receipt[key] == value, 'Wrapper receipt mismatch: ' + key)
|
||||
require(receipt['scope'] == 'constant sparse overlay installation only; no within-trial plasticity or learning', 'Wrapper scope changed')
|
||||
require(receipt['overlay_audit_sha256'] == file_hash(folder / 'overlay-audit.json') and
|
||||
receipt['inherited_summary_sha256'] == file_hash(folder / 'summary.json') and
|
||||
receipt['effective_weights_raw_sha256'] == run['summary']['effective_weights_raw_sha256'], 'Wrapper receipt not bound to output')
|
||||
return {key: value for key, value in receipt.items() if key != 'inherited_summary_sha256'}
|
||||
|
||||
|
||||
def _provenance(run, case, helper, used):
|
||||
summary, component = run['summary'], run['component']
|
||||
require(summary['input_sha256'] == case['input_hashes'], 'Actual case/weight/coefficient input hash differs')
|
||||
require(summary['all_finite'] is True and summary['counts_match_sparse_events'] is True and
|
||||
summary['learning'] is False and summary['state_units'] == 'SI volts', 'Invalid finite/state/learning declaration')
|
||||
require(summary['sealed_ae_backend_sha256'] == SEALED['ae_backend'], 'Frozen backend changed')
|
||||
build = summary['build']
|
||||
require(build['sources'] == {'author_ext.cpp': CPP_SHA, 'author_ext.cu': CUDA_SHA} and
|
||||
build['cuda_flags'] == ['-O2', '--fmad=false', '--ftz=false', '--prec-div=true', '--prec-sqrt=true'] and
|
||||
build['arch_list'] == '8.9', 'Build arithmetic/source contract changed')
|
||||
source = _json(run['directory'] / 'source-audit.json', used)
|
||||
require(source == summary['source_provenance'] and source['sealed_source_sha256'] == SEALED and
|
||||
source['candidate_sha256'] == AJ_SHA and source['module_globals_patched'] is False, 'Inherited AJ source audit changed')
|
||||
diff = run['directory'] / 'sealed-gpu-source.diff'
|
||||
used.add(diff)
|
||||
require(file_hash(diff) == source['source_diff_sha256'], 'Inherited source diff hash mismatch')
|
||||
bundle = case['bundle']
|
||||
require(component['files_sha256'] == {name: file_hash(case['bundle_dir'] / name) for name in bundle} and
|
||||
component['array_sha256'] == {name: raw_hash(value) for name, value in bundle.items()}, 'Actual APL bundle bytes changed')
|
||||
require(component['ok'] is True and component['protocol'] == 'phase4ag-apl-v1' and
|
||||
component['n_neurons'] == N and component['n_apl'] == 2 and component['n_kc'] == K and
|
||||
component['dt_ms'] == .1, 'APL component dimensions/clock changed')
|
||||
for key, value in (('decay_f32_bits', np.float32(math.exp(-.1 / 20.))), ('gain_f32_bits', np.float32(.01))):
|
||||
require(component[key] == f'{int(value.view(np.uint32)):08x}', 'APL constant changed: ' + key)
|
||||
env = component['environment']
|
||||
require(env['cuda_matmul_allow_tf32'] is False and env['float32_matmul_precision'] == 'highest' and
|
||||
env['precision_configuration_changed_by_component'] is False and '4090' in env['device_name'],
|
||||
'GPU precision environment changed')
|
||||
require(summary['apl_schedule'] == 'end, after threshold/reset and guarded arrivals, unconditional all KC' and
|
||||
summary['apl_si_conversion'] == 'applied h32 -> float64 -> multiply float64 0.001 -> separate float64 subtraction',
|
||||
'APL scheduling or SI conversion changed')
|
||||
for key in PERFORMANCE:
|
||||
require(type(summary[key]) in (int, float) and math.isfinite(summary[key]) and summary[key] >= 0, 'Invalid performance field: ' + key)
|
||||
|
||||
|
||||
def _summary_pair(left, right, checks, differences):
|
||||
a = {k: v for k, v in left.items() if k not in PERFORMANCE}
|
||||
b = {k: v for k, v in right.items() if k not in PERFORMANCE}
|
||||
checks['summary.nonperformance_exact'] = a == b
|
||||
if a != b:
|
||||
differences['summary'] = {key: {'left': a.get(key), 'right': b.get(key)}
|
||||
for key in sorted(set(a) | set(b)) if a.get(key) != b.get(key) or (key in a) != (key in b)}
|
||||
|
||||
|
||||
def compare_runs(case_dir, left, right, output_path=None, mode='zero'):
|
||||
"""Zero-vs-sealed-AK or AM repeat equality; failures return a failed report.
|
||||
|
||||
``output_path=None`` is read-only. A provided report is created exclusively,
|
||||
outside both run directories and the input case. Parent protocol must bind
|
||||
the case and old manifests; this comparator reports exactly the bytes read.
|
||||
"""
|
||||
case_dir, left, right = (Path(p).resolve() for p in (case_dir, left, right))
|
||||
if output_path is not None:
|
||||
output_path = Path(output_path).resolve()
|
||||
require(not output_path.exists(), 'Refusing existing comparison output')
|
||||
require(not any(output_path.is_relative_to(p) for p in (case_dir, left, right)), 'Comparison must not write into input evidence')
|
||||
checks, differences, used = {}, {}, {Path(__file__).resolve()}
|
||||
report = dict(schema='phase4am-comparison-v1', mode=mode, case_dir=str(case_dir), left=str(left), right=str(right),
|
||||
pass_all=False, learning_achieved=False, candidate_generated=False, tolerance=None,
|
||||
scope='Exact declared trajectory and static overlay integrity; no physiological or learning PASS',
|
||||
explicit_exclusions=sorted(PERFORMANCE), independent_cpu_wholebrain_oracle_claim=False)
|
||||
try:
|
||||
require(mode in ('zero', 'repeat', 'self'), 'Unknown comparison mode')
|
||||
require((left == right) == (mode == 'self'), 'Only explicit single-run validation may use one directory')
|
||||
helper = _helper(used)
|
||||
case = _load_case(case_dir, used)
|
||||
spec, graph = case['spec'], case['graph']
|
||||
n, edges, steps = len(graph['nodes']), len(graph['edge_id']), spec['steps']
|
||||
sides = ((left, 'left_gpu'),) if mode == 'self' else ((left, 'left_gpu'), (right, 'right_gpu'))
|
||||
runs = [helper._load_run(path, role, n, edges, steps, False, spec['name'], 18, used, checks)
|
||||
for path, role in sides]
|
||||
for role, run in zip(('left_gpu', 'right_gpu'), runs):
|
||||
_provenance(run, case, helper, used)
|
||||
require(all(np.isfinite(a).all() for a in run['state'].values()), 'Nonfinite final state/events')
|
||||
_self_consistency(run, graph, case['requested'], steps, helper, role, checks, differences)
|
||||
helper._timing_sequence_checks(run['apl']['inhibition'], run['apl']['applied'], run['pending'], 18, role, checks, differences)
|
||||
helper._timing_metadata_checks(run, role, 18, steps, checks)
|
||||
require(not (run['directory'] / 'first-failure-state.npz').exists(), 'Recorded first numerical failure exists')
|
||||
if mode != 'self':
|
||||
helper._pair_outputs(*runs, checks, differences, n, steps, False)
|
||||
require(runs[0]['timing'] == runs[1]['timing'], 'APL timing metadata differs')
|
||||
_summary_pair(runs[0]['summary'], runs[1]['summary'], checks, differences)
|
||||
baseline_sha, _, apl_count = _effective_hash(case)
|
||||
for run in runs:
|
||||
require(run['summary']['apl_fast_edges_zeroed'] == apl_count and run['summary']['kc_targets_present'] == K,
|
||||
'Fast APL or KC coverage changed')
|
||||
if mode == 'zero':
|
||||
require(not (left / 'overlay-audit.json').exists(), 'Zero reference must be the unwrapped sealed AK run')
|
||||
require(runs[0]['summary']['effective_weights_raw_sha256'] == baseline_sha, 'Sealed reference effective weights changed')
|
||||
audit = _overlay(runs[1], case, used, zero_required=True)
|
||||
_adapter_receipt(runs[1], used)
|
||||
report['overlay_name'] = audit['overlay_name']
|
||||
elif mode == 'repeat':
|
||||
a, b = [_overlay(run, case, used) for run in runs]
|
||||
require(a == b, 'Repeated overlay provenance/identity differs')
|
||||
receipts = [_adapter_receipt(run, used) for run in runs]
|
||||
require(receipts[0] == receipts[1], 'Repeated wrapper provenance differs beyond bound walltime-bearing summary')
|
||||
report['overlay_name'] = a['overlay_name']
|
||||
else:
|
||||
report['overlay_name'] = _overlay(runs[0], case, used)['overlay_name']
|
||||
_adapter_receipt(runs[0], used)
|
||||
report['scope'] = 'Single-run event/queue/input/static-overlay self-consistency; no repeat, equivalence, physiology or learning PASS'
|
||||
checks['input_overlay_and_source_provenance'] = True
|
||||
report.update(nodes=n, edges=edges, steps=steps, apl_delay_steps=18,
|
||||
full_queue_reconstructed=True, total_spikes=len(runs[0]['state']['step']))
|
||||
except Exception as error:
|
||||
report['error'] = {'type': type(error).__name__, 'message': str(error)}
|
||||
manifest = []
|
||||
try:
|
||||
for path in sorted(used):
|
||||
manifest.append(dict(path=str(path.resolve()), bytes=path.stat().st_size, sha256=file_hash(path)))
|
||||
except Exception as error:
|
||||
report['read_manifest_error'] = {'type': type(error).__name__, 'message': str(error)}
|
||||
report.update(checks={key: bool(value) for key, value in checks.items()}, differences=differences,
|
||||
read_manifest=manifest, failed_checks=sorted(key for key, value in checks.items() if not value))
|
||||
report['pass_all'] = bool(checks) and all(checks.values()) and 'error' not in report and 'read_manifest_error' not in report
|
||||
if output_path is not None:
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with output_path.open('x', encoding='utf-8') as target:
|
||||
json.dump(report, target, indent=2, sort_keys=True, allow_nan=False)
|
||||
target.write('\n')
|
||||
return report
|
||||
|
||||
|
||||
def validate_run(case_dir, output_dir, output_path=None):
|
||||
"""Single-run static-overlay integrity only; not numerical equivalence."""
|
||||
return compare_runs(case_dir, output_dir, output_dir, output_path, mode='self')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--case', required=True)
|
||||
parser.add_argument('--left', required=True)
|
||||
parser.add_argument('--right', required=True)
|
||||
parser.add_argument('--output', required=True)
|
||||
parser.add_argument('--mode', choices=('zero', 'repeat', 'self'), required=True)
|
||||
args = parser.parse_args()
|
||||
result = compare_runs(args.case, args.left, args.right, args.output, args.mode)
|
||||
print(json.dumps({'pass_all': result['pass_all'], 'failed_checks': result['failed_checks'], 'error': result.get('error')}, allow_nan=False))
|
||||
raise SystemExit(0 if result['pass_all'] else 1)
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"phase": "4AM",
|
||||
"purpose": "Exact D18 trial-boundary plastic-overlay interface and closed-loop transfer gate",
|
||||
"neurons": 166700,
|
||||
"edges": 25582938,
|
||||
"candidate_edges": 14232,
|
||||
"primary_trials": 120,
|
||||
"technical_repeats": 24,
|
||||
"steps": 1000,
|
||||
"dt_ms": 0.1,
|
||||
"apl_delay_steps": 18,
|
||||
"stages": ["zero", "static25"],
|
||||
"max_fractional_depression": 0.25,
|
||||
"virtual_gate_tick": 900,
|
||||
"gpu_uuid": "GPU-79f3a85d-adce-7833-7003-6488140ca596",
|
||||
"output_budget_bytes": 17179869184,
|
||||
"training": false,
|
||||
"candidate_generation": false,
|
||||
"static_test_is_learning": false,
|
||||
"precision_bridge": "base float64 SI plus separate float64(delta float32 mV) times 0.001; zero rows untouched",
|
||||
"future_learning": "Original frozen eligibility-v2 and additive trial-boundary update; artificial scalar D remains separate from actual DAN events"
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
"""New branch only. Old files are hash-anchored read-only inputs."""
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
ROOT = Path('/data/digital-drosophila')
|
||||
MODEL = Path(__file__).resolve().parent
|
||||
EXP = ROOT / 'experiments/phase4am-wholebrain-plastic-interface'
|
||||
AK = ROOT / 'experiments/phase4ak-apl-timing-validation'
|
||||
AL = ROOT / 'experiments/phase4al-mb-credit-transfer'
|
||||
AL_MODEL = ROOT / 'models/male-cns-lif/mb-credit-transfer-audit-v1'
|
||||
AJ_MODEL = ROOT / 'models/male-cns-lif/apl-timing-lab-v1'
|
||||
AG = ROOT / 'experiments/phase4ag-author-apl-r2'
|
||||
H = ROOT / 'experiments/phase4h-dopamine-plasticity'
|
||||
META = ROOT / 'datasets/male-cns-v1.0/processed/neuron-only/neuron-index/unified-metadata.parquet'
|
||||
CFG = json.loads((MODEL / 'config.json').read_text(encoding='utf-8'))
|
||||
PINS = {
|
||||
'models/male-cns-lif/mb-credit-transfer-audit-v1/context.py': '1626873baa224fab5d427a1fab1d6f840f14814152914ac4c2d6aa7a4f0bb1f6',
|
||||
'models/male-cns-lif/mb-credit-transfer-audit-v1/manifest.json': '72e4b4e2c53c258939ca355f31eeba2cd6381e126a61efeea651d8bac8df5328',
|
||||
'experiments/phase4al-mb-credit-transfer/manifest.json': 'a22de7d85cbfe760070ff55f5666e74475a3335c63bbd37b41241a94be7e3eea',
|
||||
'experiments/phase4al-mb-credit-transfer/audit/final-status.json': 'ec846137531d3bea357f57364857b5de5ca2381a01aac3ebb1b7ade79059209e',
|
||||
'models/male-cns-lif/apl-timing-lab-v1/gpu_timing.py': 'af1975ba5e5e700208c0a598d3cbe648a3cf10885c6231239b6e4b28f76d173b',
|
||||
'models/male-cns-lif/apl-timing-lab-v1/compare_timing.py': '9010f070fba9b7c9cf2d86187ee2abc57f3ac606ebc08a42baad4f3922b51734',
|
||||
'models/male-cns-lif/phase4c.py': '5ec83a132deb8ba20e62dab485537c2414016fe2fece546198d61c0668d75b6b',
|
||||
'models/male-cns-lif/mb-physiology-v1/phase4j.py': 'abb4574da85f00bd9ef61024c65993ef81d42651ff8d8d63c15dd69c6222471e',
|
||||
'experiments/phase4h-dopamine-plasticity/plastic-mask/plastic-edge-candidates.parquet': 'e5075ee3e5a0edf3a4d197453847163cc295b254f21c67203159bba204b12a76',
|
||||
'experiments/phase4h-dopamine-plasticity/plastic-mask/plastic-edge-ids.int64.npy': '8b0fb6eb2e6c31da4d17dc20d890aa3f367e514f05642de5bad6b42a1611c05b',
|
||||
'experiments/phase4h-dopamine-plasticity/plastic-mask/plastic-edge-mask.bool.npy': 'e8b7a439f07eae6e36729d6577a2c5e4a4c7aa4b15e97fc5266d7278db9e150e',
|
||||
'datasets/male-cns-v1.0/processed/neuron-only/neuron-index/unified-metadata.parquet': '922af16846a065c3dbfca7c11146b83ca217fcbd7392edc745f444a04eae8c63'
|
||||
}
|
||||
|
||||
|
||||
def digest(path):
|
||||
h = hashlib.sha256()
|
||||
with Path(path).open('rb') as f:
|
||||
for block in iter(lambda: f.read(8 << 20), b''):
|
||||
h.update(block)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def read(path):
|
||||
return json.loads(Path(path).read_text(encoding='utf-8'))
|
||||
|
||||
|
||||
def save(path, value):
|
||||
path = Path(path); path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open('x', encoding='utf-8') as f:
|
||||
json.dump(value, f, indent=2, ensure_ascii=False, allow_nan=False)
|
||||
f.write('\n')
|
||||
|
||||
|
||||
def write_npz(path, **values):
|
||||
import numpy as np
|
||||
with Path(path).open('xb') as f:
|
||||
np.savez_compressed(f, **values)
|
||||
|
||||
|
||||
def load_module(name, path, expected_hash):
|
||||
data = Path(path).read_bytes()
|
||||
assert hashlib.sha256(data).hexdigest() == expected_hash, str(path)
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[name] = module
|
||||
exec(compile(data, str(path), 'exec'), module.__dict__)
|
||||
return module
|
||||
|
||||
|
||||
def sources():
|
||||
return {p.relative_to(MODEL).as_posix(): digest(p) for p in sorted(MODEL.rglob('*'))
|
||||
if p.is_file() and '__pycache__' not in p.parts and p.name not in ('manifest.json', 'checksums.sha256')}
|
||||
|
||||
|
||||
def integrity():
|
||||
old = load_module('sealed_al_context_am', AL_MODEL / 'context.py',
|
||||
PINS['models/male-cns-lif/mb-credit-transfer-audit-v1/context.py'])
|
||||
result = old.integrity()
|
||||
sys.path.insert(0, str(ROOT / 'models/male-cns-lif/terminal-scope-diagnostic-v1'))
|
||||
from diagnostic import manifest_check
|
||||
for path in (AL_MODEL, AL):
|
||||
result[path.relative_to(ROOT).as_posix()] = manifest_check(path, digest)
|
||||
assert all(row['pass'] for row in result.values())
|
||||
return result
|
||||
|
||||
|
||||
def check(full=False):
|
||||
reg = read(EXP / 'protocol/preregistered.json')
|
||||
assert reg['sources'] == sources() and reg['config'] == CFG
|
||||
for row in reg['inputs'] if full else []:
|
||||
assert digest(ROOT / row['path']) == row['sha256'], row['path']
|
||||
for row in read(EXP / 'protocol/prepared-hashes.json'):
|
||||
assert digest(EXP / row['path']) == row['sha256'], row['path']
|
||||
return reg
|
||||
|
||||
|
||||
def bit_equal(a, b):
|
||||
return a.dtype == b.dtype and a.shape == b.shape and a.tobytes() == b.tobytes()
|
||||
|
||||
|
||||
def budget():
|
||||
size = sum(p.stat().st_size for p in EXP.rglob('*') if p.is_file())
|
||||
assert size < CFG['output_budget_bytes'], 'Output budget exceeded; stop and preserve all partials'
|
||||
return size
|
||||
|
||||
|
||||
def seal(root):
|
||||
rows = [dict(path=p.relative_to(root).as_posix(), bytes=p.stat().st_size, sha256=digest(p))
|
||||
for p in sorted(root.rglob('*')) if p.is_file() and '__pycache__' not in p.parts]
|
||||
save(root / 'manifest.json', dict(root=str(root), files=rows))
|
||||
with (root / 'checksums.sha256').open('x', encoding='utf-8') as f:
|
||||
for row in rows:
|
||||
f.write(row['sha256'] + ' ' + row['path'] + '\n')
|
||||
f.write(digest(root / 'manifest.json') + ' manifest.json\n')
|
||||
@@ -0,0 +1,285 @@
|
||||
"""Pure Phase 4J eligibility adapter; no brain run, training, or artifact writes.
|
||||
|
||||
D is the original artificial command scalar. Actual DAN request acceptance and
|
||||
spikes are separate evidence and MUST NOT silently replace D here. All input
|
||||
arrays remain unchanged. Returned arrays own their storage and are read-only.
|
||||
The NumPy trace backend is a candidate compatibility implementation; only an
|
||||
explicit comparison with the Torch backend establishes Torch bitwise agreement.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
from dataclasses import dataclass
|
||||
import hashlib
|
||||
import math
|
||||
from pathlib import Path
|
||||
from types import MappingProxyType, SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
PHASE4J_SHA256 = "abb4574da85f00bd9ef61024c65993ef81d42651ff8d8d63c15dd69c6222471e"
|
||||
DT_MS = 0.1
|
||||
TAU_MS = 100.0
|
||||
TRACE_CAP = 1.0
|
||||
ETA_MV = 0.00275
|
||||
FLOOR_FRACTION = 0.25
|
||||
POLARITY = -1
|
||||
COMPARTMENTS = ("B2B'2a", "a1", "y2a'1", "y5B'2a")
|
||||
FIXED_GATES = MappingProxyType({"negative_us_before_cs_delta_exact_zero": True,
|
||||
"plus_1000_to_zero_ratio_strict_upper": 0.01,
|
||||
"off_compartment_delta_exact_unchanged": True,
|
||||
"zero_d_and_frozen_delta_exact_unchanged": True})
|
||||
_TRACE_EXPRESSION = (
|
||||
"kc_trace.mul_(trace_decay).add_(spikes[self.kc_gpu].to(torch.float32))"
|
||||
".clamp_max_(1.0)"
|
||||
)
|
||||
|
||||
|
||||
def _require(condition: bool, message: str) -> None:
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def _readonly(array: np.ndarray) -> np.ndarray:
|
||||
result = np.array(array, copy=True, order="C")
|
||||
result.setflags(write=False)
|
||||
return result
|
||||
|
||||
|
||||
def bit_equal(left: np.ndarray, right: np.ndarray) -> bool:
|
||||
a, b = np.asarray(left), np.asarray(right)
|
||||
return a.dtype == b.dtype and a.shape == b.shape and a.tobytes() == b.tobytes()
|
||||
|
||||
|
||||
def _integer(value: Any, name: str, minimum: int) -> int:
|
||||
_require(isinstance(value, (int, np.integer)) and not isinstance(value, (bool, np.bool_)),
|
||||
f"{name} must be an integer")
|
||||
_require(int(value) >= minimum, f"{name} is below {minimum}")
|
||||
return int(value)
|
||||
|
||||
|
||||
def _array(value: Any, dtype: Any, name: str) -> np.ndarray:
|
||||
result = np.asarray(value)
|
||||
_require(result.ndim == 1 and result.dtype == np.dtype(dtype),
|
||||
f"{name} must be a one-dimensional {np.dtype(dtype)} array")
|
||||
return result
|
||||
|
||||
|
||||
def _kc_array(kc: np.ndarray, n_neurons: int) -> np.ndarray:
|
||||
kc = _array(kc, np.int32, "kc")
|
||||
_require(len(kc) > 0 and np.all((kc >= 0) & (kc < n_neurons)), "KC IDs out of range or empty")
|
||||
_require(np.all(kc[1:] > kc[:-1]), "KC IDs must be canonical sorted unique global IDs")
|
||||
return kc
|
||||
|
||||
|
||||
def _load_original(source: str | Path) -> tuple[Any, Any, dict[str, str]]:
|
||||
"""Verify the entire frozen source, then execute ONLY one function AST.
|
||||
|
||||
Old imports, globals, class initialization and main are never executed.
|
||||
The trace expression is also checked against the exact frozen expression.
|
||||
"""
|
||||
raw = Path(source).read_bytes()
|
||||
actual_sha = hashlib.sha256(raw).hexdigest()
|
||||
_require(actual_sha == PHASE4J_SHA256, "frozen Phase 4J source SHA256 mismatch")
|
||||
tree = ast.parse(raw.decode("utf-8"), filename=str(source))
|
||||
functions = [node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name == "apply_v2"]
|
||||
_require(len(functions) == 1, "frozen apply_v2 AST is missing or ambiguous")
|
||||
function = functions[0]
|
||||
classes = [node for node in tree.body if isinstance(node, ast.ClassDef) and node.name == "APLSimulator"]
|
||||
runs = [node for cls in classes for node in cls.body
|
||||
if isinstance(node, ast.FunctionDef) and node.name == "run"]
|
||||
expected = ast.parse(_TRACE_EXPRESSION).body[0]
|
||||
expected_dump = ast.dump(expected, include_attributes=False)
|
||||
expressions = [node for run in runs for node in ast.walk(run)
|
||||
if isinstance(node, ast.Expr) and ast.dump(node, include_attributes=False) == expected_dump]
|
||||
_require(len(expressions) == 1, "frozen Torch trace expression is missing or ambiguous")
|
||||
module = ast.Module(body=[function], type_ignores=[])
|
||||
namespace = {"np": np, "Validator": object, "Any": Any}
|
||||
exec(compile(module, str(source) + "::apply_v2-only", "exec"), namespace)
|
||||
trace_code = compile(ast.Module(body=[expressions[0]], type_ignores=[]),
|
||||
str(source) + "::trace-expression-only", "exec")
|
||||
evidence = dict(source_sha256=actual_sha,
|
||||
apply_v2_ast_sha256=hashlib.sha256(ast.dump(function, include_attributes=False).encode()).hexdigest(),
|
||||
trace_expression_ast_sha256=hashlib.sha256(expected_dump.encode()).hexdigest())
|
||||
return namespace["apply_v2"], trace_code, evidence
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TraceResult:
|
||||
command_steps: np.ndarray
|
||||
snapshots: np.ndarray
|
||||
final: np.ndarray
|
||||
audit: dict[str, Any]
|
||||
|
||||
@property
|
||||
def last_command_trace(self) -> np.ndarray:
|
||||
"""Original runtime's last scheduled-US snapshot, or zero without US."""
|
||||
return _readonly(self.snapshots[-1] if len(self.snapshots) else np.zeros_like(self.final))
|
||||
|
||||
|
||||
def trace_from_events(step: np.ndarray, neuron_idx: np.ndarray, kc: np.ndarray,
|
||||
steps: int, command_steps: Any, *, phase4j_source: str | Path,
|
||||
backend: str = "numpy", n_neurons: int = 166700) -> TraceResult:
|
||||
"""Replay actual sparse spikes; snapshot AFTER the current tick's spike.
|
||||
|
||||
One Boolean spike per global neuron/tick, including refractory ticks as
|
||||
recorded by the caller. This function does not reconstruct brain dynamics,
|
||||
infer missing events, or apply any refractory guard to eligibility.
|
||||
"""
|
||||
_, trace_code, evidence = _load_original(phase4j_source)
|
||||
steps = _integer(steps, "steps", 1)
|
||||
n_neurons = _integer(n_neurons, "n_neurons", 1)
|
||||
kc = _kc_array(kc, n_neurons)
|
||||
step = _array(step, np.int32, "step")
|
||||
neuron_idx = _array(neuron_idx, np.int32, "neuron_idx")
|
||||
_require(len(step) == len(neuron_idx), "event columns have different lengths")
|
||||
_require(np.all((step >= 0) & (step < steps)), "event tick outside trial")
|
||||
_require(np.all((neuron_idx >= 0) & (neuron_idx < n_neurons)), "event neuron outside graph")
|
||||
_require(np.all((step[1:] > step[:-1]) |
|
||||
((step[1:] == step[:-1]) & (neuron_idx[1:] > neuron_idx[:-1]))),
|
||||
"events must be sorted by tick/global ID and unique")
|
||||
commands = np.asarray(command_steps)
|
||||
_require(commands.ndim == 1, "command_steps must be one-dimensional")
|
||||
if commands.size == 0:
|
||||
commands = np.empty(0, np.int64)
|
||||
_require(commands.ndim == 1 and commands.dtype.kind in "iu", "command_steps must be integers")
|
||||
_require(np.all((commands >= 0) & (commands < steps)), "command tick outside trial")
|
||||
_require(np.all(commands[1:] > commands[:-1]), "command_steps must be sorted unique")
|
||||
commands = commands.astype(np.int64, copy=True)
|
||||
_require(backend in ("numpy", "torch"), "backend must be numpy or torch")
|
||||
lookup = np.full(n_neurons, -1, np.int32)
|
||||
lookup[kc] = np.arange(len(kc), dtype=np.int32)
|
||||
local = lookup[neuron_idx]
|
||||
keep = local >= 0
|
||||
ticks, local = step[keep], local[keep]
|
||||
snapshots = np.empty((len(commands), len(kc)), np.float32)
|
||||
decay = math.exp(-DT_MS / TAU_MS)
|
||||
command_lookup = {int(t): i for i, t in enumerate(commands)}
|
||||
cursor = 0
|
||||
torch_version = None
|
||||
if backend == "numpy":
|
||||
trace = np.zeros(len(kc), np.float32)
|
||||
spike_values = np.zeros(len(kc), np.float32)
|
||||
else:
|
||||
try:
|
||||
import torch
|
||||
except ImportError as exc:
|
||||
raise RuntimeError("Torch CPU reference unavailable; compatibility is not established") from exc
|
||||
torch_version = str(torch.__version__)
|
||||
trace = torch.zeros(len(kc), dtype=torch.float32, device="cpu")
|
||||
spike_values = torch.zeros(len(kc), dtype=torch.bool, device="cpu")
|
||||
environment = dict(torch=torch, kc_trace=trace, trace_decay=decay,
|
||||
spikes=spike_values,
|
||||
self=SimpleNamespace(kc_gpu=torch.arange(len(kc), dtype=torch.int64)))
|
||||
for tick in range(steps):
|
||||
end = cursor
|
||||
while end < len(ticks) and ticks[end] == tick:
|
||||
end += 1
|
||||
if backend == "numpy":
|
||||
spike_values.fill(0)
|
||||
spike_values[local[cursor:end]] = 1
|
||||
# Separate float32 operations; never power-jump across empty ticks.
|
||||
np.multiply(trace, np.float32(decay), out=trace)
|
||||
np.add(trace, spike_values, out=trace)
|
||||
np.minimum(trace, np.float32(TRACE_CAP), out=trace)
|
||||
if tick in command_lookup:
|
||||
snapshots[command_lookup[tick]] = trace
|
||||
else:
|
||||
spike_values.zero_()
|
||||
if end > cursor:
|
||||
spike_values[torch.from_numpy(local[cursor:end].astype(np.int64, copy=True))] = True
|
||||
exec(trace_code, environment)
|
||||
if tick in command_lookup:
|
||||
snapshots[command_lookup[tick]] = trace.numpy()
|
||||
cursor = end
|
||||
final = trace if backend == "numpy" else trace.numpy()
|
||||
audit = dict(evidence, backend=backend, torch_version=torch_version, numpy_version=str(np.__version__),
|
||||
steps=steps, kc_count=len(kc), actual_kc_events=int(keep.sum()),
|
||||
command_count=len(commands), dt_ms=DT_MS, trace_tau_ms=TAU_MS,
|
||||
trace_cap=TRACE_CAP, decay_python_float=decay,
|
||||
decay_float32_bits=int(np.asarray(np.float32(decay)).view(np.uint32)),
|
||||
sampling="after_current_tick_spike", interval_skip_optimization=False,
|
||||
torch_numpy_bitwise_comparison_performed=False,
|
||||
d_semantics="artificial scheduled command; not measured DAN activity",
|
||||
actual_dan_activity_used_to_gate=False, brain_dynamics_executed=False,
|
||||
training_executed=False, learning_pass_claimed=False)
|
||||
return TraceResult(_readonly(commands), _readonly(snapshots), _readonly(final), audit)
|
||||
|
||||
|
||||
class _Column:
|
||||
"""Small immutable-array interface consumed by the frozen function."""
|
||||
def __init__(self, values: np.ndarray):
|
||||
self.values = values
|
||||
|
||||
def to_numpy(self, dtype: Any = None) -> np.ndarray:
|
||||
return self.values.astype(dtype, copy=True) if dtype is not None else self.values.copy()
|
||||
|
||||
def astype(self, dtype: Any) -> "_Column":
|
||||
return _Column(self.values.astype(dtype))
|
||||
|
||||
|
||||
def apply_eligibility(trace: np.ndarray, kc: np.ndarray, candidate_sources: np.ndarray,
|
||||
candidate_compartments: np.ndarray, compartment: str,
|
||||
delta: np.ndarray, base_candidate: np.ndarray, *, d: float,
|
||||
frozen: bool = False, phase4j_source: str | Path,
|
||||
n_neurons: int = 166700) -> tuple[np.ndarray, dict[str, Any]]:
|
||||
"""Apply the SHA-pinned original apply_v2 AST to candidate-aligned arrays.
|
||||
|
||||
delta/base_candidate are ORIGINAL float32 mV values, not SI or reconstructed
|
||||
float64 D18 weights. Candidate row identity and the complete 14,232-edge mask
|
||||
are verified by the coordinator; a small synthetic subset is allowed here.
|
||||
Existing overlays must already obey the original floor and canonical +0.
|
||||
No input is clipped, repaired, reordered, or mutated.
|
||||
"""
|
||||
original, _, evidence = _load_original(phase4j_source)
|
||||
n_neurons = _integer(n_neurons, "n_neurons", 1)
|
||||
kc = _kc_array(kc, n_neurons)
|
||||
trace = _array(trace, np.float32, "trace")
|
||||
sources = _array(candidate_sources, np.int32, "candidate_sources")
|
||||
delta = _array(delta, np.float32, "delta")
|
||||
base = _array(base_candidate, np.float32, "base_candidate")
|
||||
compartments = np.asarray(candidate_compartments)
|
||||
_require(compartments.ndim == 1 and compartments.dtype.kind == "U", "candidate_compartments must be Unicode strings")
|
||||
_require(len(trace) == len(kc) and np.all(np.isfinite(trace)) and np.all((trace >= 0) & (trace <= TRACE_CAP)),
|
||||
"trace must match KC IDs and lie in [0,1]")
|
||||
_require(len(delta) == len(base) == len(sources) == len(compartments), "candidate columns have different lengths")
|
||||
_require(np.all(np.isin(sources, kc)), "all candidate sources must be canonical KCs")
|
||||
_require(compartment in COMPARTMENTS and np.all(np.isin(compartments, COMPARTMENTS)),
|
||||
"unrecognized exact frozen compartment")
|
||||
_require(isinstance(d, (float, int, np.floating, np.integer)) and not isinstance(d, (bool, np.bool_))
|
||||
and np.isfinite(d) and float(d) in (0.0, 1.0), "D must be the fixed artificial scalar 0 or 1")
|
||||
_require(isinstance(frozen, (bool, np.bool_)), "frozen must be Boolean")
|
||||
_require(np.all(np.isfinite(base)) and np.all(base > 0), "original candidate base must be finite positive float32 mV")
|
||||
lower = -FLOOR_FRACTION * base
|
||||
_require(np.all(np.isfinite(delta)) and np.all((delta >= lower) & (delta <= 0)), "delta violates original frozen floor")
|
||||
_require(not np.any((delta == 0) & np.signbit(delta)), "delta zero must be canonical +0 for exact identity")
|
||||
validator = SimpleNamespace(n=n_neurons, kc=kc.copy(), base_candidate=base.copy(),
|
||||
cand={"source_neuron_idx": _Column(sources.copy()),
|
||||
"target_compartment_verbatim": _Column(compartments.copy())})
|
||||
config = {"eligibility_v2": {"eta_effective_mv": ETA_MV,
|
||||
"max_fractional_depression": FLOOR_FRACTION}}
|
||||
if frozen:
|
||||
result = delta.copy()
|
||||
original_report = None
|
||||
else:
|
||||
result, original_report = original(validator, trace.copy(), compartment, float(d), delta.copy(), config)
|
||||
selected = compartments == compartment
|
||||
_require(result.dtype == np.float32 and np.all(np.isfinite(result)), "original update returned invalid values")
|
||||
_require(bit_equal(result[~selected], delta[~selected]), "off-compartment delta changed")
|
||||
if frozen or float(d) == 0:
|
||||
_require(bit_equal(result, delta), "zero-D/frozen identity failed")
|
||||
_require(np.all((result >= lower) & (result <= 0)), "original result violates frozen floor")
|
||||
report = dict(evidence, original_apply_v2_executed=not bool(frozen), original_report=original_report,
|
||||
candidate_count=len(delta), selected_compartment=compartment,
|
||||
selected_candidate_count=int(selected.sum()), d_command=float(d), frozen=bool(frozen),
|
||||
actual_changed_edges=int(np.count_nonzero(result != delta)),
|
||||
off_compartment_exact_unchanged=True, numpy_version=str(np.__version__),
|
||||
eta_effective_mv=ETA_MV, polarity=POLARITY, floor_fraction=FLOOR_FRACTION,
|
||||
delta_units="mV", delta_dtype="float32", original_base_units="mV",
|
||||
complete_mask_identity_verified_by_this_function=False,
|
||||
actual_dan_activity_used_to_gate=False,
|
||||
d_semantics="artificial scheduled command; not measured DAN activity",
|
||||
brain_dynamics_executed=False, training_executed=False, learning_pass_claimed=False)
|
||||
return _readonly(result), report
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Seal interface evidence; never promote static transfer to learned memory."""
|
||||
import yaml
|
||||
from context import ROOT, MODEL, EXP, read, save, digest, check, integrity, budget, seal
|
||||
|
||||
|
||||
def main():
|
||||
check(full=True)
|
||||
from summarize import run
|
||||
result = run(verify_only=True)
|
||||
refs = read(EXP / 'regression/frozen-five.json')
|
||||
names = [r['name'] for r in yaml.safe_load((ROOT / 'models/male-cns-lif/deterministic-runtime-v2/phase4p.yaml').read_text())['reference_trials']]
|
||||
assert len(names) == len(set(names)) == 5
|
||||
assert refs['pass_all'] is True and [r['trial'] for r in refs['trials']] == names
|
||||
assert all(set(r['exact']) == {'step', 'neuron_idx', 'counts', 'v', 'g'} and
|
||||
all(v is True for v in r['exact'].values()) for r in refs['trials'])
|
||||
old = integrity(); save(EXP / 'regression/old-integrity-after.json', old)
|
||||
final = dict(status=result['status'], zero_overlay_exact=True, static25_repeat_exact=True,
|
||||
primary_trials_per_stage=120, repeats_per_stage=24, old_integrity_pass=all(r['pass'] for r in old.values()),
|
||||
frozen_five_reference_pass=True, frozen_eligibility_and_mask_unchanged=True,
|
||||
base_weights_unchanged=True, training=False, learned_candidate=False,
|
||||
scientific_summary_sha256=digest(EXP / 'audit/scientific-summary.json'),
|
||||
payload_bytes_before_manifest=budget(), next_required='Separately preregister finite olfactory paired/control conditioning; no automatic learning PASS')
|
||||
save(EXP / 'audit/final-status.json', final)
|
||||
with (EXP / 'REPORT.md').open('x', encoding='utf-8') as f:
|
||||
f.write('# Phase 4AM — whole-brain plastic interface\n\nStatus: `' + result['status'] + '`.\n\n')
|
||||
f.write('All 144 zero-overlay runs reproduce sealed AK D18 exactly. All 120 fixed-overlay primary runs and 24 repeats are retained.\n')
|
||||
f.write('The fixed overlay is the original float32 25% floor, added through the declared float64 SI bridge. No training occurred.\n\n')
|
||||
for name, block in result['blocks'].items():
|
||||
f.write('- ' + name + ': ' + '; '.join(pop + ' event changes ' + str(v['trials_event_change']) + '/30' for pop, v in block['populations'].items()) + '.\n')
|
||||
f.write('\nOld artifacts and five deterministic-v2 references pass integrity/regression. This does not complete the learning goal.\n')
|
||||
check(full=True); seal(MODEL); seal(EXP)
|
||||
print(final['status'], flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"root": "/data/digital-drosophila/models/male-cns-lif/wholebrain-plastic-interface-v1",
|
||||
"files": [
|
||||
{
|
||||
"path": "PREPARATION.md",
|
||||
"bytes": 1625,
|
||||
"sha256": "85a3253ceb4e4c5d7bcf17b8b3294dbbf72bd1139415a892f6a90e8037785c89"
|
||||
},
|
||||
{
|
||||
"path": "PROTOCOL.md",
|
||||
"bytes": 5223,
|
||||
"sha256": "e03273995f4d5f1fc18d0af481374b8688ae735d83ae718895ffb741f8ac9399"
|
||||
},
|
||||
{
|
||||
"path": "adapter.py",
|
||||
"bytes": 15690,
|
||||
"sha256": "3daabe771d3eef6c6189827c0ee5159b266c3a058e07da5781bb863be428aef9"
|
||||
},
|
||||
{
|
||||
"path": "comparison.py",
|
||||
"bytes": 37000,
|
||||
"sha256": "4d64020af05e7fc9e535ac8532dd0e7b614b200f695ed8c6d486c6151de57175"
|
||||
},
|
||||
{
|
||||
"path": "config.json",
|
||||
"bytes": 848,
|
||||
"sha256": "c19f00c81f12c4c3503d3d557bd73d0722d4d3fed7279449408c67a89a6ffcf9"
|
||||
},
|
||||
{
|
||||
"path": "context.py",
|
||||
"bytes": 5444,
|
||||
"sha256": "0fa2e0892c973fe70ecbc0613e3cb508c256b7745042f8936edf23dedc31d166"
|
||||
},
|
||||
{
|
||||
"path": "eligibility_bridge.py",
|
||||
"bytes": 15196,
|
||||
"sha256": "174fb8d14619189e74b095e77a6db18b14c93046dc431c0b55710a5aa2dd2e61"
|
||||
},
|
||||
{
|
||||
"path": "finalize.py",
|
||||
"bytes": 2472,
|
||||
"sha256": "b8e4fb59d4efa261a8615f1456bc2ad1f1822e941af558b27074b8c41dba15a1"
|
||||
},
|
||||
{
|
||||
"path": "overlay.py",
|
||||
"bytes": 11355,
|
||||
"sha256": "7f891d0ba79b393c96e75fd2dee2eddc51e57d8213eaac2779703bc2c18712b4"
|
||||
},
|
||||
{
|
||||
"path": "prepare.py",
|
||||
"bytes": 9824,
|
||||
"sha256": "40976f0ed0b01fbf275c245de0f0a907592d23f5ca4733293b41e1d011c7ee62"
|
||||
},
|
||||
{
|
||||
"path": "run_suite.py",
|
||||
"bytes": 6857,
|
||||
"sha256": "6a6fcf4cc64d27234ab69ce5a9dcdb8bbd8b3b4965c9c08c2f9d20747025e9e7"
|
||||
},
|
||||
{
|
||||
"path": "summarize.py",
|
||||
"bytes": 6359,
|
||||
"sha256": "b4f3cacf3ab60f70d63713d5bbc3e03658f38e357a47ef219b1050003176df01"
|
||||
},
|
||||
{
|
||||
"path": "test_comparison.py",
|
||||
"bytes": 22896,
|
||||
"sha256": "a6274f338307a6961e514f565ab3e731051846caf62901e6843037755adc6fd8"
|
||||
},
|
||||
{
|
||||
"path": "test_eligibility_bridge.py",
|
||||
"bytes": 12900,
|
||||
"sha256": "113435c0f3528e3901e47421570a5f906d192826df9fc4ee600429a87ab904c5"
|
||||
},
|
||||
{
|
||||
"path": "test_overlay_adapter.py",
|
||||
"bytes": 14389,
|
||||
"sha256": "1c0536e0d0c27de6ab152ebb5aa0015ee31325417b17d3cfedc5f43bafa2ee79"
|
||||
},
|
||||
{
|
||||
"path": "test_workflow.py",
|
||||
"bytes": 2485,
|
||||
"sha256": "351a0c2887d657e1942b849d768d7efe8c52c0c3c67d0cf4d55658549c3bfe32"
|
||||
},
|
||||
{
|
||||
"path": "test_workflow_identity.py",
|
||||
"bytes": 11771,
|
||||
"sha256": "f1453a8814339ec7860ef4dee20549cc811d13cefd7aee2d308ebdfd51a839ca"
|
||||
},
|
||||
{
|
||||
"path": "verify_frozen.py",
|
||||
"bytes": 2383,
|
||||
"sha256": "5b121184ec3e666814dd440356604e96520aecdd8902d5b68c7d61caa7f224c5"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
"""Read-only sparse plastic-overlay validation and independent SI arithmetic.
|
||||
|
||||
This module does not calculate eligibility, generate learning updates, simulate
|
||||
neurons, or alter a graph. File loading enforces the complete frozen 14,232-row
|
||||
candidate identity; the array helpers also accept small synthetic identities.
|
||||
The externally anchored manifest is a provenance input, not its own authority.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
CANDIDATE_COUNT = 14232
|
||||
EDGE_COUNT = 25582938
|
||||
SCHEMA = 'phase4am-overlay-v1'
|
||||
BRIDGE = 'baseSI64 + (delta32 widened to float64) * float64(0.001); nonzero rows only'
|
||||
|
||||
|
||||
def require(ok, message):
|
||||
if not ok:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def file_hash(path):
|
||||
digest = hashlib.sha256()
|
||||
with Path(path).open('rb') as source:
|
||||
for block in iter(lambda: source.read(8 << 20), b''):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def raw_hash(value):
|
||||
require(isinstance(value, np.ndarray), 'Raw hash requires an ndarray')
|
||||
dtype = value.dtype.newbyteorder('<')
|
||||
return hashlib.sha256(np.ascontiguousarray(value, dtype=dtype).tobytes()).hexdigest()
|
||||
|
||||
|
||||
def bit_equal(first, second):
|
||||
return (isinstance(first, np.ndarray) and isinstance(second, np.ndarray) and
|
||||
first.dtype == second.dtype and first.shape == second.shape and
|
||||
first.tobytes(order='C') == second.tobytes(order='C'))
|
||||
|
||||
|
||||
def _vector(value, dtype, name, size=None):
|
||||
require(isinstance(value, np.ndarray) and value.dtype == np.dtype(dtype) and value.ndim == 1,
|
||||
name + ' requires exact ' + dtype + ' vector; no implicit conversion')
|
||||
require(size is None or value.shape == (size,), name + ' length differs')
|
||||
return value
|
||||
|
||||
|
||||
def _ids(value, name):
|
||||
_vector(value, 'int64', name)
|
||||
require(np.all((value >= 0) & (value < EDGE_COUNT)) and np.all(value[1:] > value[:-1]),
|
||||
name + ' must be sorted unique frozen-range global edge IDs')
|
||||
return value
|
||||
|
||||
|
||||
def _owned(value):
|
||||
result = np.array(value, copy=True, order='C')
|
||||
result.flags.writeable = False
|
||||
return result
|
||||
|
||||
|
||||
def validate_arrays(edge_id, plastic_delta_mv, mask_edge_id, base_candidate_mv=None):
|
||||
"""Validate/copy arrays; small masks are permitted only for pure unit helpers.
|
||||
|
||||
The production file loader separately insists on CANDIDATE_COUNT. Every
|
||||
nonzero delta needs the original, positive float32 base in legacy mV.
|
||||
Its bound is the frozen float32 expression -0.25 * base_candidate, NOT
|
||||
a fraction recomputed from the author's float64 SI runtime weights.
|
||||
"""
|
||||
ids, mask = _ids(edge_id, 'edge_id'), _ids(mask_edge_id, 'mask_edge_id')
|
||||
delta = _vector(plastic_delta_mv, 'float32', 'plastic_delta_mv', len(ids))
|
||||
require(len(ids) > 0 and bit_equal(ids, mask), 'Candidate IDs must equal the complete anchored mask in order')
|
||||
require(np.isfinite(delta).all() and np.all(delta <= np.float32(0)), 'Delta must be finite, nonpositive float32 mV')
|
||||
base = None
|
||||
if base_candidate_mv is not None:
|
||||
base = _vector(base_candidate_mv, 'float32', 'base_candidate_mv', len(ids))
|
||||
require(np.isfinite(base).all() and np.all(base > np.float32(0)), 'Original candidate base must be finite, positive float32 mV')
|
||||
lower = np.multiply(np.float32(-.25), base)
|
||||
require(np.all(delta >= lower), 'Delta exceeds the frozen original-float32 -25% bound')
|
||||
else:
|
||||
require(not np.any(delta != np.float32(0)), 'Nonzero delta requires the authoritative original float32 base')
|
||||
return {'edge_id': _owned(ids), 'plastic_delta_mv': _owned(delta),
|
||||
'base_candidate_mv': None if base is None else _owned(base)}
|
||||
|
||||
|
||||
def _checked_file(manifest, path_key, hash_key):
|
||||
path = Path(manifest[path_key])
|
||||
expected = manifest[hash_key]
|
||||
require(path.is_absolute() and path.is_file(), 'Missing absolute input: ' + path_key)
|
||||
require(isinstance(expected, str) and re.fullmatch('[0-9a-f]{64}', expected) is not None,
|
||||
'Malformed SHA256: ' + hash_key)
|
||||
require(file_hash(path) == expected, 'Input SHA256 mismatch: ' + path_key)
|
||||
return path
|
||||
|
||||
|
||||
def load_overlay(overlay_file, overlay_manifest):
|
||||
"""Load immutable, hash-bound production inputs without importing Torch.
|
||||
|
||||
Manifest keys: schema, overlay_name (zero/static25), overlay_sha256,
|
||||
mask_path/mask_sha256, base_candidate_path/base_candidate_sha256 (both
|
||||
null only for zero), aj_model_dir. Extra provenance fields are retained
|
||||
in the copied original manifest, not treated as executable instructions.
|
||||
NPZ keys: edge_id, plastic_delta_mv; optional base_candidate_mv must be
|
||||
bit-identical to the authoritative external .npy, never a replacement.
|
||||
"""
|
||||
overlay_file, manifest_file = Path(overlay_file).absolute(), Path(overlay_manifest).absolute()
|
||||
require(overlay_file.is_file() and manifest_file.is_file(), 'Missing overlay or manifest')
|
||||
manifest_bytes = manifest_file.read_bytes()
|
||||
manifest_sha = hashlib.sha256(manifest_bytes).hexdigest()
|
||||
metadata = json.loads(manifest_bytes.decode('utf-8'))
|
||||
required = {'schema', 'overlay_name', 'overlay_sha256', 'mask_path', 'mask_sha256',
|
||||
'base_candidate_path', 'base_candidate_sha256', 'aj_model_dir'}
|
||||
require(isinstance(metadata, dict) and required <= metadata.keys(), 'Incomplete overlay manifest')
|
||||
require(metadata['schema'] == SCHEMA and metadata['overlay_name'] in ('zero', 'static25'), 'Unregistered interface overlay kind')
|
||||
require(file_hash(overlay_file) == metadata['overlay_sha256'], 'Overlay file SHA256 mismatch')
|
||||
aj_dir = Path(metadata['aj_model_dir'])
|
||||
require(aj_dir.is_absolute() and aj_dir.is_dir(), 'aj_model_dir must name the real sealed AJ directory')
|
||||
mask_path = _checked_file(metadata, 'mask_path', 'mask_sha256')
|
||||
mask = np.load(mask_path, allow_pickle=False)
|
||||
_ids(mask, 'authoritative mask')
|
||||
require(mask.shape == (CANDIDATE_COUNT,), 'Frozen mask must contain exactly 14,232 candidates')
|
||||
base_path, base = None, None
|
||||
if metadata['base_candidate_path'] is None:
|
||||
require(metadata['base_candidate_sha256'] is None, 'Absent base needs a null base hash')
|
||||
else:
|
||||
base_path = _checked_file(metadata, 'base_candidate_path', 'base_candidate_sha256')
|
||||
base = np.load(base_path, allow_pickle=False)
|
||||
with np.load(overlay_file, allow_pickle=False) as archive:
|
||||
require({'edge_id', 'plastic_delta_mv'} <= set(archive.files) <=
|
||||
{'edge_id', 'plastic_delta_mv', 'base_candidate_mv'}, 'Unexpected overlay NPZ schema')
|
||||
values = validate_arrays(archive['edge_id'], archive['plastic_delta_mv'], mask, base)
|
||||
if 'base_candidate_mv' in archive.files:
|
||||
require(base is not None and bit_equal(archive['base_candidate_mv'], base),
|
||||
'Embedded base is not the authoritative external original float32 base')
|
||||
delta = values['plastic_delta_mv']
|
||||
if metadata['overlay_name'] == 'zero':
|
||||
require(not np.any(delta != 0), 'Named zero overlay is not zero')
|
||||
else:
|
||||
require(base is not None and bit_equal(delta, np.multiply(np.float32(-.25), base)),
|
||||
'static25 must exactly equal the original float32 floor, not 25% of float64 runtime base')
|
||||
values.update(metadata=metadata, input={
|
||||
'overlay_file': str(overlay_file), 'overlay_sha256': metadata['overlay_sha256'],
|
||||
'manifest_file': str(manifest_file), 'manifest_sha256': manifest_sha,
|
||||
'mask_file': str(mask_path), 'mask_sha256': metadata['mask_sha256'],
|
||||
'base_candidate_file': None if base_path is None else str(base_path),
|
||||
'base_candidate_sha256': metadata['base_candidate_sha256']})
|
||||
verify_inputs(values)
|
||||
return values
|
||||
|
||||
|
||||
def verify_inputs(plan):
|
||||
for name in ('overlay', 'manifest', 'mask', 'base_candidate'):
|
||||
path, expected = plan['input'][name + '_file'], plan['input'][name + '_sha256']
|
||||
require((path is None and expected is None) or (path is not None and file_hash(path) == expected),
|
||||
'Overlay input changed: ' + name)
|
||||
|
||||
|
||||
def map_installation(base_si, graph_edge_id, apl_out_mask, plan):
|
||||
"""Return sparse nonzero installation rows and independently computed values.
|
||||
|
||||
Missing zero-delta mask members are permitted in a small fixture. Missing
|
||||
nonzero members fail closed. APL/candidate overlap is always rejected.
|
||||
Neither base nor any caller array is changed. Zero delta has no write row.
|
||||
"""
|
||||
ids = _ids(graph_edge_id, 'graph_edge_id')
|
||||
base = _vector(base_si, 'float64', 'base_si', len(ids))
|
||||
apl = _vector(apl_out_mask, 'bool', 'apl_out_mask', len(ids))
|
||||
require(np.isfinite(base).all(), 'Nonfinite graph SI base')
|
||||
candidates = plan['edge_id']
|
||||
delta = plan['plastic_delta_mv']
|
||||
rows = np.searchsorted(ids, candidates)
|
||||
present = rows < len(ids)
|
||||
present[present] &= ids[rows[present]] == candidates[present]
|
||||
nonzero = delta != np.float32(0)
|
||||
require(not np.any(nonzero & ~present), 'A nonzero plastic candidate is absent from this graph')
|
||||
mapped = rows[present]
|
||||
require(not np.any(apl[mapped]), 'Plastic candidate overlaps the frozen fast-APL output mask')
|
||||
require(np.all(base[mapped] > np.float64(0)), 'Candidate graph SI base must remain positive')
|
||||
selected = rows[nonzero].astype(np.int64)
|
||||
delta_si = np.multiply(delta[nonzero].astype(np.float64), np.float64(.001))
|
||||
expected = np.add(base[selected], delta_si)
|
||||
require(np.isfinite(expected).all() and np.all(expected > 0), 'Overlay would change a candidate sign or create nonfinite weights')
|
||||
return {'rows': _owned(selected), 'delta_si': _owned(delta_si), 'expected_si': _owned(expected),
|
||||
'mapped_candidate_count': int(present.sum()), 'missing_candidate_count': int((~present).sum()),
|
||||
'installed_nonzero_count': int(nonzero.sum()), 'apl_overlap_count': 0}
|
||||
|
||||
|
||||
def materialize_numpy(base_si, graph_edge_id, apl_out_mask, plan):
|
||||
"""Independent pure CPU reference; used by unit tests, not a brain run."""
|
||||
mapping = map_installation(base_si, graph_edge_id, apl_out_mask, plan)
|
||||
result = np.array(base_si, copy=True, order='C')
|
||||
result[apl_out_mask] = np.float64(0)
|
||||
result[mapping['rows']] = mapping['expected_si']
|
||||
return result, mapping
|
||||
|
||||
|
||||
def expected_hashes(base_si, apl_out_mask, mapping, chunk_elements=1 << 20):
|
||||
"""Bounded-memory base/effective raw hashes, preserving untouched signed bits."""
|
||||
require(type(chunk_elements) is int and chunk_elements > 0, 'Invalid hash chunk size')
|
||||
base_hash, effective_hash = hashlib.sha256(), hashlib.sha256()
|
||||
rows = mapping['rows']
|
||||
for start in range(0, len(base_si), chunk_elements):
|
||||
stop = min(start + chunk_elements, len(base_si))
|
||||
original = np.ascontiguousarray(base_si[start:stop], dtype='<f8')
|
||||
base_hash.update(original.tobytes())
|
||||
effective = original.copy()
|
||||
effective[apl_out_mask[start:stop]] = np.float64(0)
|
||||
lo, hi = np.searchsorted(rows, (start, stop))
|
||||
effective[rows[lo:hi] - start] = mapping['expected_si'][lo:hi]
|
||||
effective_hash.update(effective.tobytes())
|
||||
return base_hash.hexdigest(), effective_hash.hexdigest()
|
||||
|
||||
|
||||
def array_metadata(value):
|
||||
return {'dtype': value.dtype.name, 'shape': list(value.shape), 'raw_sha256': raw_hash(value)}
|
||||
@@ -0,0 +1,145 @@
|
||||
"""Register the exact zero/static interface tests; create no brain trajectory."""
|
||||
import ast
|
||||
from datetime import datetime, timezone
|
||||
import hashlib
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from context import (ROOT, MODEL, EXP, AK, AL, H, AG, META, AJ_MODEL, CFG, PINS,
|
||||
read, save, digest, sources, integrity, write_npz)
|
||||
|
||||
|
||||
def old_base_candidate(raw, source_path):
|
||||
"""Evaluate exactly the pinned old assignment RHS; all candidate KC signs +1."""
|
||||
assert raw.dtype == np.int64 and raw.ndim == 1 and np.all(raw > 0)
|
||||
tree = ast.parse(source_path.read_text())
|
||||
runtime = next(n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == 'Runtime')
|
||||
init = next(n for n in runtime.body if isinstance(n, ast.FunctionDef) and n.name == '__init__')
|
||||
selected = [n for n in ast.walk(init) if isinstance(n, ast.Assign) and any(
|
||||
isinstance(t, ast.Subscript) and isinstance(t.value, ast.Name) and t.value.id == 'effective'
|
||||
for t in n.targets)]
|
||||
assert len(selected) == 1
|
||||
value = selected[0].value
|
||||
assert ast.unparse(value) == "raw_np[a:b].astype(np.float32) * signs[src] * float(cfg['model']['w_syn_mv_per_anatomical_synapse'])"
|
||||
env = dict(raw_np=raw, a=0, b=len(raw), np=np, signs=np.array([1], np.int8), src=0,
|
||||
cfg={'model': {'w_syn_mv_per_anatomical_synapse': .275}}, float=float)
|
||||
out = eval(compile(ast.Expression(value), str(source_path), 'eval'), {'__builtins__': {}}, env)
|
||||
assert out.dtype == np.float32 and np.isfinite(out).all() and np.all(out > 0)
|
||||
return out, hashlib.sha256(ast.dump(value, include_attributes=False).encode()).hexdigest()
|
||||
|
||||
|
||||
def main():
|
||||
assert not EXP.exists() or (EXP.is_dir() and not any(EXP.iterdir())), 'Never reuse a populated experiment'
|
||||
assert shutil.disk_usage(EXP.parent).free > CFG['output_budget_bytes'] + 10 * 1024**3
|
||||
for relative, sha in PINS.items():
|
||||
assert digest(ROOT / relative) == sha, relative
|
||||
assert read(AL / 'audit/final-status.json')['status'] == 'PASS-CONDITIONAL-CREDIT-TRANSFER-AUDIT-NOT-LEARNING'
|
||||
assert CFG['stages'] == ['zero', 'static25'] and not CFG['training'] and not CFG['candidate_generation']
|
||||
records = read(AK / 'protocol/validation-cases.json')
|
||||
assert len(records) == 120 and len({r['name'] for r in records}) == 120
|
||||
first_seed = min(r['seed'] for r in records)
|
||||
planned = [dict(row, repeat=repeat) for row in records
|
||||
for repeat in ((0, 1) if row['seed'] == first_seed else (0,))]
|
||||
assert len(planned) == 144
|
||||
inputs = dict(PINS)
|
||||
static = [AK / 'protocol/validation-cases.json',
|
||||
H / 'mappings/mbon-to-downstream-actual-edges.parquet',
|
||||
ROOT / 'experiments/phase4l-olfactory-grounding/odor-encoder/selection.json',
|
||||
ROOT / 'experiments/phase4af-author-fullgraph/oracle/no_input/coefficients.json']
|
||||
static += list((AG / 'graphs/full').glob('*.npy')) + list((AG / 'apl-bundle').glob('*.npy'))
|
||||
raw_path = ROOT / 'datasets/male-cns-v1.0/processed/neuron-only/gpu/csr-weights.int64.npy'
|
||||
static.append(raw_path)
|
||||
for row in planned:
|
||||
case = AK / 'cases' / row['cohort'] / row['name']
|
||||
spec = read(case / 'spec.json')
|
||||
assert spec['steps'] == 1000 and spec['dt_ms'] == .1 and spec['apl_delay_steps'] == 18
|
||||
assert spec['model']['w_syn_mv_per_anatomical_synapse'] == .275
|
||||
static += [case / 'spec.json', case / 'requests.npz']
|
||||
for p in static:
|
||||
inputs[p.relative_to(ROOT).as_posix()] = digest(p)
|
||||
for name in ('protocol', 'overlays', 'gpu', 'audit', 'regression', 'logs', 'extension-build', 'summaries'):
|
||||
(EXP / name).mkdir(parents=True, exist_ok=False)
|
||||
test = subprocess.run([sys.executable, '-B', '-m', 'unittest', 'discover', '-v', '-s', str(MODEL)],
|
||||
cwd=MODEL, text=True, capture_output=True)
|
||||
with (EXP / 'regression/unit-tests.log').open('x', encoding='utf-8') as f:
|
||||
f.write(test.stdout + test.stderr)
|
||||
save(EXP / 'regression/unit-tests.json', dict(pass_all=test.returncode == 0,
|
||||
exit_code=test.returncode, log_sha256=digest(EXP / 'regression/unit-tests.log')))
|
||||
assert test.returncode == 0, 'Unit tests failed; preserve partial experiment'
|
||||
assert 'skipped=' not in test.stderr, 'All server compatibility tests must execute'
|
||||
save(EXP / 'protocol/preregistered.json', dict(timestamp_utc=datetime.now(timezone.utc).isoformat(),
|
||||
sources=sources(), config=CFG, planned=planned,
|
||||
inputs=[dict(path=k, sha256=v) for k, v in sorted(inputs.items())],
|
||||
parent_commit=subprocess.check_output(['git', '-C', str(ROOT), 'rev-parse', 'HEAD'], text=True).strip(),
|
||||
before_new_brain_trajectories=True, prior_AK_AL_results_known=True,
|
||||
no_dynamic_pair_selection=True, training=False, candidate_generated=False,
|
||||
python=sys.version, numpy=np.__version__, platform=platform.platform()))
|
||||
save(EXP / 'regression/old-integrity-before.json', integrity())
|
||||
candidate = pd.read_parquet(H / 'plastic-mask/plastic-edge-candidates.parquet')
|
||||
mask_path = H / 'plastic-mask/plastic-edge-ids.int64.npy'
|
||||
ids = np.load(mask_path, allow_pickle=False)
|
||||
mask = np.load(H / 'plastic-mask/plastic-edge-mask.bool.npy', allow_pickle=False)
|
||||
assert ids.dtype == np.int64 and ids.shape == (14232,)
|
||||
assert np.array_equal(candidate.edge_id, ids) and np.array_equal(np.flatnonzero(mask), ids)
|
||||
graph = {k: np.load(AG / 'graphs/full' / (k + '.npy'), mmap_mode='r')
|
||||
for k in ('src', 'dst', 'edge_id', 'weights', 'signed_raw', 'nodes')}
|
||||
assert np.array_equal(graph['nodes'], np.arange(166700, dtype=np.int32))
|
||||
assert np.array_equal(graph['edge_id'], np.arange(25582938, dtype=np.int64))
|
||||
assert np.array_equal(graph['src'][ids], candidate.source_neuron_idx)
|
||||
assert np.array_equal(graph['dst'][ids], candidate.target_neuron_idx)
|
||||
raw = np.load(raw_path, mmap_mode='r')
|
||||
assert np.array_equal(raw[ids], candidate.raw_synapse_count)
|
||||
assert np.array_equal(graph['signed_raw'][ids], raw[ids]), 'Original candidate sign must be +1'
|
||||
assert np.all(graph['weights'][ids] > 0) and (candidate.plasticity_polarity == -1).all()
|
||||
base, rhs_sha = old_base_candidate(raw[ids], ROOT / 'models/male-cns-lif/phase4c.py')
|
||||
base_path = EXP / 'protocol/base-candidate.float32-mV.npy'
|
||||
with base_path.open('xb') as f:
|
||||
np.save(f, base, allow_pickle=False)
|
||||
for stage in CFG['stages']:
|
||||
delta = np.zeros(len(ids), np.float32) if stage == 'zero' else (-float(.25) * base).astype(np.float32)
|
||||
path = EXP / 'overlays' / (stage + '.npz')
|
||||
write_npz(path, edge_id=ids, plastic_delta_mv=delta)
|
||||
save(EXP / 'overlays' / (stage + '.json'), dict(schema='phase4am-overlay-v1', overlay_name=stage,
|
||||
overlay_sha256=digest(path), mask_path=str(mask_path), mask_sha256=digest(mask_path),
|
||||
base_candidate_path=str(base_path), base_candidate_sha256=digest(base_path),
|
||||
aj_model_dir=str(AJ_MODEL), base_rhs_ast_sha256=rhs_sha,
|
||||
old_f32_expression_source_sha256=digest(ROOT / 'models/male-cns-lif/phase4c.py'),
|
||||
training=False, fixed_for_whole_trial=True))
|
||||
meta = pd.read_parquet(META).sort_values('neuron_idx').reset_index(drop=True)
|
||||
assert np.array_equal(meta.neuron_idx.to_numpy(), graph['nodes'])
|
||||
kc = np.flatnonzero(meta.annotation_class.eq('Kenyon_Cell')).astype(np.int32)
|
||||
mbon = np.flatnonzero(meta.annotation_class.eq('MBON')).astype(np.int32)
|
||||
assert len(kc) == 4064 and len(mbon) == 97
|
||||
assert np.isin(candidate.source_neuron_idx, kc).all() and np.isin(candidate.target_neuron_idx, mbon).all()
|
||||
down = pd.read_parquet(H / 'mappings/mbon-to-downstream-actual-edges.parquet')
|
||||
assert np.array_equal(graph['src'][down.edge_id.to_numpy()], down.source_neuron_idx.to_numpy())
|
||||
assert np.array_equal(graph['dst'][down.edge_id.to_numpy()], down.target_neuron_idx.to_numpy())
|
||||
populations = dict(KC=kc, MBON=mbon,
|
||||
mask_recipient_MBON=np.unique(candidate.target_neuron_idx.to_numpy(dtype=np.int32)),
|
||||
DAN=np.flatnonzero(meta.annotation_class.eq('DAN')).astype(np.int32),
|
||||
MBON_downstream=np.unique(down.target_neuron_idx.to_numpy(dtype=np.int32)))
|
||||
# Exact observed superclass strings are checked; no inferred body identity.
|
||||
role_labels = {'descending': ['descending_neuron', 'descending_neuron_tbc'],
|
||||
'motor': ['vnc_motor', 'cb_motor']}
|
||||
for name, labels in role_labels.items():
|
||||
assert set(labels).issubset(set(meta.annotation_superclass.dropna().astype(str))), labels
|
||||
populations[name] = np.flatnonzero(meta.annotation_superclass.isin(labels)).astype(np.int32)
|
||||
write_npz(EXP / 'protocol/populations.npz', **populations)
|
||||
save(EXP / 'protocol/static-audit.json', dict(candidate_edges=len(ids), mask_fixed=True,
|
||||
old_base_rhs_ast_sha256=rhs_sha, populations={k: len(v) for k, v in populations.items()},
|
||||
exact_descending_motor_superclass_labels=role_labels,
|
||||
compartments={str(k): len(g) for k, g in candidate.groupby('target_compartment_verbatim')},
|
||||
old_selected_odors=read(ROOT / 'experiments/phase4l-olfactory-grounding/odor-encoder/selection.json'),
|
||||
source_mapping='Exact annotation_class/superclass and frozen actual-edge table; no inferred type/body IDs',
|
||||
old_base_signed_positive=True, training=False))
|
||||
save(EXP / 'protocol/prepared-hashes.json', [dict(path=p.relative_to(EXP).as_posix(), sha256=digest(p))
|
||||
for folder in ('protocol', 'overlays') for p in sorted((EXP / folder).rglob('*'))
|
||||
if p.is_file() and p.name != 'prepared-hashes.json'])
|
||||
print('PREREGISTERED zero 144 then static25 144; no brain trajectories or training', flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,132 @@
|
||||
"""Finite fixed inventory; no overwrites, no automatic scientific retries."""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
import urllib.request
|
||||
from context import ROOT, MODEL, EXP, AK, CFG, read, save, digest, check, budget
|
||||
|
||||
|
||||
def preflight():
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = CFG['gpu_uuid']
|
||||
os.environ['CUDA_HOME'] = '/usr/local/cuda'
|
||||
os.environ['PATH'] = str(ROOT / 'models/male-cns-lif/.venv/bin') + ':/usr/local/cuda/bin:' + os.environ['PATH']
|
||||
os.environ['MAX_JOBS'] = '2'
|
||||
os.environ['TORCH_CUDA_ARCH_LIST'] = '8.9'
|
||||
for port in (8188, 8189, 8190):
|
||||
with urllib.request.urlopen(f'http://192.168.10.51:{port}/queue', timeout=5) as response:
|
||||
q = json.load(response)
|
||||
assert not q['queue_running'] and not q['queue_pending'], 'ComfyUI busy: defer, never evict'
|
||||
|
||||
|
||||
def output_inventory(folder):
|
||||
return [dict(path=p.relative_to(folder).as_posix(), bytes=p.stat().st_size, sha256=digest(p))
|
||||
for p in sorted(folder.rglob('*')) if p.is_file() and p.name != 'output-hashes.json']
|
||||
|
||||
|
||||
def compare_expected(stage, row, folder):
|
||||
from comparison import compare_runs, validate_run
|
||||
case = AK / 'cases' / row['cohort'] / row['name']
|
||||
if stage == 'zero':
|
||||
reference = AK / 'gpu' / row['cohort'] / (row['name'] + '-r' + str(row['repeat']))
|
||||
return compare_runs(case, reference, folder, mode='zero')
|
||||
if row['repeat'] == 1:
|
||||
reference = EXP / 'gpu' / stage / (row['name'] + '-r0')
|
||||
return compare_runs(case, reference, folder, mode='repeat')
|
||||
return validate_run(case, folder)
|
||||
|
||||
|
||||
def verify_identity(folder, stage, row):
|
||||
assert stage in CFG['stages'] and row['repeat'] in (0, 1)
|
||||
assert folder == EXP / 'gpu' / stage / (row['name'] + '-r' + str(row['repeat']))
|
||||
case = AK / 'cases' / row['cohort'] / row['name']
|
||||
summary = read(folder / 'summary.json')
|
||||
assert summary['case'] == row['name'] and summary['complete'] and summary['pass_all']
|
||||
assert summary['input_sha256']['spec.json'] == digest(case / 'spec.json')
|
||||
assert summary['input_sha256']['requests.npz'] == digest(case / 'requests.npz')
|
||||
overlay = read(folder / 'overlay-audit.json')
|
||||
assert overlay['complete'] and overlay['overlay_name'] == stage
|
||||
assert overlay['input']['overlay_sha256'] == digest(EXP / 'overlays' / (stage + '.npz'))
|
||||
assert overlay['input']['manifest_sha256'] == digest(EXP / 'overlays' / (stage + '.json'))
|
||||
report = read(folder / 'comparison.json')
|
||||
assert report['pass_all'] and report['overlay_name'] == stage
|
||||
assert report['case_dir'] == str(case) and report['right'] == str(folder)
|
||||
if stage == 'zero':
|
||||
assert report['mode'] == 'zero'
|
||||
assert report['left'] == str(AK / 'gpu' / row['cohort'] / (row['name'] + '-r' + str(row['repeat'])))
|
||||
elif row['repeat'] == 1:
|
||||
assert report['mode'] == 'repeat' and report['left'] == str(EXP / 'gpu' / stage / (row['name'] + '-r0'))
|
||||
else:
|
||||
assert report['mode'] == 'self' and report['left'] == str(folder)
|
||||
|
||||
|
||||
def verify_completed(folder, stage=None, row=None, recompute=False):
|
||||
rows = read(folder / 'output-hashes.json')
|
||||
assert rows == output_inventory(folder), 'Completed output bytes changed: ' + str(folder)
|
||||
assert read(folder / 'comparison.json')['pass_all']
|
||||
assert read(folder / 'summary.json')['complete']
|
||||
if stage is not None:
|
||||
verify_identity(folder, stage, row)
|
||||
if recompute:
|
||||
report = compare_expected(stage, row, folder)
|
||||
assert report['pass_all'] and report == read(folder / 'comparison.json'), 'Recomputed comparison differs'
|
||||
|
||||
|
||||
def run(stage):
|
||||
assert stage in CFG['stages']
|
||||
reg = check(full=True); budget()
|
||||
assert not list((EXP / 'audit').glob('stopped-*.json')), 'Previous failure: preserve and inspect, never silently retry'
|
||||
receipt = EXP / 'audit' / (stage + '-complete.json')
|
||||
assert not receipt.exists(), 'Stage already completed'
|
||||
if stage == 'static25':
|
||||
prior = read(EXP / 'audit/zero-complete.json')
|
||||
assert prior['pass_all'] and prior['trials'] == 144
|
||||
for row in reg['planned']:
|
||||
verify_completed(EXP / 'gpu/zero' / (row['name'] + '-r' + str(row['repeat'])), 'zero', row)
|
||||
preflight()
|
||||
from adapter import run_gpu_overlay
|
||||
began = time.perf_counter()
|
||||
completed = []
|
||||
try:
|
||||
for ordinal, row in enumerate(reg['planned'], 1):
|
||||
key = row['name'] + '-r' + str(row['repeat'])
|
||||
folder = EXP / 'gpu' / stage / key
|
||||
case = AK / 'cases' / row['cohort'] / row['name']
|
||||
if folder.exists():
|
||||
verify_completed(folder, stage, row, recompute=True)
|
||||
print('VERIFIED-COMPLETED', stage, ordinal, key, flush=True)
|
||||
else:
|
||||
preflight(); check(); budget()
|
||||
print('START', stage, ordinal, key, flush=True)
|
||||
summary = run_gpu_overlay(case, folder, EXP / 'extension-build',
|
||||
EXP / 'overlays' / (stage + '.npz'), EXP / 'overlays' / (stage + '.json'))
|
||||
assert summary['pass_all'] and summary['complete']
|
||||
report = compare_expected(stage, row, folder)
|
||||
save(folder / 'comparison.json', report)
|
||||
assert report['pass_all'], 'Independent recording/equivalence gate failed: ' + key
|
||||
save(folder / 'output-hashes.json', output_inventory(folder))
|
||||
verify_completed(folder, stage, row)
|
||||
print('COMPLETE', stage, ordinal, key, 'simulation_seconds', summary['diagnostic_simulation_seconds'], flush=True)
|
||||
completed.append(dict(name=row['name'], repeat=row['repeat'],
|
||||
output_hashes_sha256=digest(folder / 'output-hashes.json'),
|
||||
comparison_sha256=digest(folder / 'comparison.json')))
|
||||
assert len(completed) == 144
|
||||
check(full=True)
|
||||
result = dict(stage=stage, pass_all=True, trials=144, primary_trials=120, technical_repeats=24,
|
||||
completed=completed, wall_seconds=time.perf_counter() - began, training=False,
|
||||
scope='All zero-delta runs exactly match sealed AK' if stage == 'zero' else
|
||||
'Full closed-loop fixed overlay, internal event/queue accounting and 24 exact technical repeats; not learning')
|
||||
save(receipt, result); budget()
|
||||
return result
|
||||
except Exception as error:
|
||||
save(EXP / 'audit' / ('stopped-' + stage + '.json'), dict(stage=stage,
|
||||
completed_before_error=completed, type=type(error).__name__, message=str(error),
|
||||
traceback=traceback.format_exc(), partial_outputs_preserved=True, no_parameter_rescue=True))
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('stage', choices=CFG['stages'])
|
||||
run(parser.parse_args().stage)
|
||||
@@ -0,0 +1,106 @@
|
||||
"""Complete paired static-overlay effects; no learned-memory inference."""
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from context import EXP, CFG, read, save, digest, bit_equal
|
||||
from run_suite import verify_completed
|
||||
|
||||
|
||||
def load(path):
|
||||
with np.load(path, allow_pickle=False) as data:
|
||||
return {k: data[k].copy() for k in data.files}
|
||||
|
||||
|
||||
def first_ticks(state, n=166700):
|
||||
out = np.full(n, np.iinfo(np.int32).max, np.int32)
|
||||
np.minimum.at(out, state['neuron_idx'], state['step'])
|
||||
out[out == np.iinfo(np.int32).max] = -1
|
||||
return out
|
||||
|
||||
|
||||
def metrics(base, changed, population):
|
||||
a, b = base['counts'][population], changed['counts'][population]
|
||||
af, bf = first_ticks(base)[population], first_ticks(changed)[population]
|
||||
ae = np.isin(base['neuron_idx'], population); be = np.isin(changed['neuron_idx'], population)
|
||||
same = bit_equal(base['step'][ae], changed['step'][be]) and bit_equal(base['neuron_idx'][ae], changed['neuron_idx'][be])
|
||||
ax = base['step'][ae].astype(np.int64) * 166700 + base['neuron_idx'][ae]
|
||||
bx = changed['step'][be].astype(np.int64) * 166700 + changed['neuron_idx'][be]
|
||||
different = np.setxor1d(ax, bx, assume_unique=True)
|
||||
return dict(cells=len(population), base_active=int(np.count_nonzero(a)), changed_active=int(np.count_nonzero(b)),
|
||||
base_spikes=int(a.sum()), changed_spikes=int(b.sum()),
|
||||
recruited_cells=int(np.count_nonzero((a == 0) & (b > 0))), lost_cells=int(np.count_nonzero((a > 0) & (b == 0))),
|
||||
count_changed_cells=int(np.count_nonzero(a != b)), first_spike_changed_cells=int(np.count_nonzero(af != bf)),
|
||||
count_l1=int(np.abs(a.astype(np.int64) - b.astype(np.int64)).sum()), event_sequence_changed=not same,
|
||||
first_event_divergence_ms=None if not len(different) else float(different.min() // 166700 * .1),
|
||||
final_v_max_abs_SI=float(np.max(np.abs(base['v'][population] - changed['v'][population]))) if len(population) else None,
|
||||
final_g_max_abs_SI=float(np.max(np.abs(base['g'][population] - changed['g'][population]))) if len(population) else None)
|
||||
|
||||
|
||||
def run(verify_only=False):
|
||||
def emit(path, value):
|
||||
if verify_only:
|
||||
assert read(path) == value, str(path)
|
||||
else:
|
||||
save(path, value)
|
||||
reg = read(EXP / 'protocol/preregistered.json')
|
||||
for stage in CFG['stages']:
|
||||
receipt = read(EXP / 'audit' / (stage + '-complete.json'))
|
||||
assert receipt['pass_all'] and receipt['trials'] == 144
|
||||
assert len(receipt['completed']) == 144
|
||||
for row, entry in zip(reg['planned'], receipt['completed']):
|
||||
assert entry['name'] == row['name'] and entry['repeat'] == row['repeat']
|
||||
folder = EXP / 'gpu' / stage / (row['name'] + '-r' + str(row['repeat']))
|
||||
verify_completed(folder, stage, row, recompute=True)
|
||||
assert digest(folder / 'output-hashes.json') == entry['output_hashes_sha256']
|
||||
assert digest(folder / 'comparison.json') == entry['comparison_sha256']
|
||||
pops = load(EXP / 'protocol/populations.npz')
|
||||
rows, cellrows, times, peaks = [], [], [], []
|
||||
for row in reg['planned']:
|
||||
if row['repeat']:
|
||||
continue
|
||||
key = row['name'] + '-r0'
|
||||
base, changed = [load(EXP / 'gpu' / stage / key / 'state.npz') for stage in CFG['stages']]
|
||||
effects = {name: metrics(base, changed, nodes) for name, nodes in pops.items()}
|
||||
result = dict(**row, effects=effects, whole_brain=metrics(base, changed, np.arange(166700, dtype=np.int32)))
|
||||
emit(EXP / 'summaries' / (row['name'] + '.json'), result); rows.append(result)
|
||||
af, bf = first_ticks(base), first_ticks(changed)
|
||||
for neuron in pops['MBON']:
|
||||
n = int(neuron)
|
||||
cellrows.append(dict(case=row['name'], cohort=row['cohort'], mode=row['mode'], seed=row['seed'],
|
||||
neuron_idx=n, base_count=int(base['counts'][n]), static25_count=int(changed['counts'][n]),
|
||||
base_first_tick=int(af[n]), static25_first_tick=int(bf[n]),
|
||||
base_final_v_SI=float(base['v'][n]), static25_final_v_SI=float(changed['v'][n]),
|
||||
base_final_g_SI=float(base['g'][n]), static25_final_g_SI=float(changed['g'][n])))
|
||||
for stage in CFG['stages']:
|
||||
s = read(EXP / 'gpu' / stage / key / 'summary.json')
|
||||
times.append(dict(stage=stage, case=row['name'], wall_seconds=s['wall_seconds'],
|
||||
simulation_seconds=s['diagnostic_simulation_seconds']))
|
||||
peaks.append(s['peak_allocated_vram_bytes'])
|
||||
table = pd.DataFrame(cellrows)
|
||||
table_path = EXP / 'summaries/per-MBON-static-effects.parquet'
|
||||
if verify_only:
|
||||
pd.testing.assert_frame_equal(table, pd.read_parquet(table_path), check_exact=True)
|
||||
else:
|
||||
assert not table_path.exists(); table.to_parquet(table_path, index=False)
|
||||
blocks = {}
|
||||
for cohort in sorted({r['cohort'] for r in rows}):
|
||||
for mode in sorted({r['mode'] for r in rows}):
|
||||
block = [r for r in rows if r['cohort'] == cohort and r['mode'] == mode]
|
||||
assert len(block) == 30
|
||||
blocks[cohort + '/' + mode] = dict(trials=30, populations={name: dict(
|
||||
trials_event_change=sum(r['effects'][name]['event_sequence_changed'] for r in block),
|
||||
trials_count_change=sum(r['effects'][name]['count_changed_cells'] > 0 for r in block),
|
||||
mean_count_l1=float(np.mean([r['effects'][name]['count_l1'] for r in block])),
|
||||
mean_recruited_cells=float(np.mean([r['effects'][name]['recruited_cells'] for r in block])),
|
||||
mean_lost_cells=float(np.mean([r['effects'][name]['lost_cells'] for r in block]))) for name in pops})
|
||||
summary = dict(status='PASS-EXACT-WHOLEBRAIN-PLASTIC-INTERFACE-NOT-LEARNING',
|
||||
primary_trials_per_stage=120, technical_repeats_per_stage=24, zero_overlay_exact=True,
|
||||
static25_repeat_exact=True, blocks=blocks, peak_allocated_vram_bytes=max(peaks),
|
||||
timing=times, per_MBON_sha256=digest(table_path), training=False, learned_candidate=False,
|
||||
conditional_AL_not_a_nonzero_arithmetic_oracle=True,
|
||||
scope='Valid interface and recording; reported static response is not an associative learning PASS')
|
||||
emit(EXP / 'audit/scientific-summary.json', summary)
|
||||
return summary
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(run()['status'], flush=True)
|
||||
@@ -0,0 +1,363 @@
|
||||
"""Pure temporary-file synthetic tests; never a brain run or old-file write.
|
||||
|
||||
Public integration tests mock only the full-case loader to use a tiny graph.
|
||||
The sealed output parser, timing/queue reconstructor, overlay bridge, source AST
|
||||
and receipt checks remain real. Production rejects these tiny cases unchanged.
|
||||
"""
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import numpy as np
|
||||
|
||||
import comparison as c
|
||||
|
||||
|
||||
def write_json(path, data):
|
||||
Path(path).write_text(json.dumps(data, sort_keys=True, allow_nan=False), encoding='utf-8')
|
||||
|
||||
|
||||
class ComparisonTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory(prefix='phase4am-comparison-synthetic-')
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.root = Path(self.temp.name).resolve()
|
||||
self.case_dir = self.root / 'case'
|
||||
self.graph_dir = self.root / 'graph'
|
||||
self.bundle_dir = self.root / 'bundle'
|
||||
for p in (self.case_dir, self.graph_dir, self.bundle_dir):
|
||||
p.mkdir()
|
||||
self.helper = c._helper(set())
|
||||
self.steps = 30
|
||||
self.graph = dict(nodes=np.arange(4, dtype=np.int32), edge_id=np.arange(6, dtype=np.int64),
|
||||
ptr=np.array([0, 2, 3, 5, 6], np.int64), src=np.array([0, 0, 1, 2, 2, 3], np.int32),
|
||||
dst=np.array([3, 2, 2, 0, 3, 1], np.int32), signed_raw=np.array([1, 0, 2, -2, 3, 1], np.int64))
|
||||
self.graph['weights'] = self.graph['signed_raw'] * (.275 * .001)
|
||||
for key, value in self.graph.items():
|
||||
np.save(self.graph_dir / (key + '.npy'), value)
|
||||
self.bundle = {'kc_globals.npy': np.r_[np.array([0, 1], np.int32), np.arange(4, 4066, dtype=np.int32)],
|
||||
'apl_globals.npy': np.array([2, 3], np.int32), 'input_matrix.npy': np.zeros((2, c.N), np.float32),
|
||||
'output_matrix.npy': np.zeros((2, c.K), np.float32)}
|
||||
for key, value in self.bundle.items():
|
||||
np.save(self.bundle_dir / key, value)
|
||||
self.requests = [(0, 0), (1, 1), (2, 0), (22, 0), (23, 3)]
|
||||
np.savez(self.case_dir / 'requests.npz', tick=np.array([x[0] for x in self.requests], np.int32),
|
||||
neuron_idx=np.array([x[1] for x in self.requests], np.int32))
|
||||
write_json(self.case_dir / 'spec.json', {'name': 'synthetic', 'steps': self.steps,
|
||||
'dt_ms': .1, 'duration_ms': 3., 'record_history': False, 'apl_delay_steps': 18})
|
||||
write_json(self.root / 'coeff.json', {'synthetic': True})
|
||||
self.case = dict(spec=json.loads((self.case_dir / 'spec.json').read_text()), graph=self.graph,
|
||||
bundle=self.bundle, token=('synthetic', str(self.root)), graph_dir=self.graph_dir, bundle_dir=self.bundle_dir,
|
||||
requested=self.requests, input_hashes={'spec.json': c.file_hash(self.case_dir / 'spec.json'),
|
||||
'requests.npz': c.file_hash(self.case_dir / 'requests.npz'), 'base_weights.npy': c.file_hash(self.graph_dir / 'weights.npy'),
|
||||
'coefficients.json': c.file_hash(self.root / 'coeff.json')})
|
||||
self.ids = np.array([0, 2], np.int64)
|
||||
self.base = np.array([.275, .55], np.float32)
|
||||
np.save(self.root / 'mask.npy', self.ids)
|
||||
np.save(self.root / 'base.npy', self.base)
|
||||
self.delta = np.zeros(2, np.float32)
|
||||
self.overlay_name = 'zero'
|
||||
self._overlay_inputs()
|
||||
|
||||
def _overlay_inputs(self):
|
||||
np.savez(self.root / 'overlay.npz', edge_id=self.ids, plastic_delta_mv=self.delta)
|
||||
write_json(self.root / 'overlay-manifest.json', dict(schema='phase4am-overlay-v1', overlay_name=self.overlay_name,
|
||||
overlay_sha256=c.file_hash(self.root / 'overlay.npz'), mask_path=str(self.root / 'mask.npy'),
|
||||
mask_sha256=c.file_hash(self.root / 'mask.npy'), base_candidate_path=str(self.root / 'base.npy'),
|
||||
base_candidate_sha256=c.file_hash(self.root / 'base.npy'),
|
||||
aj_model_dir=str(Path(c.__file__).resolve().parent.parent / 'apl-timing-lab-v1')))
|
||||
|
||||
def run_files(self, name, wrapped):
|
||||
directory = self.root / name
|
||||
directory.mkdir()
|
||||
ticks = np.array([0, 1, 18, 22, 23], np.int32)
|
||||
nodes = np.array([0, 1, 2, 0, 3], np.int32)
|
||||
state = dict(step=ticks, neuron_idx=nodes, counts=np.bincount(nodes, minlength=4).astype(np.int32),
|
||||
v=np.full(4, -.052, np.float64), g=np.zeros(4, np.float64))
|
||||
hashes, queues, requests = [], [], []
|
||||
last = np.full(4, -1000000000, np.int64)
|
||||
for tick in range(self.steps):
|
||||
fired = nodes[ticks == tick]
|
||||
for request_tick, target in self.requests:
|
||||
if request_tick == tick:
|
||||
ok = tick - last[target] >= 22
|
||||
requests.append(dict(tick=tick, neuron_idx=target, attempted=1, accepted=int(ok), rejected=int(not ok)))
|
||||
last[fired] = tick
|
||||
ref = np.maximum(22 - (tick - last), 0).astype(np.int32)
|
||||
hashes.append(dict(tick=tick, v_sha256=c.raw_hash(state['v']), g_sha256=c.raw_hash(state['g']), ref_sha256=c.raw_hash(ref)))
|
||||
sources = nodes[ticks == tick - 18] if tick >= 18 else []
|
||||
rows = np.array([eid for source in sources for eid in range(self.graph['ptr'][source], self.graph['ptr'][source + 1])], np.int64)
|
||||
accepted = (ref[self.graph['dst'][rows]] == 0).astype(np.uint8)
|
||||
queues.append(self.helper._queue_digest(tick, rows, accepted))
|
||||
state['ref'] = ref
|
||||
np.savez(directory / 'state.npz', **state)
|
||||
for filename, rows in (('tick-state-hashes.jsonl', hashes), ('queue.jsonl', queues)):
|
||||
(directory / filename).write_text(''.join(json.dumps(row) + '\n' for row in rows), encoding='utf-8')
|
||||
(directory / 'requests.csv').write_text('tick,neuron_idx,attempted,accepted,rejected\n' +
|
||||
''.join(','.join(str(row[key]) for key in c._helper(set()).REQUEST_FIELDS) + '\n' for row in requests), encoding='utf-8')
|
||||
for field, width in (('drive', 2), ('state', 2), ('inhibition', c.K), ('applied', c.K)):
|
||||
np.save(directory / ('apl-' + field + '.float32.npy'), np.zeros((self.steps, width), np.float32))
|
||||
pending = np.zeros((18, c.K), np.float32)
|
||||
np.save(directory / 'apl-pending-tail.float32.npy', pending)
|
||||
timing = dict(apl_delay_steps=18, steps=self.steps, pending_generated_count=18, generated_rows=self.steps,
|
||||
applied_generated_rows=self.steps - 18, pending_shape=[18, c.K], pending_initial_zero_prefix=0,
|
||||
pending_application_tick_start=self.steps, pending_application_tick_stop_exclusive=self.steps + 18,
|
||||
pending_generated_tick_start=self.steps - 18, pending_generated_tick_stop_exclusive=self.steps,
|
||||
pending_raw_sha256=c.raw_hash(pending), final_apl_state_float32=[0., 0.], final_apl_state_u32_bits=[0, 0],
|
||||
final_apl_state_nonzero=False, brain_tail_advanced=False, future_apl_decay_or_feedback_generated=False,
|
||||
finite_generated_sequence_only=True, closed_loop_branch_integrals_claimed_equal=False,
|
||||
biological_delay_measurement_claimed=False)
|
||||
write_json(directory / 'apl-timing-audit.json', timing)
|
||||
component = dict(ok=True, protocol='phase4ag-apl-v1', n_neurons=c.N, n_apl=2, n_kc=c.K, dt_ms=.1,
|
||||
files_sha256={key: c.file_hash(self.bundle_dir / key) for key in self.bundle},
|
||||
array_sha256={key: c.raw_hash(value) for key, value in self.bundle.items()},
|
||||
decay_f32_bits=f'{int(np.float32(np.exp(-.1 / 20.)).view(np.uint32)):08x}',
|
||||
gain_f32_bits=f'{int(np.float32(.01).view(np.uint32)):08x}',
|
||||
environment=dict(device_name='NVIDIA GeForce RTX 4090', cuda_matmul_allow_tf32=False,
|
||||
float32_matmul_precision='highest', precision_configuration_changed_by_component=False))
|
||||
write_json(directory / 'apl-component.json', component)
|
||||
(directory / 'sealed-gpu-source.diff').write_text('synthetic diff, not executed\n', encoding='utf-8')
|
||||
provenance = dict(sealed_source_sha256=c.SEALED, candidate_sha256=c.AJ_SHA,
|
||||
source_diff_sha256=c.file_hash(directory / 'sealed-gpu-source.diff'), module_globals_patched=False)
|
||||
write_json(directory / 'source-audit.json', provenance)
|
||||
_, effective_sha, apl_count = c._effective_hash(self.case, self.ids if wrapped else None, self.delta if wrapped else None)
|
||||
summary = dict(case='synthetic', complete=True, pass_all=True, steps=self.steps, completed_ticks=self.steps,
|
||||
nodes=4, edges=6, precision='float64', apl_precision='float32', state_units='SI volts', total_spikes=len(ticks),
|
||||
requested=len(requests), accepted_requests=sum(row['accepted'] for row in requests),
|
||||
rejected_requests=sum(row['rejected'] for row in requests), arriving_edges=sum(row['count'] for row in queues),
|
||||
accepted_arriving_edges=sum(row['accepted_count'] for row in queues), counts_match_sparse_events=True,
|
||||
all_finite=True, record_history=False, apl_fast_edges_zeroed=apl_count, kc_targets_present=c.K,
|
||||
effective_weights_raw_sha256=effective_sha, apl_component=component,
|
||||
apl_schedule='end, after threshold/reset and guarded arrivals, unconditional all KC',
|
||||
apl_si_conversion='applied h32 -> float64 -> multiply float64 0.001 -> separate float64 subtraction',
|
||||
apl_delay_steps=18, apl_timing=timing, source_provenance=provenance, pending_generated_count=18,
|
||||
pending_initial_zero_prefix=0, pending_tail_shape=[18, c.K], future_generated_decay_tail_included=False,
|
||||
build=dict(sources={'author_ext.cpp': c.CPP_SHA, 'author_ext.cu': c.CUDA_SHA}, arch_list='8.9',
|
||||
cuda_flags=['-O2', '--fmad=false', '--ftz=false', '--prec-div=true', '--prec-sqrt=true']),
|
||||
sealed_ae_backend_sha256=c.SEALED['ae_backend'], learning=False, input_sha256=self.case['input_hashes'],
|
||||
**{key: 1.0 for key in c.PERFORMANCE})
|
||||
write_json(directory / 'summary.json', summary)
|
||||
if wrapped:
|
||||
self._write_overlay(directory, summary)
|
||||
return directory
|
||||
|
||||
def _write_overlay(self, directory, summary):
|
||||
for source, target in (('overlay.npz', 'overlay-input.npz'), ('overlay-manifest.json', 'overlay-manifest.json')):
|
||||
shutil.copyfile(self.root / source, directory / target)
|
||||
input_rows = {}
|
||||
for label, filename in (('overlay', 'overlay.npz'), ('manifest', 'overlay-manifest.json'), ('mask', 'mask.npy'), ('base_candidate', 'base.npy')):
|
||||
input_rows[label + '_file'] = str(self.root / filename)
|
||||
input_rows[label + '_sha256'] = c.file_hash(self.root / filename)
|
||||
audit = dict(schema='phase4am-overlay-audit-v1', complete=True, overlay_name=self.overlay_name, input=input_rows,
|
||||
arrays={key: dict(dtype=str(value.dtype), shape=list(value.shape), raw_sha256=c.raw_hash(value)) for key, value in
|
||||
dict(edge_id=self.ids, plastic_delta_mv=self.delta, base_candidate_mv=self.base).items()},
|
||||
candidate_count=2, nonzero_delta_count=int(np.count_nonzero(self.delta)), mapped_candidate_count=2,
|
||||
missing_candidate_count=0, installed_nonzero_count=int(np.count_nonzero(self.delta)), delta_dtype='float32',
|
||||
delta_units='mV', state_dtype='float64', bridge=c.BRIDGE, installation_schedule=c.SCHEDULE,
|
||||
weights_updated_during_run=False, learning=False, zero_delta_no_write=True, frozen_mask_exact=True,
|
||||
bounds_pass=True, apl_overlap_count=0, apl_fast_edges_zeroed=summary['apl_fast_edges_zeroed'],
|
||||
base_graph_files_modified=False, untouched_weights_bitwise_preserved=True,
|
||||
base_weights_file_sha256=c.file_hash(self.graph_dir / 'weights.npy'), base_weights_raw_sha256=c._base_raw_hash(self.case),
|
||||
effective_weights_raw_sha256=summary['effective_weights_raw_sha256'],
|
||||
expected_effective_weights_raw_sha256=summary['effective_weights_raw_sha256'],
|
||||
copied_overlay_file='overlay-input.npz', copied_manifest_file='overlay-manifest.json',
|
||||
inherited_aj_source_provenance=summary['source_provenance'],
|
||||
inherited_source_audit_scope='AJ source provenance only; AM overlay semantics are recorded separately',
|
||||
static25_is_exact_fraction_of_float64_base_claimed=False, arrival_weight_sampling='arrival time; delta fixed for entire trial')
|
||||
write_json(directory / 'overlay-audit.json', audit)
|
||||
model = Path(c.__file__).resolve().parent
|
||||
receipt = dict(schema='phase4am-adapter-provenance-v1', complete=True,
|
||||
scope='constant sparse overlay installation only; no within-trial plasticity or learning',
|
||||
adapter_file=str(model / 'adapter.py'), adapter_sha256=c.file_hash(model / 'adapter.py'),
|
||||
overlay_module_file=str(model / 'overlay.py'), overlay_module_sha256=c.file_hash(model / 'overlay.py'),
|
||||
sealed_sources={relative: dict(path=str(model.parent / relative), sha256=sha) for relative, sha in c.ADAPTER_PINS.items()},
|
||||
ast=c._expected_ast(model.parent / 'apl-timing-lab-v1/gpu_timing.py', model / 'adapter.py'),
|
||||
installation_calls=1, weights_updated_during_run=False, learning=False, apl_delay_steps=18,
|
||||
original_module_globals_patched=False, delta_constant_for_entire_trial=True,
|
||||
inherited_source_audit_describes_AM_semantics=False, overlay_audit_sha256=c.file_hash(directory / 'overlay-audit.json'),
|
||||
inherited_summary_sha256=c.file_hash(directory / 'summary.json'), effective_weights_raw_sha256=summary['effective_weights_raw_sha256'])
|
||||
write_json(directory / 'adapter-provenance.json', receipt)
|
||||
|
||||
def compare(self, left, right, mode='zero', output=None):
|
||||
with patch.object(c, '_load_case', return_value=self.case), patch.object(c, 'CANDIDATES', 2):
|
||||
return c.compare_runs(self.case_dir, left, right, output, mode)
|
||||
|
||||
def assert_pass(self, report):
|
||||
self.assertTrue(report['pass_all'], json.dumps({key: report.get(key) for key in ('error', 'failed_checks', 'differences')}))
|
||||
self.assertFalse(report['learning_achieved'])
|
||||
self.assertFalse(report['candidate_generated'])
|
||||
self.assertTrue(report['read_manifest'])
|
||||
|
||||
def test_zero_complete_real_parsers(self):
|
||||
left, right = self.run_files('left', False), self.run_files('right', True)
|
||||
self.assert_pass(self.compare(left, right))
|
||||
|
||||
def test_static25_repeat_and_single_validation(self):
|
||||
self.overlay_name = 'static25'
|
||||
self.delta = np.multiply(self.base, np.float32(-.25))
|
||||
self._overlay_inputs()
|
||||
left, right = self.run_files('left', True), self.run_files('right', True)
|
||||
self.assert_pass(self.compare(left, right, 'repeat'))
|
||||
self.assert_pass(self.compare(left, left, 'self'))
|
||||
|
||||
def test_production_rejects_toy_dimensions(self):
|
||||
with self.assertRaises(ValueError):
|
||||
c._load_case(self.case_dir, set())
|
||||
|
||||
def test_missing_adapter_receipt_fails_despite_complete_summary(self):
|
||||
left, right = self.run_files('left', False), self.run_files('right', True)
|
||||
(right / 'adapter-provenance.json').unlink()
|
||||
self.assertFalse(self.compare(left, right)['pass_all'])
|
||||
|
||||
def test_corrupt_adapter_receipt_ast_and_summary_binding_fail(self):
|
||||
left, right = self.run_files('left', False), self.run_files('right', True)
|
||||
path = right / 'adapter-provenance.json'
|
||||
original = json.loads(path.read_text())
|
||||
for change in ('ast', 'binding', 'complete'):
|
||||
data = copy.deepcopy(original)
|
||||
if change == 'ast':
|
||||
data['ast']['entire_tick_loop_ast_identical'] = False
|
||||
elif change == 'binding':
|
||||
data['inherited_summary_sha256'] = '0' * 64
|
||||
else:
|
||||
data['complete'] = False
|
||||
write_json(path, data)
|
||||
self.assertFalse(self.compare(left, right)['pass_all'], change)
|
||||
|
||||
def test_per_neuron_count_corruption_not_hidden_by_equal_total(self):
|
||||
left, right = self.run_files('left', False), self.run_files('right', True)
|
||||
for folder in (left, right):
|
||||
with np.load(folder / 'state.npz') as f:
|
||||
state = {key: f[key].copy() for key in f.files}
|
||||
state['counts'][0] -= 1
|
||||
state['counts'][1] += 1
|
||||
np.savez(folder / 'state.npz', **state)
|
||||
self.assertFalse(self.compare(left, right)['pass_all'])
|
||||
|
||||
def test_both_equal_wrong_queues_fail_independent_reconstruction(self):
|
||||
left, right = self.run_files('left', False), self.run_files('right', True)
|
||||
for folder in (left, right):
|
||||
path = folder / 'queue.jsonl'
|
||||
rows = [json.loads(row) for row in path.read_text().splitlines()]
|
||||
rows[18]['ordered_edge_sha256'] = '0' * 64
|
||||
path.write_text(''.join(json.dumps(row) + '\n' for row in rows), encoding='utf-8')
|
||||
report = self.compare(left, right)
|
||||
self.assertFalse(report['pass_all'])
|
||||
self.assertIn('left_gpu.full_event_queue_request_accounting', report['failed_checks'])
|
||||
|
||||
def test_both_equal_wrong_requests_fail(self):
|
||||
left, right = self.run_files('left', False), self.run_files('right', True)
|
||||
for folder in (left, right):
|
||||
path = folder / 'requests.csv'
|
||||
path.write_text(path.read_text().replace('2,0,1,0,1', '2,0,1,1,0'), encoding='utf-8')
|
||||
self.assertFalse(self.compare(left, right)['pass_all'])
|
||||
|
||||
def test_both_equal_wrong_ref_hash_fails(self):
|
||||
left, right = self.run_files('left', False), self.run_files('right', True)
|
||||
for folder in (left, right):
|
||||
path = folder / 'tick-state-hashes.jsonl'
|
||||
rows = [json.loads(row) for row in path.read_text().splitlines()]
|
||||
rows[1]['ref_sha256'] = '0' * 64
|
||||
path.write_text(''.join(json.dumps(row) + '\n' for row in rows), encoding='utf-8')
|
||||
self.assertFalse(self.compare(left, right)['pass_all'])
|
||||
|
||||
def test_state_signed_zero_detected(self):
|
||||
left, right = self.run_files('left', False), self.run_files('right', True)
|
||||
with np.load(right / 'state.npz') as f:
|
||||
state = {key: f[key].copy() for key in f.files}
|
||||
state['g'][0] = -0.
|
||||
np.savez(right / 'state.npz', **state)
|
||||
self.assertFalse(self.compare(left, right)['pass_all'])
|
||||
|
||||
def test_same_nonfinite_states_fail(self):
|
||||
left, right = self.run_files('left', False), self.run_files('right', True)
|
||||
for folder in (left, right):
|
||||
with np.load(folder / 'state.npz') as f:
|
||||
state = {key: f[key].copy() for key in f.files}
|
||||
state['v'][0] = np.inf
|
||||
np.savez(folder / 'state.npz', **state)
|
||||
self.assertFalse(self.compare(left, right)['pass_all'])
|
||||
|
||||
def test_apl_pending_signed_zero_fails(self):
|
||||
left, right = self.run_files('left', False), self.run_files('right', True)
|
||||
path = right / 'apl-pending-tail.float32.npy'
|
||||
value = np.load(path)
|
||||
value[0, 0] = np.float32(-0.)
|
||||
np.save(path, value)
|
||||
self.assertFalse(self.compare(left, right)['pass_all'])
|
||||
|
||||
def test_missing_tick_or_extra_request_fails(self):
|
||||
left, right = self.run_files('left', False), self.run_files('right', True)
|
||||
path = right / 'queue.jsonl'
|
||||
path.write_text('\n'.join(path.read_text().splitlines()[:-1]) + '\n', encoding='utf-8')
|
||||
self.assertFalse(self.compare(left, right)['pass_all'])
|
||||
|
||||
def test_summary_only_whitelisted_performance_may_differ(self):
|
||||
a = dict(dynamics='same', build={'source': 'same'}, wall_seconds=1)
|
||||
b = dict(a, wall_seconds=5)
|
||||
checks, differences = {}, {}
|
||||
c._summary_pair(a, b, checks, differences)
|
||||
self.assertTrue(all(checks.values()))
|
||||
b['build'] = {'source': 'different'}
|
||||
c._summary_pair(a, b, checks, differences)
|
||||
self.assertFalse(all(checks.values()))
|
||||
|
||||
def test_overlay_bad_units_and_fake_complete_receipt_fail(self):
|
||||
left, right = self.run_files('left', False), self.run_files('right', True)
|
||||
path = right / 'overlay-audit.json'
|
||||
data = json.loads(path.read_text()); data['delta_units'] = 'V'; write_json(path, data)
|
||||
self.assertFalse(self.compare(left, right)['pass_all'])
|
||||
|
||||
def test_source_or_build_mutation_fails(self):
|
||||
left, right = self.run_files('left', False), self.run_files('right', True)
|
||||
path = right / 'summary.json'
|
||||
data = json.loads(path.read_text()); data['build']['cuda_flags'].append('--use_fast_math'); write_json(path, data)
|
||||
self.assertFalse(self.compare(left, right)['pass_all'])
|
||||
|
||||
def test_no_existing_output_overwrite_and_no_input_directory_write(self):
|
||||
left, right = self.run_files('left', False), self.run_files('right', True)
|
||||
output = self.root / 'report.json'
|
||||
self.assert_pass(self.compare(left, right, output=output))
|
||||
with self.assertRaises(ValueError):
|
||||
self.compare(left, right, output=output)
|
||||
with self.assertRaises(ValueError):
|
||||
self.compare(left, right, output=right / 'report.json')
|
||||
|
||||
def test_fake_repeat_self_disallowed(self):
|
||||
left = self.run_files('left', True)
|
||||
self.assertFalse(self.compare(left, left, 'repeat')['pass_all'])
|
||||
|
||||
def test_static25_cannot_be_zero_reference(self):
|
||||
self.overlay_name = 'static25'; self.delta = np.multiply(self.base, np.float32(-.25)); self._overlay_inputs()
|
||||
left, right = self.run_files('left', False), self.run_files('right', True)
|
||||
self.assertFalse(self.compare(left, right)['pass_all'])
|
||||
|
||||
def test_helper_source_pin_and_adapter_ast_contract(self):
|
||||
model = Path(c.__file__).resolve().parent
|
||||
self.assertEqual(c.file_hash(model.parent / 'apl-timing-lab-v1/compare_timing.py'), c.HELPER_SHA)
|
||||
import adapter
|
||||
_, owner = adapter.derive_ast((model.parent / 'apl-timing-lab-v1/gpu_timing.py').read_bytes())
|
||||
independent = c._expected_ast(model.parent / 'apl-timing-lab-v1/gpu_timing.py', model / 'adapter.py')
|
||||
for key, value in owner.items():
|
||||
self.assertEqual(independent[key], value)
|
||||
|
||||
def test_fresh_sha_detects_same_size_write_even_with_identical_stat(self):
|
||||
path = self.root / 'same-stat.bin'
|
||||
path.write_bytes(b'first!')
|
||||
identity = c._fingerprint(path)
|
||||
with patch.object(c, '_fingerprint', return_value=identity):
|
||||
before = c.file_hash(path)
|
||||
path.write_bytes(b'other!')
|
||||
after = c.file_hash(path)
|
||||
self.assertEqual(before, hashlib.sha256(b'first!').hexdigest())
|
||||
self.assertEqual(after, hashlib.sha256(b'other!').hexdigest())
|
||||
self.assertNotEqual(before, after)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,259 @@
|
||||
"""Synthetic eligibility tests only; never load recorded brain trajectories."""
|
||||
import ast
|
||||
import importlib.util
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
import tempfile
|
||||
import unittest
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
import eligibility_bridge as bridge
|
||||
|
||||
|
||||
def source_path():
|
||||
explicit = os.environ.get("PHASE4J_SOURCE")
|
||||
if explicit:
|
||||
return Path(explicit)
|
||||
parent = Path(__file__).resolve().parents[1]
|
||||
paths = (parent / "models/male-cns-lif/phase4j.py", parent / "mb-physiology-v1/phase4j.py")
|
||||
for path in paths:
|
||||
if path.is_file():
|
||||
return path
|
||||
raise FileNotFoundError("Set PHASE4J_SOURCE to the SHA-pinned original phase4j.py")
|
||||
|
||||
|
||||
SOURCE = source_path()
|
||||
KC = np.array([1, 3, 7], np.int32)
|
||||
|
||||
|
||||
def run_trace(rows=(), steps=8, commands=(0, 3, 7), backend="numpy"):
|
||||
events = np.asarray(rows, np.int32).reshape(-1, 2)
|
||||
return bridge.trace_from_events(events[:, 0], events[:, 1], KC, steps, commands,
|
||||
phase4j_source=SOURCE, backend=backend, n_neurons=10)
|
||||
|
||||
|
||||
def update_args():
|
||||
return dict(trace=np.array([1, .25, 0], np.float32), kc=KC.copy(),
|
||||
candidate_sources=np.array([1, 1, 3, 7, 3], np.int32),
|
||||
candidate_compartments=np.array(["y5B'2a", "a1", "y5B'2a", "y5B'2a", "y2a'1"]),
|
||||
compartment="y5B'2a", delta=np.zeros(5, np.float32),
|
||||
base_candidate=np.array([.275, .55, .0001, .275, 2.75], np.float32),
|
||||
d=1., phase4j_source=SOURCE, n_neurons=10)
|
||||
|
||||
|
||||
class FrozenTraceTests(unittest.TestCase):
|
||||
def test_no_input_positive_zero_and_no_command(self):
|
||||
result = run_trace(commands=())
|
||||
self.assertEqual(result.snapshots.shape, (0, 3))
|
||||
self.assertTrue(bridge.bit_equal(result.final, np.zeros(3, np.float32)))
|
||||
self.assertTrue(bridge.bit_equal(result.last_command_trace, result.final))
|
||||
self.assertFalse(result.audit["training_executed"])
|
||||
self.assertFalse(result.audit["torch_numpy_bitwise_comparison_performed"])
|
||||
json.dumps(result.audit, allow_nan=False)
|
||||
|
||||
def test_simultaneous_spike_is_seen_and_cap_is_one(self):
|
||||
result = run_trace([(0, 1), (1, 1), (3, 3), (3, 7)])
|
||||
self.assertEqual(result.snapshots[0, 0], np.float32(1))
|
||||
self.assertEqual(result.snapshots[1, 1:].tolist(), [1., 1.])
|
||||
self.assertLessEqual(float(result.final.max()), 1.)
|
||||
|
||||
def test_interval_decay_is_each_tick_not_power_jump(self):
|
||||
result = run_trace([(0, 1)], steps=1001, commands=(0, 1, 1000))
|
||||
expected = np.float32(1)
|
||||
decay = np.float32(math.exp(-.1 / 100.))
|
||||
for _ in range(1000):
|
||||
expected = np.float32(expected * decay)
|
||||
self.assertEqual(result.snapshots[-1, 0].view(np.uint32), expected.view(np.uint32))
|
||||
self.assertEqual(result.snapshots[1, 0].view(np.uint32), decay.view(np.uint32))
|
||||
|
||||
def test_us_before_future_cs_has_zero_delta(self):
|
||||
result = run_trace([(2, 1)], commands=(1,))
|
||||
args = update_args()
|
||||
args["trace"] = result.last_command_trace
|
||||
delta, _ = bridge.apply_eligibility(**args)
|
||||
self.assertTrue(bridge.bit_equal(delta, args["delta"]))
|
||||
|
||||
def test_plus_1000_ms_ratio_strict_below_point01(self):
|
||||
result = run_trace([(0, 1)], steps=10001, commands=(0, 10000))
|
||||
args = update_args()
|
||||
args["base_candidate"][:] = 100 # no saturation in this temporal check
|
||||
args["trace"] = result.snapshots[0]
|
||||
zero, _ = bridge.apply_eligibility(**args)
|
||||
args["trace"] = result.snapshots[1]
|
||||
late, _ = bridge.apply_eligibility(**args)
|
||||
self.assertLess(float(np.abs(late).sum() / np.abs(zero).sum()), .01)
|
||||
self.assertGreater(float(result.snapshots[-1, 0]), 0.)
|
||||
|
||||
def test_non_kc_events_do_not_enter_trace(self):
|
||||
a = run_trace([(0, 1), (3, 3)])
|
||||
b = run_trace([(0, 0), (0, 1), (2, 9), (3, 3)])
|
||||
self.assertTrue(bridge.bit_equal(a.snapshots, b.snapshots))
|
||||
self.assertEqual(b.audit["actual_kc_events"], 2)
|
||||
|
||||
def test_inputs_and_outputs_are_not_mutated(self):
|
||||
step, node, kc = np.array([0, 7], np.int32), np.array([1, 3], np.int32), KC.copy()
|
||||
before = [a.copy() for a in (step, node, kc)]
|
||||
result = bridge.trace_from_events(step, node, kc, 8, [7], phase4j_source=SOURCE, n_neurons=10)
|
||||
for a, b in zip((step, node, kc), before):
|
||||
self.assertTrue(bridge.bit_equal(a, b))
|
||||
for a in (result.final, result.snapshots, result.command_steps, result.last_command_trace):
|
||||
self.assertFalse(a.flags.writeable)
|
||||
|
||||
def test_bad_event_and_command_schema_is_rejected(self):
|
||||
for rows in ([(0, 1), (0, 1)], [(1, 1), (0, 1)], [(-1, 1)], [(8, 1)], [(0, 10)]):
|
||||
with self.subTest(rows=rows), self.assertRaises(ValueError):
|
||||
run_trace(rows)
|
||||
for commands in ((1, 1), (3, 1), (-1,), (8,), (.5,), (True,), np.empty((0, 2), np.int32)):
|
||||
with self.subTest(commands=commands), self.assertRaises(ValueError):
|
||||
run_trace(commands=commands)
|
||||
with self.assertRaises(ValueError):
|
||||
bridge.trace_from_events(np.array([], np.int64), np.array([], np.int32), KC, 8, [], phase4j_source=SOURCE)
|
||||
|
||||
def test_bad_kc_and_backend_are_rejected(self):
|
||||
for kc in (np.array([3, 1], np.int32), np.array([1, 1], np.int32), np.array([], np.int32)):
|
||||
with self.assertRaises(ValueError):
|
||||
bridge.trace_from_events(np.array([], np.int32), np.array([], np.int32), kc, 1, [], phase4j_source=SOURCE)
|
||||
with self.assertRaises(ValueError):
|
||||
run_trace(backend="auto")
|
||||
|
||||
@unittest.skipUnless(importlib.util.find_spec("torch") is not None, "Torch absent: CPU bitwise compatibility NOT established locally")
|
||||
def test_torch_original_expression_bitwise_against_numpy(self):
|
||||
rng = np.random.default_rng(719)
|
||||
mask = rng.random((1001, 10)) < .07
|
||||
tick, node = np.nonzero(mask)
|
||||
rows = np.stack((tick, node), axis=1).tolist()
|
||||
numpy_result = run_trace(rows, steps=1001, commands=(0, 1, 7, 999, 1000))
|
||||
torch_result = run_trace(rows, steps=1001, commands=(0, 1, 7, 999, 1000), backend="torch")
|
||||
self.assertTrue(bridge.bit_equal(numpy_result.snapshots, torch_result.snapshots))
|
||||
self.assertTrue(bridge.bit_equal(numpy_result.final, torch_result.final))
|
||||
empty_np = run_trace([(0, 1)], steps=10001, commands=(0, 10000))
|
||||
empty_torch = run_trace([(0, 1)], steps=10001, commands=(0, 10000), backend="torch")
|
||||
self.assertTrue(bridge.bit_equal(empty_np.snapshots, empty_torch.snapshots))
|
||||
|
||||
|
||||
class FrozenUpdateTests(unittest.TestCase):
|
||||
def test_actual_source_sha_and_ast_only_extraction(self):
|
||||
_, report = bridge.apply_eligibility(**update_args())
|
||||
self.assertEqual(report["source_sha256"], bridge.PHASE4J_SHA256)
|
||||
self.assertFalse(report["actual_dan_activity_used_to_gate"])
|
||||
self.assertFalse(report["learning_pass_claimed"])
|
||||
json.dumps(report, allow_nan=False)
|
||||
|
||||
def test_source_drift_is_rejected_before_execution(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = Path(directory) / "phase4j.py"
|
||||
path.write_bytes(SOURCE.read_bytes() + b"\nraise RuntimeError('must not execute')\n")
|
||||
args = update_args()
|
||||
args["phase4j_source"] = path
|
||||
with self.assertRaisesRegex(ValueError, "SHA256 mismatch"):
|
||||
bridge.apply_eligibility(**args)
|
||||
|
||||
def test_bitwise_against_independently_extracted_original_apply_v2(self):
|
||||
tree = ast.parse(SOURCE.read_text(encoding="utf-8"))
|
||||
function = next(x for x in tree.body if isinstance(x, ast.FunctionDef) and x.name == "apply_v2")
|
||||
namespace = {"np": np, "Validator": object, "Any": Any}
|
||||
exec(compile(ast.Module(body=[function], type_ignores=[]), "independent-old-apply-v2", "exec"), namespace)
|
||||
|
||||
class Column:
|
||||
def __init__(self, values):
|
||||
self.values = values
|
||||
|
||||
def to_numpy(self, dtype=None):
|
||||
return np.array(self.values, dtype=dtype)
|
||||
|
||||
def astype(self, dtype):
|
||||
return Column(np.asarray(self.values).astype(dtype))
|
||||
|
||||
args = update_args()
|
||||
validator = SimpleNamespace(n=10, kc=KC, base_candidate=args["base_candidate"],
|
||||
cand={"source_neuron_idx": Column(args["candidate_sources"]),
|
||||
"target_compartment_verbatim": Column(args["candidate_compartments"])})
|
||||
cfg = {"eligibility_v2": {"eta_effective_mv": .00275, "max_fractional_depression": .25}}
|
||||
for _ in range(50):
|
||||
actual, report = bridge.apply_eligibility(**args)
|
||||
expected, old_report = namespace["apply_v2"](validator, args["trace"], args["compartment"], 1., args["delta"], cfg)
|
||||
self.assertTrue(bridge.bit_equal(actual, expected))
|
||||
self.assertEqual(report["original_report"], old_report)
|
||||
args["delta"] = actual
|
||||
|
||||
def test_zero_d_and_frozen_preserve_existing_overlay(self):
|
||||
args = update_args()
|
||||
args["delta"][:] = -.25 * args["base_candidate"]
|
||||
for d, frozen in ((0., False), (1., True), (0., True)):
|
||||
args.update(d=d, frozen=frozen)
|
||||
result, report = bridge.apply_eligibility(**args)
|
||||
self.assertTrue(bridge.bit_equal(result, args["delta"]))
|
||||
self.assertEqual(report["actual_changed_edges"], 0)
|
||||
|
||||
def test_off_compartment_and_inactive_source_exact_unchanged(self):
|
||||
args = update_args()
|
||||
args["delta"][1] = -.01
|
||||
result, _ = bridge.apply_eligibility(**args)
|
||||
self.assertTrue(bridge.bit_equal(result[[1, 3, 4]], args["delta"][[1, 3, 4]]))
|
||||
self.assertEqual(result[0], np.float32(-.00275))
|
||||
|
||||
def test_original_floor_clamps_and_does_not_reverse_sign(self):
|
||||
args = update_args()
|
||||
result, report = bridge.apply_eligibility(**args)
|
||||
self.assertEqual(result[2], (-.25 * args["base_candidate"])[2])
|
||||
args["delta"] = result
|
||||
again, _ = bridge.apply_eligibility(**args)
|
||||
self.assertEqual(again[2].view(np.uint32), result[2].view(np.uint32))
|
||||
self.assertTrue(np.all(again <= 0))
|
||||
# Original updated_edges counts attempted updates, including saturation.
|
||||
self.assertEqual(report["original_report"]["updated_edges"], 2)
|
||||
|
||||
def test_all_original_compartments_are_literal_not_inferred(self):
|
||||
args = update_args()
|
||||
args["candidate_sources"] = np.full(4, 1, np.int32)
|
||||
args["candidate_compartments"] = np.array(bridge.COMPARTMENTS)
|
||||
args["delta"] = np.zeros(4, np.float32)
|
||||
args["base_candidate"] = np.ones(4, np.float32)
|
||||
for comp in bridge.COMPARTMENTS:
|
||||
args["compartment"] = comp
|
||||
result, _ = bridge.apply_eligibility(**args)
|
||||
self.assertEqual(int(np.count_nonzero(result)), 1)
|
||||
self.assertNotEqual(result[list(bridge.COMPARTMENTS).index(comp)], 0)
|
||||
|
||||
def test_update_does_not_mutate_inputs_and_returns_readonly(self):
|
||||
args = update_args()
|
||||
before = {k: v.copy() for k, v in args.items() if isinstance(v, np.ndarray)}
|
||||
result, _ = bridge.apply_eligibility(**args)
|
||||
for k, value in before.items():
|
||||
self.assertTrue(bridge.bit_equal(args[k], value))
|
||||
self.assertFalse(result.flags.writeable)
|
||||
|
||||
def test_invalid_units_dtypes_values_and_compartment_fail_closed(self):
|
||||
bad = [("trace", np.array([1, .25, 0], np.float64)),
|
||||
("trace", np.array([1, np.nan, 0], np.float32)),
|
||||
("trace", np.array([1.1, 0, 0], np.float32)),
|
||||
("candidate_sources", np.array([1, 1, 3, 8, 3], np.int32)),
|
||||
("compartment", "PAM15"), ("d", -.1), ("d", .5), ("d", float("nan")),
|
||||
("d", True), ("frozen", 1),
|
||||
("base_candidate", np.zeros(5, np.float32)),
|
||||
("delta", np.ones(5, np.float32)),
|
||||
("delta", np.full(5, -.5, np.float32)),
|
||||
("delta", np.full(5, -0., np.float32))]
|
||||
for key, value in bad:
|
||||
with self.subTest(key=key, value=value):
|
||||
args = update_args()
|
||||
args[key] = value
|
||||
with self.assertRaises(ValueError):
|
||||
bridge.apply_eligibility(**args)
|
||||
|
||||
def test_empty_candidate_subset_is_safe_but_not_claimed_full_mask(self):
|
||||
args = update_args()
|
||||
for key in ("candidate_sources", "candidate_compartments", "delta", "base_candidate"):
|
||||
args[key] = args[key][:0]
|
||||
result, report = bridge.apply_eligibility(**args)
|
||||
self.assertEqual(result.shape, (0,))
|
||||
self.assertFalse(report["complete_mask_identity_verified_by_this_function"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,275 @@
|
||||
"""Pure synthetic inputs/AST tests; no Torch import, GPU, or brain execution."""
|
||||
import ast
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import types
|
||||
import unittest
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
|
||||
import adapter
|
||||
import overlay
|
||||
|
||||
|
||||
HERE = Path(__file__).absolute().parent
|
||||
AJ = HERE.parent / 'apl-timing-lab-v1/gpu_timing.py'
|
||||
|
||||
|
||||
class ArrayTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.ids = np.array([2, 7, 15], np.int64)
|
||||
self.base32 = np.array([.275, 1.1, 2.75], np.float32)
|
||||
|
||||
def plan(self, delta=None):
|
||||
if delta is None:
|
||||
delta = np.zeros(3, np.float32)
|
||||
return overlay.validate_arrays(self.ids, delta, self.ids, self.base32)
|
||||
|
||||
def test_zero_preserves_unrelated_signed_zero_and_original_bytes(self):
|
||||
ids = np.array([0, 2, 7, 15, 17], np.int64)
|
||||
base = np.array([-0., .000275, .0011, .00275, -1.], np.float64)
|
||||
original = base.tobytes()
|
||||
result, mapping = overlay.materialize_numpy(base, ids, np.zeros(5, bool), self.plan())
|
||||
self.assertEqual(result.tobytes(), original)
|
||||
self.assertEqual(base.tobytes(), original)
|
||||
self.assertEqual(mapping['installed_nonzero_count'], 0)
|
||||
self.assertEqual(len(mapping['rows']), 0)
|
||||
self.assertFalse(np.shares_memory(result, base))
|
||||
|
||||
def test_negative_zero_delta_does_not_write(self):
|
||||
plan = self.plan(np.full(3, -0., np.float32))
|
||||
result, mapping = overlay.materialize_numpy(self.base32.astype(np.float64) * .001,
|
||||
self.ids, np.zeros(3, bool), plan)
|
||||
self.assertEqual(len(mapping['rows']), 0)
|
||||
self.assertTrue(np.signbit(plan['plastic_delta_mv']).all())
|
||||
|
||||
def test_original_float32_floor_then_widen_and_multiply(self):
|
||||
delta = np.float32(-.25) * self.base32
|
||||
plan = self.plan(delta)
|
||||
base_si = np.array([.000275, .0011, .00275], np.float64)
|
||||
result, mapping = overlay.materialize_numpy(base_si, self.ids, np.zeros(3, bool), plan)
|
||||
reference = np.add(base_si, np.multiply(delta.astype(np.float64), np.float64(.001)))
|
||||
self.assertTrue(overlay.bit_equal(result, reference))
|
||||
self.assertTrue(np.any(result.view(np.uint64) != (base_si * .75).view(np.uint64)))
|
||||
self.assertEqual(mapping['installed_nonzero_count'], 3)
|
||||
|
||||
def test_global_id_mapping_fixture_intersection_and_missing_nonzero_reject(self):
|
||||
plan = self.plan(np.array([0., -.1, 0.], np.float32))
|
||||
ids = np.array([1, 7, 20], np.int64)
|
||||
result, mapping = overlay.materialize_numpy(np.array([1., .002, -0.], np.float64), ids,
|
||||
np.array([True, False, False]), plan)
|
||||
np.testing.assert_array_equal(mapping['rows'], np.array([1], np.int64))
|
||||
self.assertEqual(mapping['missing_candidate_count'], 2)
|
||||
self.assertEqual(result[0].view(np.uint64), 0)
|
||||
self.assertTrue(np.signbit(result[2]))
|
||||
with self.assertRaises(ValueError):
|
||||
overlay.materialize_numpy(np.array([1.], np.float64), np.array([1], np.int64), np.zeros(1, bool), plan)
|
||||
|
||||
def test_apl_overlap_sign_nonfinite_bad_mapping_fail_closed(self):
|
||||
base = self.base32.astype(np.float64) * .001
|
||||
for ids, values, apl in ((self.ids, base, np.array([1, 0, 0], bool)),
|
||||
(self.ids[::-1], base, np.zeros(3, bool)),
|
||||
(self.ids, -base, np.zeros(3, bool)),
|
||||
(self.ids, np.array([np.nan, 1., 2.]), np.zeros(3, bool))):
|
||||
with self.subTest(ids=ids), self.assertRaises(ValueError):
|
||||
overlay.map_installation(values, ids, apl, self.plan())
|
||||
|
||||
def test_validation_bad_dtype_shape_sign_finite_bound_and_mask(self):
|
||||
bad_deltas = [np.zeros(3, np.float64), np.zeros((3, 1), np.float32), np.zeros(2, np.float32),
|
||||
np.array([.1, 0, 0], np.float32), np.array([np.nan, 0, 0], np.float32),
|
||||
np.array([-np.inf, 0, 0], np.float32), np.array([-1., 0, 0], np.float32)]
|
||||
for delta in bad_deltas:
|
||||
with self.subTest(delta=delta), self.assertRaises(ValueError):
|
||||
self.plan(delta)
|
||||
for ids in (self.ids.astype(np.int32), self.ids[::-1], np.array([2, 2, 15], np.int64),
|
||||
np.array([-1, 7, 15], np.int64), np.array([2, 7, overlay.EDGE_COUNT], np.int64)):
|
||||
with self.subTest(ids=ids), self.assertRaises(ValueError):
|
||||
overlay.validate_arrays(ids, np.zeros(3, np.float32), self.ids, self.base32)
|
||||
with self.assertRaises(ValueError):
|
||||
overlay.validate_arrays(self.ids, np.array([-.01, 0, 0], np.float32), self.ids)
|
||||
|
||||
def test_positive_original_base_and_bound_bits_readonly(self):
|
||||
for base in (np.zeros(3, np.float32), -self.base32, self.base32.astype(np.float64),
|
||||
np.array([np.inf, 1., 1.], np.float32)):
|
||||
with self.assertRaises(ValueError):
|
||||
overlay.validate_arrays(self.ids, np.zeros(3, np.float32), self.ids, base)
|
||||
self.ids.flags.writeable = False
|
||||
self.base32.flags.writeable = False
|
||||
plan = self.plan()
|
||||
self.assertFalse(plan['edge_id'].flags.writeable)
|
||||
self.assertFalse(plan['plastic_delta_mv'].flags.writeable)
|
||||
self.assertFalse(np.shares_memory(plan['base_candidate_mv'], self.base32))
|
||||
lower = np.float32(-.25) * self.base32
|
||||
too_low = lower.copy(); too_low[0] = np.nextafter(lower[0], np.float32(-np.inf))
|
||||
with self.assertRaises(ValueError):
|
||||
self.plan(too_low)
|
||||
|
||||
def test_chunked_expected_hash_matches_materialization(self):
|
||||
ids = np.array([0, 2, 7, 15, 20], np.int64)
|
||||
base = np.array([-0., .001, .002, .003, -1.], np.float64)
|
||||
apl = np.array([False, False, False, False, True])
|
||||
plan = self.plan(np.array([-.01, 0, -.2], np.float32))
|
||||
result, mapping = overlay.materialize_numpy(base, ids, apl, plan)
|
||||
for chunk in (1, 2, 3, 100):
|
||||
self.assertEqual(overlay.expected_hashes(base, apl, mapping, chunk),
|
||||
(overlay.raw_hash(base), overlay.raw_hash(result)))
|
||||
|
||||
|
||||
class FileTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.root = Path(self.temp.name)
|
||||
self.ids = np.arange(overlay.CANDIDATE_COUNT, dtype=np.int64)
|
||||
self.base = np.full(overlay.CANDIDATE_COUNT, .275, np.float32)
|
||||
np.save(self.root / 'mask.npy', self.ids)
|
||||
np.save(self.root / 'base.npy', self.base)
|
||||
|
||||
def files(self, name='zero', embedded=False, base=True):
|
||||
delta = np.zeros_like(self.base) if name == 'zero' else np.float32(-.25) * self.base
|
||||
values = dict(edge_id=self.ids, plastic_delta_mv=delta)
|
||||
if embedded:
|
||||
values['base_candidate_mv'] = self.base
|
||||
np.savez(self.root / 'overlay.npz', **values)
|
||||
manifest = dict(schema=overlay.SCHEMA, overlay_name=name,
|
||||
overlay_sha256=overlay.file_hash(self.root / 'overlay.npz'),
|
||||
mask_path=str(self.root / 'mask.npy'), mask_sha256=overlay.file_hash(self.root / 'mask.npy'),
|
||||
base_candidate_path=str(self.root / 'base.npy') if base else None,
|
||||
base_candidate_sha256=overlay.file_hash(self.root / 'base.npy') if base else None,
|
||||
aj_model_dir=str(self.root))
|
||||
(self.root / 'manifest.json').write_text(json.dumps(manifest), encoding='utf-8')
|
||||
return self.root / 'overlay.npz', self.root / 'manifest.json'
|
||||
|
||||
def test_full_mask_zero_optional_base_and_static_floor(self):
|
||||
for name, embedded, base in (('zero', False, False), ('zero', True, True), ('static25', True, True)):
|
||||
plan = overlay.load_overlay(*self.files(name, embedded, base))
|
||||
self.assertEqual(len(plan['edge_id']), overlay.CANDIDATE_COUNT)
|
||||
overlay.verify_inputs(plan)
|
||||
|
||||
def test_hash_tamper_and_wrong_count_reject(self):
|
||||
paths = self.files()
|
||||
(self.root / 'base.npy').write_bytes(b'not the anchored source')
|
||||
with self.assertRaises(ValueError):
|
||||
overlay.load_overlay(*paths)
|
||||
np.save(self.root / 'base.npy', self.base)
|
||||
np.save(self.root / 'mask.npy', self.ids[:-1])
|
||||
paths = self.files()
|
||||
with self.assertRaises(ValueError):
|
||||
overlay.load_overlay(*paths)
|
||||
|
||||
def test_static25_needs_original_base_and_postload_tampering_is_detected(self):
|
||||
with self.assertRaises(ValueError):
|
||||
overlay.load_overlay(*self.files('static25', base=False))
|
||||
paths = self.files()
|
||||
plan = overlay.load_overlay(*paths)
|
||||
(self.root / 'manifest.json').write_text('{}', encoding='utf-8')
|
||||
with self.assertRaises(ValueError):
|
||||
overlay.verify_inputs(plan)
|
||||
|
||||
|
||||
class AstTests(unittest.TestCase):
|
||||
def source(self):
|
||||
if not AJ.is_file():
|
||||
self.skipTest('Pinned AJ static source is not installed at its declared sibling path')
|
||||
return AJ.read_bytes()
|
||||
|
||||
def test_exact_whole_tick_loop_and_only_one_preloop_insertion(self):
|
||||
source = self.source()
|
||||
transformed, audit = adapter.derive_ast(source)
|
||||
original = next(node for node in ast.parse(source).body if isinstance(node, ast.FunctionDef) and node.name == 'run_gpu')
|
||||
getloop = lambda function: next(node for node in ast.walk(function) if isinstance(node, ast.For) and
|
||||
isinstance(node.target, ast.Name) and node.target.id == 'tick')
|
||||
self.assertEqual(ast.dump(getloop(original), include_attributes=False), ast.dump(getloop(transformed), include_attributes=False))
|
||||
hooks = [node for node in ast.walk(transformed) if isinstance(node, ast.Call) and
|
||||
isinstance(node.func, ast.Name) and node.func.id == adapter.HOOK_NAME]
|
||||
self.assertEqual(len(hooks), 1)
|
||||
self.assertFalse(any(node is hooks[0] for node in ast.walk(getloop(transformed))))
|
||||
self.assertTrue(audit['entire_tick_loop_ast_identical'])
|
||||
self.assertEqual(audit['inserted_ast_statements'], 1)
|
||||
|
||||
def test_mutated_source_fails_even_if_loop_looks_plausible(self):
|
||||
with self.assertRaises(ValueError):
|
||||
adapter.derive_ast(self.source() + b'\n# unregistered change\n')
|
||||
|
||||
def test_compilation_new_namespace_no_fake_file_no_old_global_patch(self):
|
||||
self.source()
|
||||
# Compile only; the function is never called, so no Torch or GPU needed.
|
||||
sealed = types.ModuleType('synthetic_sealed_namespace')
|
||||
sealed.__file__ = str(AJ)
|
||||
sealed.marker = object()
|
||||
before = dict(sealed.__dict__)
|
||||
hook = lambda *args: None
|
||||
function, audit = adapter.compile_derived(sealed, hook)
|
||||
self.assertEqual(sealed.__dict__, before)
|
||||
self.assertNotIn(adapter.HOOK_NAME, sealed.__dict__)
|
||||
self.assertIs(function.__globals__[adapter.HOOK_NAME], hook)
|
||||
self.assertIs(function.__globals__['marker'], sealed.marker)
|
||||
self.assertEqual(function.__globals__['__file__'], str(Path(adapter.__file__).absolute()))
|
||||
self.assertNotEqual(function.__globals__['__file__'], sealed.__file__)
|
||||
self.assertFalse(audit['fake_original_file_used'])
|
||||
self.assertFalse(audit['original_module_globals_patched'])
|
||||
|
||||
def test_real_source_dependency_hashes_without_importing_gpu(self):
|
||||
self.source()
|
||||
proof = adapter.verify_sources(AJ.parent)
|
||||
self.assertEqual({key: row['sha256'] for key, row in proof.items()}, adapter.PINS)
|
||||
self.assertEqual(hashlib.sha256(AJ.read_bytes()).hexdigest(), adapter.AJ_SHA256)
|
||||
|
||||
def test_derived_hook_runs_after_apl_zero_and_before_any_state_tick(self):
|
||||
self.source()
|
||||
|
||||
class StopBeforeBrain(BaseException):
|
||||
pass
|
||||
|
||||
class Tensor:
|
||||
def __init__(self, array):
|
||||
self.array = np.array(array, copy=True)
|
||||
|
||||
def to(self, *, device, dtype):
|
||||
return Tensor(self.array.astype(dtype))
|
||||
|
||||
def contiguous(self):
|
||||
return self
|
||||
|
||||
def clone(self):
|
||||
return Tensor(self.array)
|
||||
|
||||
def __setitem__(self, item, value):
|
||||
self.array[item.array if isinstance(item, Tensor) else item] = value
|
||||
|
||||
torch_stub = types.SimpleNamespace(from_numpy=Tensor, float64=np.float64, int64=np.int64,
|
||||
int32=np.int32, bool=bool, cuda=types.SimpleNamespace(is_available=lambda: True,
|
||||
device_count=lambda: 1, get_device_name=lambda: 'synthetic RTX4090', reset_peak_memory_stats=lambda _: None))
|
||||
graph = {'weights': np.array([.1, -.2], np.float64), 'nodes': np.array([3, 9], np.int32),
|
||||
'edge_id': np.array([4, 12], np.int64), 'ptr': np.array([0, 1, 2], np.int64),
|
||||
'dst': np.array([1, 0], np.int32), 'src': np.array([0, 1], np.int32)}
|
||||
apl = types.SimpleNamespace(device='no-device', apl_globals=np.array([9], np.int32), metadata={})
|
||||
sealed = types.ModuleType('synthetic_before_loop')
|
||||
sealed.__file__ = str(AJ)
|
||||
sealed.__dict__.update(Path=Path, time=time, np=np, torch=torch_stub, require=overlay.require,
|
||||
source_audit=lambda _: {'scope': 'synthetic inherited AJ source'}, write_json=lambda *_: None,
|
||||
load_inputs=lambda _: ({'apl_bundle_dir': 'unused', 'dt_ms': .1, 'steps': 2}, graph, {},
|
||||
np.empty(0, np.int32), np.empty(0, np.int32), np.empty(0, np.int32), {}),
|
||||
sealed_backend=lambda: types.SimpleNamespace(build_extension=lambda _: (None, {})),
|
||||
sealed_apl=lambda: types.SimpleNamespace(LegacyAPL=lambda *_: apl))
|
||||
observed = []
|
||||
|
||||
def hook(weights, actual_graph, mask, upload, out_dir, provenance):
|
||||
self.assertTrue(overlay.bit_equal(weights.array, np.array([.1, 0.], np.float64)))
|
||||
self.assertTrue(overlay.bit_equal(graph['weights'], np.array([.1, -.2], np.float64)))
|
||||
np.testing.assert_array_equal(mask, [False, True])
|
||||
observed.append(provenance)
|
||||
raise StopBeforeBrain()
|
||||
|
||||
derived, _ = adapter.compile_derived(sealed, hook)
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
with self.assertRaises(StopBeforeBrain):
|
||||
derived(Path(directory), Path(directory) / 'new-output', Path(directory) / 'new-build')
|
||||
self.assertEqual(len(observed), 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,55 @@
|
||||
"""Small independent checks of provenance/summary mechanics."""
|
||||
import ast
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
import numpy as np
|
||||
from prepare import old_base_candidate
|
||||
from summarize import first_ticks, metrics
|
||||
from run_suite import output_inventory, verify_completed
|
||||
from context import save
|
||||
|
||||
|
||||
class WorkflowTests(unittest.TestCase):
|
||||
def test_old_expression_float32(self):
|
||||
candidates = [Path(__file__).resolve().parent.parent / 'phase4c.py',
|
||||
Path(__file__).resolve().parent.parent / 'models/male-cns-lif/phase4c.py']
|
||||
source = next(p for p in candidates if p.is_file())
|
||||
x = np.array([1, 2, 9, 19, 1234, 1048577], np.int64)
|
||||
value, sha = old_base_candidate(x, source)
|
||||
expected = x.astype(np.float32) * np.int8(1) * float(.275)
|
||||
self.assertEqual(value.tobytes(), expected.tobytes())
|
||||
self.assertEqual(len(sha), 64)
|
||||
|
||||
def test_first_tick_and_missing(self):
|
||||
state = dict(step=np.array([0, 2, 3, 4], np.int32), neuron_idx=np.array([1, 2, 1, 2], np.int32))
|
||||
np.testing.assert_array_equal(first_ticks(state, 4), [-1, 0, 2, -1])
|
||||
|
||||
def test_same_counts_event_change_not_hidden(self):
|
||||
a = dict(step=np.array([0], np.int32), neuron_idx=np.array([1], np.int32),
|
||||
counts=np.array([0, 1, 0], np.int32), v=np.zeros(3), g=np.zeros(3))
|
||||
b = {k: v.copy() for k, v in a.items()}; b['step'][0] = 2
|
||||
m = metrics(a, b, np.array([1], np.int32))
|
||||
self.assertTrue(m['event_sequence_changed']); self.assertEqual(m['count_changed_cells'], 0)
|
||||
self.assertEqual(m['first_spike_changed_cells'], 1); self.assertEqual(m['first_event_divergence_ms'], 0)
|
||||
|
||||
def test_extra_or_changed_output_rejected(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
folder = Path(temp)
|
||||
save(folder / 'comparison.json', {'pass_all': True})
|
||||
save(folder / 'summary.json', {'complete': True})
|
||||
save(folder / 'output-hashes.json', output_inventory(folder))
|
||||
verify_completed(folder)
|
||||
save(folder / 'unexpected.json', {})
|
||||
with self.assertRaises(AssertionError):
|
||||
verify_completed(folder)
|
||||
|
||||
def test_exclusive_json(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
path = Path(temp) / 'value.json'; save(path, {})
|
||||
with self.assertRaises(FileExistsError):
|
||||
save(path, {})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,220 @@
|
||||
"""Synthetic AM workflow identity/dispatch tests; no old evidence or network."""
|
||||
import builtins
|
||||
import copy
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
from types import ModuleType
|
||||
import unittest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import run_suite as runner
|
||||
|
||||
|
||||
class WorkflowIdentityTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.root = Path(self.temp.name).resolve()
|
||||
self.exp = self.root / "experiments/new-am"
|
||||
self.ak = self.root / "experiments/fake-ak"
|
||||
self.patches = patch.multiple(runner, ROOT=self.root, EXP=self.exp, AK=self.ak)
|
||||
self.patches.start()
|
||||
self.addCleanup(self.patches.stop)
|
||||
|
||||
def fixture(self, stage="zero", repeat=0, name="synthetic-odor"):
|
||||
row = dict(name=name, repeat=repeat, cohort="seed_validation")
|
||||
case = self.ak / "cases" / row["cohort"] / name
|
||||
case.mkdir(parents=True, exist_ok=True)
|
||||
(case / "spec.json").write_bytes(b'{"synthetic":true}\n')
|
||||
# This is a hash-only mock input, never parsed as scientific NPZ data.
|
||||
(case / "requests.npz").write_bytes(b"synthetic-request-bytes")
|
||||
overlays = self.exp / "overlays"
|
||||
overlays.mkdir(parents=True, exist_ok=True)
|
||||
(overlays / (stage + ".npz")).write_bytes((stage + "-synthetic-overlay").encode())
|
||||
(overlays / (stage + ".json")).write_bytes((stage + "-synthetic-manifest").encode())
|
||||
folder = self.exp / "gpu" / stage / (name + "-r" + str(repeat))
|
||||
folder.mkdir(parents=True, exist_ok=False)
|
||||
summary = dict(case=name, complete=True, pass_all=True, input_sha256={
|
||||
key: runner.digest(case / key) for key in ("spec.json", "requests.npz")})
|
||||
overlay = dict(complete=True, overlay_name=stage, input={
|
||||
"overlay_sha256": runner.digest(overlays / (stage + ".npz")),
|
||||
"manifest_sha256": runner.digest(overlays / (stage + ".json"))})
|
||||
if stage == "zero":
|
||||
mode = "zero"
|
||||
left = self.ak / "gpu" / row["cohort"] / (name + "-r" + str(repeat))
|
||||
elif repeat == 1:
|
||||
mode = "repeat"
|
||||
left = self.exp / "gpu" / stage / (name + "-r0")
|
||||
else:
|
||||
mode, left = "self", folder
|
||||
report = dict(pass_all=True, overlay_name=stage, case_dir=str(case),
|
||||
right=str(folder), left=str(left), mode=mode)
|
||||
for key, value in (("summary.json", summary), ("overlay-audit.json", overlay),
|
||||
("comparison.json", report)):
|
||||
runner.save(folder / key, value)
|
||||
runner.save(folder / "output-hashes.json", runner.output_inventory(folder))
|
||||
return row, case, folder, report
|
||||
|
||||
@staticmethod
|
||||
def replace_json(path, value):
|
||||
# Mutations are deliberate corruptions of NEW temporary test fixtures.
|
||||
path.write_text(json.dumps(value), encoding="utf-8")
|
||||
|
||||
def corrupt(self, folder, filename, field, value):
|
||||
original = runner.read(folder / filename)
|
||||
changed = copy.deepcopy(original)
|
||||
target = changed
|
||||
for key in field[:-1]:
|
||||
target = target[key]
|
||||
target[field[-1]] = value
|
||||
self.replace_json(folder / filename, changed)
|
||||
return original
|
||||
|
||||
def test_three_legitimate_identities_and_zero_repeat(self):
|
||||
for stage, repeat in (("zero", 0), ("zero", 1), ("static25", 0), ("static25", 1)):
|
||||
with self.subTest(stage=stage, repeat=repeat):
|
||||
row, _, folder, _ = self.fixture(stage, repeat)
|
||||
runner.verify_identity(folder, stage, row)
|
||||
runner.verify_completed(folder, stage, row)
|
||||
|
||||
def test_wrong_case_and_cohort_fail_even_with_valid_hash_inventory(self):
|
||||
row, _, folder, _ = self.fixture()
|
||||
for altered in (dict(row, name="other-odor"), dict(row, cohort="novel_to_aj")):
|
||||
with self.subTest(altered=altered), self.assertRaises((AssertionError, FileNotFoundError)):
|
||||
runner.verify_identity(folder, "zero", altered)
|
||||
self.corrupt(folder, "summary.json", ("case",), "other-odor")
|
||||
self.replace_json(folder / "output-hashes.json", runner.output_inventory(folder))
|
||||
with self.assertRaises(AssertionError):
|
||||
runner.verify_completed(folder, "zero", row)
|
||||
|
||||
def test_wrong_stage_and_repeat_path_are_rejected(self):
|
||||
row, _, folder, _ = self.fixture()
|
||||
for stage, wrong_row in (("static25", row), ("zero", dict(row, repeat=1)),
|
||||
("unknown", row), ("zero", dict(row, repeat=2))):
|
||||
with self.subTest(stage=stage, row=wrong_row), self.assertRaises(AssertionError):
|
||||
runner.verify_identity(folder, stage, wrong_row)
|
||||
|
||||
def test_overlay_stage_identity_and_two_input_hashes_are_required(self):
|
||||
row, _, folder, _ = self.fixture("static25")
|
||||
for field, value in ((("overlay_name",), "zero"), (("complete",), False),
|
||||
(("input", "overlay_sha256"), "0" * 64),
|
||||
(("input", "manifest_sha256"), "0" * 64)):
|
||||
original = self.corrupt(folder, "overlay-audit.json", field, value)
|
||||
with self.subTest(field=field), self.assertRaises(AssertionError):
|
||||
runner.verify_identity(folder, "static25", row)
|
||||
self.replace_json(folder / "overlay-audit.json", original)
|
||||
|
||||
def test_summary_completion_and_actual_case_hashes_are_required(self):
|
||||
row, _, folder, _ = self.fixture()
|
||||
for field, value in ((("complete",), False), (("pass_all",), False),
|
||||
(("input_sha256", "spec.json"), "0" * 64),
|
||||
(("input_sha256", "requests.npz"), "0" * 64)):
|
||||
original = self.corrupt(folder, "summary.json", field, value)
|
||||
with self.subTest(field=field), self.assertRaises(AssertionError):
|
||||
runner.verify_identity(folder, "zero", row)
|
||||
self.replace_json(folder / "summary.json", original)
|
||||
|
||||
def test_comparison_case_paths_mode_and_stage_are_required(self):
|
||||
for stage, repeat in (("zero", 0), ("static25", 0), ("static25", 1)):
|
||||
row, _, folder, _ = self.fixture(stage, repeat)
|
||||
for field, value in ((("case_dir",), str(self.root / "wrong-case")),
|
||||
(("right",), str(self.root / "wrong-output")),
|
||||
(("left",), str(self.root / "wrong-reference")),
|
||||
(("overlay_name",), "not-the-stage"),
|
||||
(("mode",), "invalid-mode"), (("pass_all",), False)):
|
||||
original = self.corrupt(folder, "comparison.json", field, value)
|
||||
with self.subTest(stage=stage, repeat=repeat, field=field), self.assertRaises(AssertionError):
|
||||
runner.verify_identity(folder, stage, row)
|
||||
self.replace_json(folder / "comparison.json", original)
|
||||
|
||||
def test_self_report_cannot_substitute_for_repeat(self):
|
||||
row, _, folder, _ = self.fixture("static25", 1)
|
||||
report = runner.read(folder / "comparison.json")
|
||||
report.update(mode="self", left=str(folder))
|
||||
self.replace_json(folder / "comparison.json", report)
|
||||
self.replace_json(folder / "output-hashes.json", runner.output_inventory(folder))
|
||||
with self.assertRaises(AssertionError):
|
||||
runner.verify_completed(folder, "static25", row)
|
||||
|
||||
def test_recompute_must_match_full_stored_report(self):
|
||||
row, _, folder, report = self.fixture("static25", 1)
|
||||
with patch.object(runner, "compare_expected", return_value=copy.deepcopy(report)) as compare:
|
||||
runner.verify_completed(folder, "static25", row, recompute=True)
|
||||
compare.assert_called_once_with("static25", row, folder)
|
||||
for result in (dict(report, pass_all=False), dict(report, newly_detected_difference=True)):
|
||||
with patch.object(runner, "compare_expected", return_value=result), self.assertRaises(AssertionError):
|
||||
runner.verify_completed(folder, "static25", row, recompute=True)
|
||||
|
||||
def test_partial_folder_is_preserved_without_attempted_comparison(self):
|
||||
row, _, folder, _ = self.fixture()
|
||||
(folder / "output-hashes.json").unlink() # Deliberately incomplete NEW fixture.
|
||||
before = {p.name: p.read_bytes() for p in folder.iterdir()}
|
||||
with patch.object(runner, "compare_expected") as compare, self.assertRaises(FileNotFoundError):
|
||||
runner.verify_completed(folder, "zero", row, recompute=True)
|
||||
compare.assert_not_called()
|
||||
self.assertEqual(before, {p.name: p.read_bytes() for p in folder.iterdir()})
|
||||
|
||||
def test_compare_expected_zero_dispatches_to_corresponding_ak_repeat(self):
|
||||
self.assert_dispatch("zero", 0)
|
||||
self.assert_dispatch("zero", 1)
|
||||
|
||||
def test_compare_expected_static_repeat_dispatches_to_r0(self):
|
||||
self.assert_dispatch("static25", 1)
|
||||
|
||||
def test_compare_expected_static_primary_uses_self_validation(self):
|
||||
self.assert_dispatch("static25", 0)
|
||||
|
||||
def assert_dispatch(self, stage, repeat):
|
||||
fake = ModuleType("comparison")
|
||||
fake.compare_runs = Mock(return_value={"marker": "pair"})
|
||||
fake.validate_run = Mock(return_value={"marker": "single"})
|
||||
row = dict(name="synthetic", cohort="novel_to_aj", repeat=repeat)
|
||||
folder = self.exp / "gpu" / stage / (row["name"] + "-r" + str(repeat))
|
||||
case = self.ak / "cases" / row["cohort"] / row["name"]
|
||||
with patch.dict(sys.modules, {"comparison": fake}):
|
||||
result = runner.compare_expected(stage, row, folder)
|
||||
if stage == "zero":
|
||||
reference = self.ak / "gpu" / row["cohort"] / (row["name"] + "-r" + str(repeat))
|
||||
fake.compare_runs.assert_called_once_with(case, reference, folder, mode="zero")
|
||||
fake.validate_run.assert_not_called()
|
||||
self.assertEqual(result, {"marker": "pair"})
|
||||
elif repeat:
|
||||
reference = self.exp / "gpu" / stage / (row["name"] + "-r0")
|
||||
fake.compare_runs.assert_called_once_with(case, reference, folder, mode="repeat")
|
||||
fake.validate_run.assert_not_called()
|
||||
self.assertEqual(result, {"marker": "pair"})
|
||||
else:
|
||||
fake.validate_run.assert_called_once_with(case, folder)
|
||||
fake.compare_runs.assert_not_called()
|
||||
self.assertEqual(result, {"marker": "single"})
|
||||
# No output_path was passed: the comparator cannot write into evidence.
|
||||
|
||||
def test_preflight_sets_cuda_environment_without_torch_or_network(self):
|
||||
imported = builtins.__import__
|
||||
|
||||
def guarded_import(name, *args, **kwargs):
|
||||
if name == "torch" or name.startswith("torch."):
|
||||
self.fail("preflight imported Torch")
|
||||
return imported(name, *args, **kwargs)
|
||||
|
||||
def response(url, timeout):
|
||||
self.assertEqual(os.environ["CUDA_VISIBLE_DEVICES"], runner.CFG["gpu_uuid"])
|
||||
self.assertEqual(os.environ["CUDA_HOME"], "/usr/local/cuda")
|
||||
self.assertEqual(os.environ["TORCH_CUDA_ARCH_LIST"], "8.9")
|
||||
self.assertEqual(timeout, 5)
|
||||
return io.BytesIO(b'{"queue_running":[],"queue_pending":[]}')
|
||||
|
||||
with patch.dict(os.environ, {"PATH": "synthetic-original-path"}), \
|
||||
patch("builtins.__import__", side_effect=guarded_import), \
|
||||
patch.object(runner.urllib.request, "urlopen", side_effect=response) as urlopen:
|
||||
runner.preflight()
|
||||
self.assertEqual(urlopen.call_count, 3)
|
||||
self.assertTrue(os.environ["PATH"].endswith("synthetic-original-path"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Old deterministic-v2 references, into new output paths only."""
|
||||
import sys
|
||||
import time
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import yaml
|
||||
from context import ROOT, EXP, check, save, bit_equal
|
||||
from run_suite import preflight
|
||||
|
||||
|
||||
def main():
|
||||
check(full=True)
|
||||
assert not (EXP / 'regression/frozen-five.json').exists()
|
||||
preflight()
|
||||
runtime_path = ROOT / 'models/male-cns-lif/deterministic-runtime-v2'
|
||||
pc = yaml.safe_load((runtime_path / 'phase4p.yaml').read_text())
|
||||
sys.path.insert(0, str(ROOT / 'models/male-cns-lif'))
|
||||
sys.path.insert(0, str(runtime_path))
|
||||
import torch
|
||||
import phase4p
|
||||
assert torch.cuda.mem_get_info()[0] > 2 * 1024**3
|
||||
torch.cuda.reset_peak_memory_stats()
|
||||
started = time.perf_counter()
|
||||
runtime = phase4p.CanonicalRuntime(pc, runtime_path, EXP)
|
||||
rows = []
|
||||
for spec in pc['reference_trials']:
|
||||
preflight()
|
||||
reference_dir = ROOT / 'experiments/phase4p-deterministic-runtime/phase4c-v2/reference' / spec['name']
|
||||
events = pd.read_parquet(reference_dir / 'spike-events.parquet')
|
||||
reference = dict(step=events.step.to_numpy(), neuron_idx=events.neuron_idx.to_numpy(),
|
||||
counts=np.load(reference_dir / 'per-neuron-spike-counts.int32.npy'),
|
||||
v=np.load(reference_dir / 'final-voltage.float32.npy'), g=np.load(reference_dir / 'final-g.float32.npy'))
|
||||
result = runtime.run(np.flatnonzero(runtime.pops[spec['population']]), spec['rate_hz'], spec['seed'], spec['duration_ms'])
|
||||
row = dict(trial=spec['name'], exact={key: bool(bit_equal(value, result[key])) for key, value in reference.items()},
|
||||
dtype_shape={key: dict(reference_dtype=str(value.dtype), actual_dtype=str(result[key].dtype),
|
||||
reference_shape=list(value.shape), actual_shape=list(result[key].shape)) for key, value in reference.items()},
|
||||
wall_seconds=result['metrics']['wall_seconds'])
|
||||
rows.append(row); print(row, flush=True)
|
||||
passed = len(rows) == 5 and all(all(row['exact'].values()) for row in rows)
|
||||
save(EXP / 'regression/frozen-five.json', dict(pass_all=passed, trials=rows,
|
||||
wall_seconds=time.perf_counter() - started, peak_allocated_vram=int(torch.cuda.max_memory_allocated()),
|
||||
scope='Old frozen deterministic-v2 references; not a claim D18 equals old runtime'))
|
||||
assert passed
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user