Audit fixed D18 credit and conditional MBON transfer (Phase 4AL)

This commit is contained in:
Digital Drosophila Archive
2026-09-30 13:58:39 +08:00
parent c6444255fe
commit 6d5f66bcc3
1058 changed files with 120679 additions and 18 deletions
@@ -0,0 +1,201 @@
# Phase 4AL — credit specificity and conditional KC→MBON transfer
## Purpose and frozen boundary
This is a diagnostic audit of the already sealed Phase 4AK D18 branch, not
conditioning, training, or a new learning candidate. Phase 4AK established its
predeclared 100 ms KC representation gate; it did not establish appropriate
credit assignment, functional associative memory, or biological fidelity of
every omitted mechanism.
Use all 120 Phase 4AK primary trials and all 24 exact-repeat trials. Keep the
two cohorts and their two input modes separate. Repeats check reproducibility;
they are not extra independent observations in the five-seed statistics. Do
not replace seeds, select favorable odors, choose a best pair or compartment,
or inspect old held-out dynamic outcomes to change this design.
The inherited matched-energy mode matches expected input, not necessarily the
realized event count of every stochastic trial; do not relabel it exact
realized-count matching.
All earlier raw, processed, frozen/reference, model and experiment artifacts
remain read-only. Preserve the base CSR, anatomical counts, transmitter signs,
LIF parameters, D18 operator, APL settings, eligibility-v2, and existing 14,232
plastic-edge mask. All new records belong only to this new audit. No DAN
stimulus is injected; no reinforcement is delivered to a brain; no learned
`plastic_delta` is written. No decoder, action selection, game, RL, or visual
interface is introduced.
## Three distinct evidence levels
1. **Exact baseline reconstruction.** Reconstruct target MBONs from the actual
frozen full-network events using real edges, original arithmetic/order,
initial states, arrivals, refractory acceptance, resets, and applicable APL
handling. Require bitwise identity of events, per-neuron counts, and final
V/g/ref against Phase 4AK D18. A failed or incomplete reconstruction blocks
scientific interpretation of that replay; it is never rescued by plausible
plots or by agreement of aggregate firing rates. Complete-audit integrity
requires all 144 baseline replays; do not silently drop failed cases and
report the remaining subset as the complete predeclared audit.
2. **Virtual credit audit.** Replay the unchanged, existing float32 eligibility
operator and inspect its state at the fixed tick-900 virtual gate, after
that tick's original `mul → add → clamp` sequence. Tick-900 spikes are
included. This is shadow accounting only, not dopamine delivery or weight
updating; a before/after-update change is a different experiment. Do not
treat virtual gate amplitude as measured dopamine concentration.
3. **One fixed conditional sensitivity test.** Compare the exact baseline with
one temporary replay applying uniform 25% depression to the whole existing
plastic-edge mask. This uses the pre-existing maximum allowed fractional
depression as a single declared diagnostic intervention. It is not a
fraction scan, an eligibility-trained delta, a selected successful memory,
or permission to enlarge the mask. The base weights remain unchanged; only
the diagnostic effective amplitudes change, preserving edge identity and
transmitter sign. On an independent incoming-weight copy, apply
`weights[plastic] *= 0.75` throughout the full 100 ms replay, not only after
tick 900. All presynaptic spike trains, including those of other MBONs and
DANs, remain the original recorded trains. Do not save the overlay as a
learned or official model.
The 25% intervention is **not an upper bound on functional response**. LIF
threshold, reset, refractory gating, and recurrent influences make responses
nonlinear; the largest permitted fractional depression does not establish the
largest attainable change in spikes, latency, or downstream activity.
## Incoming-drive and overlap accounting
Decompose actual MBON incoming drive into six mutually exclusive groups:
- plastic KC→MBON;
- non-plastic KC→MBON;
- DAN;
- MBON;
- APL;
- other.
Use the frozen canonical IDs, mask membership and audited metadata, not guessed
names or transmitter-to-cell-role substitutions. Preserve unknown role
provenance inside the residual `other` group. Establish each edge's group once;
do not reclassify it to improve the result. Report accepted/rejected arrivals,
signed contributions, and roundoff/reset effects as the implementation makes
available. The model's variable `g` is a model state; do not rename it a measured
biophysical conductance or dopamine signal. A source's share of accepted drive
is not its necessity, sufficiency, or unique causal contribution to a spike.
Any ratio with a zero/undefined denominator must remain explicitly undefined.
The sole set-similarity convention is empty/empty Jaccard = 1 with an explicit
`empty_union` flag. Such pairs are counted separately and excluded from block
overlap aggregates; this convention is never evidence of useful credit.
For each cohort × mode, retain all 15 unordered pairs of its six odors: 60
pair records in total. Report two separate three-of-five-seeds memberships for
each odor: (1) a strictly positive frozen eligibility trace sampled after the
tick-900 update, including tick-900 spikes, and (2) a strictly positive spike
count anywhere in the full 100 ms trial. The primary virtual-credit grouping
uses the first definition. The whole-window activity grouping is auxiliary;
it cannot replace the primary grouping to rescue a result. These are
descriptive reliability rules, not biological thresholds, top-K filters, or
ways to delete cells/edges from replay. Preserve per-trial activity and
eligibility alongside both aggregate memberships.
Compute overlap on the actual A-active and B-active sets. In particular,
`Jaccard(A_active, B_active)` must use their real intersection and union; never
substitute the already disjoint sets `A_only` and `B_only`, whose zero
intersection is true by construction. Shared, A-only, B-only, and inactive
strata are useful descriptive partitions, not proof of stimulus-specific
causation. Shared representations can support biological generalization;
nonzero B effects are not automatically a defect, nor are they automatically
evidence of valid generalization.
Keep arithmetic/integrity checks separate from scientific results. Report all
pairwise and trial-level outcomes without inventing an overall 120-trial
biological PASS fraction, post hoc effect threshold, or pooled rescue gate.
An absence of MBON spike change may coexist with a subthreshold change and
does not mean the entire biological pathway is incapable of plasticity.
## What a conditional replay can and cannot establish
The baseline can establish that the saved events and the frozen operator
reconstruct the saved MBON trajectory exactly. Virtual eligibility can reveal
which real candidate edges would receive credit under that declared rule.
The single depression replay can measure how the selected MBONs respond to
that fixed intervention **with the external network event history held fixed**.
It cannot establish closed-loop whole-brain necessity/sufficiency or learned
behavior. If an MBON's replayed spikes change while its outgoing effects and
the other neurons' events remain clamped to the old trial, MBON→DAN, MBON→MBON,
and other feedback are not recomputed self-consistently. Their fixed incoming
traces must not be described as counterfactual feedback responses. If the
replay instead also clamps the target MBON's original reset/refractory gates,
its result is narrower still: conditional drive/state accounting, not an
independently recomputed functional spike response. State these scopes
explicitly for each reported quantity.
The anatomical D18 arrival delay is 18 × 0.1 ms = 1.8 ms. It is not the
eligibility lifetime, a measured dopamine latency, or the CS–reinforcer
association window. A 100 ms observation does not test memory retention,
consolidation, extinction, reversal, or behavioral recall. Do not promote any
successful replay to a dopamine-plasticity or learning candidate.
## Primary evidence and non-measured assumptions
- **Hige et al., Neuron (2015), DOI 10.1016/j.neuron.2015.11.003.** Pairing an
odor with selected DAN activation produced odor-specific, compartment-local
KC→MBON depression with order dependence. The reported induction did not
require postsynaptic MBON spiking; overlapping KC representations predicted
reduced specificity/generalization. This supports auditing the correct
presynaptic and compartment credit, not requiring MBON spikes to define all
plasticity. It does not specify MaleCNS per-edge learning parameters.
[Author publication and paper](https://www.janelia.org/publication/heterosynaptic-plasticity-underlies-aversive-olfactory-learning-drosophila).
- **Handler et al., Cell (2019), DOI 10.1016/j.cell.2019.05.040.** DopR1/DopR2
pathways support opposing plasticity associated with different cue–DAN
timing. This establishes why time ordering matters, not that the frozen
float32 trace is a measured biochemical state or that one rule applies to
every compartment. [Primary paper](https://www.sciencedirect.com/science/article/pii/S0092867419306117).
- **Aso and Rubin, eLife (2016), DOI 10.7554/eLife.16135.** DAN cell types differ
in memory formation, updating and persistence. PAM/PPL1 labels alone are
not a license to assign a universal valence or a common temporal rule.
[Primary paper](https://elifesciences.org/articles/16135).
- **Aso et al., eLife (2014), DOI 10.7554/eLife.04580.** Different MBON types
bias approach/avoidance, with combined activation supporting population
integration. Assessing a population response is more appropriate than
equating greater weight or total firing with positive memory. The current
audit nevertheless has no behavioral decoder or demonstrated behavior.
[Primary paper](https://elifesciences.org/articles/04580.pdf).
- **Huang et al., Nature (2024), DOI 10.1038/s41586-024-07819-w.** Physiological
measurements and a constrained model show heterogeneous, bidirectional
PPL1 signaling and feedback interactions among memory units. This motivates
explicit closed-loop limitations. Its fitted model weights and plasticity
parameters are not MaleCNS anatomical counts or measurements to import into
this frozen branch. [Primary paper](https://www.nature.com/articles/s41586-024-07819-w).
- **Manoim et al., Current Biology (2022), DOI 10.1016/j.cub.2022.09.007, and
Manoim Wolkovitz et al. (2026), DOI 10.1016/j.cub.2026.01.014.** KC lateral
axonal mAChR-B modulation contributes to stimulus specificity; subsequent
work identifies voltage-dependent modulation relevant to active versus
inactive KCs. Sparse coding plus a timed reinforcer is therefore not, by
itself, proof of appropriate credit. These studies do not prove that an
omitted receptor mechanism caused any result in this fixed model, and do
not authorize a sign, gain, threshold, or receptor-parameter change.
[2022 paper](https://www.sciencedirect.com/science/article/pii/S0960982222014518);
[2026 paper](https://www.sciencedirect.com/science/article/pii/S096098222600014X).
The frozen eligibility operator, tick-900 virtual gate, three-of-five activity
summary, 25% uniform diagnostic depression, clamped event history, and chosen
observation window are engineering/model assumptions. None is relabelled as a
MaleCNS single-cell or synaptic measurement by citing the papers above.
## Deferred functional-memory controls — not executed here
A later explicitly authorized learning experiment would need pre/post CS+
and CS− responses, reciprocal cue assignment, independent evaluation seeds,
and reinforcement-free probes with updates disabled. CS-only, DAN-only,
unpaired/yoked, D(t)=0, and frozen/delta=0 conditions distinguish associative
effects from exposure, transient state, adaptation and unrelated modulation.
Real DAN-only stimulation is not universally expected to have zero effect;
that prediction depends on the declared model and compartment. Retention
requires its own observation interval, and any model-specific output effect
must remain distinct from a behavioral-memory claim. No such controls are
silently replaced by virtual credit or the 25% sensitivity test.
Google/DeepMind's [FlyBody work](https://www.nature.com/articles/s41586-025-09029-4)
provides body physics and artificial RL controllers, not evidence for this
KC→MBON credit rule. Google's [MaleCNS author resource](https://research.google/blog/a-connectomics-milestone-mapping-the-complete-male-fruit-fly-brain/)
supports the anatomical resource, not measured dopamine kinetics or successful
learning in this model. Neither permits bypassing the present mechanism audit.
@@ -0,0 +1,13 @@
# Preparation record
The first preparation invocation stopped before experiment creation or
registration: the ordinary account could not create a child of the protected
experiments directory. No trajectory, credit calculation or conditional replay
was executed. The fix provisions only the new, empty Phase 4AL directory with
the ordinary account as owner; preparation permits an absent or empty directory
but still refuses any populated experiment. No old directory permission is
changed. This is an execution-environment correction, not a scientific change.
Local synthetic tests initially had no Torch installation and explicitly
skipped the Torch arithmetic comparison. Server testing includes this comparison
and preparation rejects skipped tests.
@@ -0,0 +1,15 @@
cda2d810eada83ea33c96c1ce9d16102d1af22d1862bce84c47f0e7415863eca EVIDENCE.md
8259428a56721ba8174039846623998ef11a1d8e9e14f123dfe0a0e4849c71ee PREPARATION.md
b996e175731a920ad19e2c8989bd267cdbb71c14aa4558a8f1ef7486454c4b44 config.json
1626873baa224fab5d427a1fab1d6f840f14814152914ac4c2d6aa7a4f0bb1f6 context.py
e03d3e1f64328058273e8364990e7a2c7f1527a31822ea7594a9e8914aa43497 credit.py
9356587aa8811a3db50daee9bca22cf4114e06c6669cc80c69cd0dd2224b0130 finalize.py
825e9e60cb579479ddb2860a36f8dee0715b9b44d624cbad59ce0d279550d17f mbon_accounting.py
9e7532bc4a564b0a0c0384ba2f3a7bfc0559c16bb256510dde7c97cce9036c0c prepare.py
3ece589f0f6ce1bff2cde94494e896a58fa4154106e613ee7e60549094304071 run_audit.py
62432fe7b068eafae99396c4a9231adf8ef8e9d340ab64517f618fc794e7fd78 summarize.py
4c473ce709ed9496b7ce1b5bedd6964c780715ff41dd54977489fbd2b99f998e test_credit.py
23c4e9138b326e1a84fed88d771a6c2044a6abf72ecc7a1b829f604bcdde215d test_integration.py
d192dd357db96e628c21b9bcde617eaf6c6bc1efe5920ec55cc9cc1e4b549454 test_mbon_accounting.py
85442fcefa93c64a2fac7f0a4b60ce505ddb9edab7a63cb0e25cb20f013d3850 verify_frozen.py
72e4b4e2c53c258939ca355f31eeba2cd6381e126a61efeea651d8bac8df5328 manifest.json
@@ -0,0 +1,24 @@
{
"phase": "4AL",
"purpose": "read-only D18 credit and conditional MBON transfer audit; not learning",
"neurons": 166700,
"edges": 25582938,
"candidate_edges": 14232,
"primary_trials": 120,
"technical_repeats": 24,
"steps": 1000,
"dt_ms": 0.1,
"virtual_gate_tick": 900,
"kc_trace_tau_ms": 100.0,
"trace_cap": 1.0,
"stable_seed_count": 3,
"conditional_depression_fraction": 0.25,
"groups": ["plastic_KC", "nonplastic_KC", "DAN", "MBON", "APL", "other"],
"workers": 4,
"output_budget_bytes": 4294967296,
"training": false,
"candidate_generation": false,
"gate_meaning": "Post-update tick 900 virtual assay, not a DAN or dopamine measurement",
"conditional_meaning": "Recorded all-source events clamped, target MBON spikes independently predicted; not whole-brain counterfactual",
"no_new_overall_credit_threshold": true
}
@@ -0,0 +1,116 @@
"""New read-only audit envelope. All old payloads are hash-anchored 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/phase4al-mb-credit-transfer'
AK = ROOT / 'experiments/phase4ak-apl-timing-validation'
AK_MODEL = ROOT / 'models/male-cns-lif/apl-timing-validation-v1'
H = ROOT / 'experiments/phase4h-dopamine-plasticity'
AG = ROOT / 'experiments/phase4ag-author-apl-r2'
CORE = ROOT / 'models/male-cns-lif/kc-drive-audit-v1/replay_core.py'
META = ROOT / 'datasets/male-cns-v1.0/processed/neuron-only/neuron-index/unified-metadata.parquet'
COEFF = ROOT / 'experiments/phase4af-author-fullgraph/oracle/no_input/coefficients.json'
CFG = json.loads((MODEL / 'config.json').read_text(encoding='utf-8'))
PINS = {
'models/male-cns-lif/runtime-credit-v1/phase4o_event_replay.py': 'b169937bab495c77d8ee6c1013877e7500e7354140d9fcc90595d984238ba44e',
'models/male-cns-lif/phase4l.py': 'e97d58d8c1f44920d0e953f8f5831ddc86fa0198784e86460e315de4116ac553',
'models/male-cns-lif/apl-timing-validation-v1/manifest.json': '3206a8666718b4a99e09b8384c87dd25895b5d7038d23fad7b93e40379183ccb',
'experiments/phase4ak-apl-timing-validation/manifest.json': '25eb7e467eb17c3fa83ffee2e4e82b754f161fd8891e515bed23eeb500c5656d',
'models/male-cns-lif/apl-timing-validation-v1/context.py': '5e4e500d5ec777bb8ae7ff77ac9a7279bfe995b1a17be2baededdbe065c98b73',
'experiments/phase4ak-apl-timing-validation/protocol/validation-cases.json': '50d7b3aa7b0235e8f7bc1e21ef693cd5c554272ef3bd205c72f61e8e07ad1fd9',
'experiments/phase4ak-apl-timing-validation/audit/final-status.json': 'c49181086ecbd19511b9f1e783afea8f66d0f74348a7b70d7f6c5fa99b4337fc',
'models/male-cns-lif/kc-drive-audit-v1/replay_core.py': 'aa3187cb23712c09ab603e27e60424294db92de8086d60a95cf7a629d0521fdc',
'models/male-cns-lif/mb-physiology-v1/phase4j.py': 'abb4574da85f00bd9ef61024c65993ef81d42651ff8d8d63c15dd69c6222471e',
'models/male-cns-lif/mb-physiology-v1/phase4j.yaml': 'b35e4332a91b8489f69348cedf5ac675e9c3902c7e8c7beec5d053773c6529a3',
'experiments/phase4h-dopamine-plasticity/manifests/checksums.sha256': '1ea0394fd0477bb40d656ed0854108964f3809d633b5561bed9de43cda630ba1',
'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():
previous = load_module('sealed_ak_context_al', AK_MODEL / 'context.py',
PINS['models/male-cns-lif/apl-timing-validation-v1/context.py'])
result = previous.integrity()
sys.path.insert(0, str(ROOT / 'models/male-cns-lif/terminal-scope-diagnostic-v1'))
from diagnostic import manifest_check
for path in (AK_MODEL, AK):
result[path.relative_to(ROOT).as_posix()] = manifest_check(path, digest)
assert all(row['pass'] for row in result.values())
return result
def check():
reg = read(EXP / 'protocol/preregistered.json')
assert reg['sources'] == sources() and reg['config'] == CFG
for row in reg['inputs']:
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'], 'Audit output budget exceeded; stop, preserve 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,171 @@
"""Read-only, float32 TemporalEligibility-v2 trace and fixed A/B identities.
This module consumes already observed full-graph spike events. It neither
simulates neurons nor generates a weight delta. A gate is a virtual readout
after the same-tick KC trace update, not evidence of an actual DAN event.
"""
from __future__ import annotations
import math
import numpy as np
N_GLOBAL = 166700
CATEGORY_CODES = {'inactive': 0, 'A_specific': 1, 'B_specific': 2, 'shared': 3}
MAJORITY = 3
REPLICATES = 5
def _require(condition, message):
if not condition:
raise ValueError(message)
def _vector(value, name, dtype):
_require(isinstance(value, np.ndarray) and value.ndim == 1 and value.dtype == np.dtype(dtype),
name + ' must be a one-dimensional ' + dtype + ' ndarray; no implicit dtype conversion')
return value
def _frozen_scalar(value, expected, name):
_require(not isinstance(value, (bool, np.bool_)) and isinstance(value, (int, float, np.number)) and
np.ndim(value) == 0 and math.isfinite(float(value)) and float(value) == expected,
name + ' must retain its frozen value ' + str(expected))
def replay_credit(step, neuron_idx, kc_globals, steps=1000, gate_tick=900,
dt_ms=.1, tau_ms=100, cap=1):
"""Replay the frozen trace without touching the caller's arrays.
``step`` and ``neuron_idx`` are equal-length int32 vectors for the complete
observed graph (or its KC-only subset), ordered uniquely by (tick, global
neuron). Non-KC events are validated then ignored. ``kc_globals`` is sorted
unique int32. The trace starts at +0 and, on every tick including tick zero,
performs separate float32 multiply, add current spikes, then clamp to 1.
No 18-tick shift is applied: the recorded KC emission tick is the trace
event tick. Gate counts include gate_tick; final counts cover [0, steps).
dt/tau/cap are exposed for explicit provenance but may not be changed.
Shorter steps/gates are allowed for synthetic contract tests.
Returns owned trace_gate/trace_final float32[K] and
spike_count_gate/spike_count_total int32[K], plus scalar audit metadata.
Memory is O(events + KC + global-neuron count), never a dense T-by-K trace.
"""
ticks = _vector(step, 'step', 'int32')
nodes = _vector(neuron_idx, 'neuron_idx', 'int32')
kc = _vector(kc_globals, 'kc_globals', 'int32')
_require(type(steps) is int and 0 < steps <= np.iinfo(np.int32).max, 'steps must be a positive int32-range integer')
_require(type(gate_tick) is int and 0 <= gate_tick < steps, 'gate_tick must lie in [0, steps)')
_frozen_scalar(dt_ms, .1, 'dt_ms')
_frozen_scalar(tau_ms, 100., 'tau_ms')
_frozen_scalar(cap, 1., 'cap')
_require(ticks.shape == nodes.shape, 'Event vectors have different lengths')
_require(np.all((ticks >= 0) & (ticks < steps)), 'Event tick outside the observed window')
_require(np.all((nodes >= 0) & (nodes < N_GLOBAL)), 'Event neuron outside the frozen global graph')
_require(len(ticks) < 2 or np.all((ticks[1:] > ticks[:-1]) |
((ticks[1:] == ticks[:-1]) & (nodes[1:] > nodes[:-1]))),
'Events must be sorted and unique by (tick, global neuron); duplicates are not extra spikes')
_require(len(kc) > 0 and kc[0] >= 0 and kc[-1] < N_GLOBAL and np.all(kc[1:] > kc[:-1]),
'kc_globals must be nonempty, sorted, unique, in-range global identities')
lookup = np.full(N_GLOBAL, -1, np.int32)
lookup[kc] = np.arange(len(kc), dtype=np.int32)
local = lookup[nodes]
selected = local >= 0
kc_ticks, kc_local = ticks[selected], local[selected]
counts_total = np.bincount(kc_local, minlength=len(kc)).astype(np.int32)
counts_gate = np.bincount(kc_local[kc_ticks <= gate_tick], minlength=len(kc)).astype(np.int32)
decay = np.float32(math.exp(-float(dt_ms) / float(tau_ms)))
trace = np.zeros(len(kc), np.float32)
trace_gate = None
begin = 0
for tick in range(steps):
# Preserve the source's separate rounding operations; do not substitute
# float64 exponentiation by elapsed time or shift spikes to arrivals.
np.multiply(trace, decay, out=trace)
end = int(np.searchsorted(kc_ticks, tick, side='right'))
if end > begin:
firing = kc_local[begin:end]
trace[firing] = np.add(trace[firing], np.float32(1.0))
np.minimum(trace, np.float32(1.0), out=trace)
if tick == gate_tick:
trace_gate = trace.copy()
begin = end
_require(begin == len(kc_ticks) and trace_gate is not None and np.isfinite(trace).all(), 'Incomplete or nonfinite trace replay')
return {
'trace_gate': trace_gate, 'trace_final': trace.copy(),
'spike_count_gate': counts_gate, 'spike_count_total': counts_total,
'steps': steps, 'gate_tick': gate_tick, 'dt_ms': float(dt_ms), 'tau_ms': float(tau_ms), 'cap': float(cap),
'decay_f32_bits': f'{int(decay.view(np.uint32)):08x}',
'total_input_events': int(len(ticks)), 'total_KC_events': int(len(kc_ticks)),
'KC_events_through_gate': int(counts_gate.sum(dtype=np.int64)),
'gate_includes_same_tick_spikes': True, 'event_shift_steps': 0,
'gate_is_virtual_readout_not_DAN_activity': True,
'weights_modified': False, 'weight_delta_generated': False,
}
def _traces(value, name):
_require(isinstance(value, np.ndarray) and value.ndim == 2 and value.dtype == np.dtype('float32') and
value.shape[0] == REPLICATES and value.shape[1] > 0,
name + ' must have exact shape (5, KC) and float32 dtype')
_require(np.isfinite(value).all() and np.all((value >= 0) & (value <= 1)),
name + ' must contain finite frozen-cap traces in [0, 1]')
return value
def _set_summary(active_a, active_b, category):
intersection = int(np.count_nonzero(active_a & active_b))
union = int(np.count_nonzero(active_a | active_b))
return {
'total': int(len(category)), 'A_active': int(np.count_nonzero(active_a)),
'B_active': int(np.count_nonzero(active_b)), 'intersection': intersection, 'union': union,
'active_set_jaccard': float(intersection / union) if union else 1.0,
'empty_union': union == 0,
'category_counts': {name: int(np.count_nonzero(category == code)) for name, code in CATEGORY_CODES.items()},
}
def pair_identity(traces_a, traces_b, edge_source_local):
"""Classify fixed paired five-seed A/B traces, without installing plasticity.
A KC is A-active/B-active if its gate trace is strictly positive in at
least 3 of 5 corresponding trials. Categories are int8: inactive=0,
A_specific=1, B_specific=2, shared=3. Every candidate edge inherits its
source KC's category in the supplied edge order. Multiple edges sharing
a KC are retained as separate rows, not independent neural observations.
``edge_source_local`` is int32[E] indexing the common KC axis of both
trace matrices. The caller must bind that axis and the five paired seed
rows to the frozen identities; this pure function cannot infer labels.
Jaccard uses COMPLETE active A/B sets, not the disjoint specific sets.
Empty/empty Jaccard is 1 with empty_union=True, never evidence of useful
credit. Compartment stratification and any success gate belong to caller.
"""
a, b = _traces(traces_a, 'traces_a'), _traces(traces_b, 'traces_b')
_require(a.shape == b.shape, 'A and B trace matrices use different KC axes')
source = _vector(edge_source_local, 'edge_source_local', 'int32')
_require(np.all((source >= 0) & (source < a.shape[1])), 'Candidate edge source is not a valid KC-local index')
positive_a = np.count_nonzero(a > np.float32(0), axis=0).astype(np.int32)
positive_b = np.count_nonzero(b > np.float32(0), axis=0).astype(np.int32)
active_a, active_b = positive_a >= MAJORITY, positive_b >= MAJORITY
category = active_a.astype(np.int8) + np.int8(2) * active_b.astype(np.int8)
edge_a, edge_b, edge_category = active_a[source].copy(), active_b[source].copy(), category[source].copy()
return {
'kc_category': category, 'edge_category': edge_category,
'kc_active_a': active_a, 'kc_active_b': active_b,
'edge_active_a': edge_a, 'edge_active_b': edge_b,
'kc_positive_trials_a': positive_a, 'kc_positive_trials_b': positive_b,
'summary': {
'replicates_per_condition': REPLICATES, 'majority_required': MAJORITY,
'positivity_rule': 'float32 trace > 0; no fitted epsilon', 'category_codes': dict(CATEGORY_CODES),
'KC': _set_summary(active_a, active_b, category),
'edges': _set_summary(edge_a, edge_b, edge_category),
'jaccard_scope': 'complete majority-active A and B sets, including shared members',
'empty_union_jaccard_convention': 1.0,
'edge_counts_are_source_degree_weighted': True,
'actual_DAN_credit_claimed': False, 'weights_modified': False, 'weight_delta_generated': False,
},
}
@@ -0,0 +1,37 @@
"""Seal all evidence without mistaking diagnostic coverage for learning."""
from context import MODEL, EXP, read, save, digest, check, integrity, budget, seal
def main():
check()
from summarize import run
summary = run(verify_only=True)
assert summary['arithmetic_and_coverage_pass'] and summary['primary_trials'] == 120 and summary['exact_repeats'] == 24
refs = read(EXP / 'regression/frozen-five.json')
assert refs['pass_all'] and len(refs['trials']) == 5 and all(all(r['exact'].values()) for r in refs['trials'])
old = integrity()
save(EXP / 'regression/old-integrity-after.json', old)
status = 'PASS-CONDITIONAL-CREDIT-TRANSFER-AUDIT-NOT-LEARNING'
result = dict(status=status, **summary, old_integrity_pass=all(r['pass'] for r in old.values()),
frozen_five_reference_pass=refs['pass_all'], payload_bytes_before_manifest=budget(),
frozen_eligibility_and_mask_unchanged=True, base_weights_unchanged=True,
scientific_summary_sha256=digest(EXP / 'audit/scientific-summary.json'),
summary_all_144_baseline_raw_states_and_repeat_pair_metrics_reverified=True,
next_required_gate='Only if the diagnostic evidence supports it, separately preregister compartment-specific reinforcement and independent whole-brain evaluation; no automatic conditioning unlock')
save(EXP / 'audit/final-status.json', result)
with (EXP / 'REPORT.md').open('x', encoding='utf-8') as f:
f.write('# Phase 4AL — conditional credit/MBON transfer audit\n\nStatus: `' + status + '`.\n\n')
f.write('All 120 primary trials and 24 repeats are retained. Full events/counts/final V/g/ref baseline exactness is required.\n')
f.write('Virtual eligibility is sampled after tick 900; no DAN is injected, no delta is learned.\n')
f.write('Uniform 25% mask depression is a temporary conditional replay; every source train, including feedback, is clamped.\n')
f.write('All 60 odor pair-mode records and all 97 MBON targets are reported; no best pair/parameter selection.\n\n')
for name, block in summary['blocks'].items():
f.write(f"- {name}: specific KC/edge sets {block['pairs_with_both_specific_KC_and_edge_sets']}/15; conditional event change {block['conditional25_event_changed_trials']}/30; count/first-latency change {block['conditional25_count_or_first_latency_changed_trials']}/30.\n")
f.write('\nNo biological learning PASS or candidate. This does not complete the learning goal.\n')
f.write('Read EVIDENCE.md in the model for provenance, unmatched biology and conditional/closed-loop distinctions.\n')
check(); seal(MODEL); seal(EXP)
print(status, flush=True)
if __name__ == '__main__':
main()
@@ -0,0 +1,75 @@
{
"root": "/data/digital-drosophila/models/male-cns-lif/mb-credit-transfer-audit-v1",
"files": [
{
"path": "EVIDENCE.md",
"bytes": 12770,
"sha256": "cda2d810eada83ea33c96c1ce9d16102d1af22d1862bce84c47f0e7415863eca"
},
{
"path": "PREPARATION.md",
"bytes": 755,
"sha256": "8259428a56721ba8174039846623998ef11a1d8e9e14f123dfe0a0e4849c71ee"
},
{
"path": "config.json",
"bytes": 864,
"sha256": "b996e175731a920ad19e2c8989bd267cdbb71c14aa4558a8f1ef7486454c4b44"
},
{
"path": "context.py",
"bytes": 5478,
"sha256": "1626873baa224fab5d427a1fab1d6f840f14814152914ac4c2d6aa7a4f0bb1f6"
},
{
"path": "credit.py",
"bytes": 9109,
"sha256": "e03d3e1f64328058273e8364990e7a2c7f1527a31822ea7594a9e8914aa43497"
},
{
"path": "finalize.py",
"bytes": 2743,
"sha256": "9356587aa8811a3db50daee9bca22cf4114e06c6669cc80c69cd0dd2224b0130"
},
{
"path": "mbon_accounting.py",
"bytes": 18376,
"sha256": "825e9e60cb579479ddb2860a36f8dee0715b9b44d624cbad59ce0d279550d17f"
},
{
"path": "prepare.py",
"bytes": 9844,
"sha256": "9e7532bc4a564b0a0c0384ba2f3a7bfc0559c16bb256510dde7c97cce9036c0c"
},
{
"path": "run_audit.py",
"bytes": 9755,
"sha256": "3ece589f0f6ce1bff2cde94494e896a58fa4154106e613ee7e60549094304071"
},
{
"path": "summarize.py",
"bytes": 12067,
"sha256": "62432fe7b068eafae99396c4a9231adf8ef8e9d340ab64517f618fc794e7fd78"
},
{
"path": "test_credit.py",
"bytes": 12924,
"sha256": "4c473ce709ed9496b7ce1b5bedd6964c780715ff41dd54977489fbd2b99f998e"
},
{
"path": "test_integration.py",
"bytes": 3016,
"sha256": "23c4e9138b326e1a84fed88d771a6c2044a6abf72ecc7a1b829f604bcdde215d"
},
{
"path": "test_mbon_accounting.py",
"bytes": 9241,
"sha256": "d192dd357db96e628c21b9bcde617eaf6c6bc1efe5920ec55cc9cc1e4b549454"
},
{
"path": "verify_frozen.py",
"bytes": 3004,
"sha256": "85442fcefa93c64a2fac7f0a4b60ce505ddb9edab7a63cb0e25cb20f013d3850"
}
]
}
@@ -0,0 +1,267 @@
"""Read-only CPU accounting for the sealed AH replay callback.
Source/sign lanes are conditional, non-feedback bookkeeping, not additional
neurons, conductances, energies, or a whole-brain causal intervention. The
caller supplies anatomical groups and must independently verify the original
events/state and due/pending coverage. No files, Torch, or simulation entry
points are used here.
"""
from collections.abc import Mapping
import json
import numpy as np
DT_MS = .1
COEFF_KEYS = ("gd", "c0", "cg", "cv", "threshold", "reset", "seed_volt")
FLOAT_KEYS = ("old_v", "old_g", "v_integrated", "g_decayed", "v_threshold",
"v_after_reset", "g_after_reset", "v_pre_apl", "g_pre_apl", "delta_si", "v", "g")
INT_KEYS = ("ref_before", "ref_after_reset", "ref_pre_apl", "ref")
BOOL_KEYS = ("available", "request", "request_accepted", "request_rejected", "fired")
def require(value, message):
if not value:
raise ValueError(message)
def bit_equal(a, b):
return (a.dtype == b.dtype and a.shape == b.shape and
np.array_equal(np.ascontiguousarray(a).view(np.uint8), np.ascontiguousarray(b).view(np.uint8)))
class MBONAccounting:
"""callback(tick, stage); finish() -> (per_target, per_edge, JSON summary).
Group arrays have shape (N,G), sign lanes (N,G,2), with sign order positive,
negative. Negative increments/integrals retain their negative sign.
Histories use (T,N), or (T,N,G,2) for history_group_g. Target indices are
incoming.dst_local, not global IDs; the caller joins the verified target
list. Float64 lanes retain their own canonical additions; summing lanes
is not the runtime's interleaved accumulation, so residuals are reported
without correcting them or assigning them to an anatomical source.
"""
def __init__(self, incoming, groups, group_names, coeff, steps):
require(isinstance(steps, (int, np.integer)) and not isinstance(steps, (bool, np.bool_))
and 0 < steps <= np.iinfo(np.int32).max, "steps must be a positive int32-range integer")
require(isinstance(group_names, tuple) and group_names and
all(isinstance(x, str) and x for x in group_names) and len(set(group_names)) == len(group_names),
"group_names must be a nonempty tuple of unique names")
require(isinstance(coeff, Mapping), "coeff must be a mapping")
self.coeff = {}
for key in COEFF_KEYS:
value = coeff.get(key)
require(not isinstance(value, (bool, np.bool_)) and
isinstance(value, (int, float, np.integer, np.float64)) and np.isfinite(value),
"Missing/non-double/nonfinite coefficient: " + key)
self.coeff[key] = np.float64(value)
require(self.coeff["seed_volt"] > self.coeff["threshold"], "seed_volt must exceed threshold")
require("dt_ms" not in coeff or coeff["dt_ms"] == DT_MS, "AH time step is fixed at .1 ms")
require(isinstance(incoming.target_count, (int, np.integer)) and 0 < incoming.target_count <= 166700,
"Invalid incoming target count")
self.n, self.steps, self.names = int(incoming.target_count), int(steps), group_names
self.e = len(incoming.edge_id)
for name, dtype in (("src_global", "int32"), ("dst_local", "int32"),
("edge_id", "int64"), ("weights", "float64")):
value = np.asarray(getattr(incoming, name))
require(value.dtype == np.dtype(dtype) and value.shape == (self.e,), "Invalid incoming " + name)
setattr(self, name, value.copy())
require(np.all((self.src_global >= 0) & (self.src_global < 166700)) and
np.all(self.src_global[1:] >= self.src_global[:-1]), "Noncanonical incoming sources")
require(np.all(self.edge_id >= 0) and np.all(self.edge_id[1:] > self.edge_id[:-1]), "Noncanonical edge IDs")
require(np.all((self.dst_local >= 0) & (self.dst_local < self.n)) and
np.isfinite(self.weights).all(), "Invalid incoming destination/weight")
codes = np.asarray(groups)
require(codes.dtype == np.int32 and codes.shape == (self.e,) and
np.all((codes >= 0) & (codes < len(self.names))), "groups must be int32 per incoming row")
self.groups = codes.copy()
self.ticks, self.finished, self.previous = 0, False, None
shape = (self.n, len(self.names))
self.counts = {key: np.zeros(shape, np.int64) for key in
("due_count", "accepted_count", "rejected_count", "accepted_zero_count", "rejected_zero_count")}
self.increments = {key: np.zeros(shape, np.float64) for key in
("accepted_positive_V", "accepted_negative_V", "rejected_positive_V", "rejected_negative_V")}
self.edge_due = np.zeros(self.e, np.int64)
self.edge_accepted = np.zeros(self.e, np.int64)
self.spike_count = np.zeros(self.n, np.int32)
self.first = np.full(self.n, -1, np.int32)
self.requests = {key: np.zeros(self.n, np.int64) for key in
("request_count", "request_accepted_count", "request_rejected_count")}
self.sg = np.zeros(shape + (2,), np.float64)
self.sv = np.zeros_like(self.sg)
self.bg = np.zeros(self.n, np.float64)
self.bv = np.zeros(self.n, np.float64)
self.integral_g = np.zeros_like(self.sg)
self.integral_v = np.zeros_like(self.sg)
self.max_g_residual = np.zeros(self.n, np.float64)
self.max_v_residual = np.zeros(self.n, np.float64)
self.history_v = np.empty((self.steps, self.n), np.float64)
self.history_g = np.empty_like(self.history_v)
self.history_ref = np.empty((self.steps, self.n), np.int32)
self.history_group_g = np.empty((self.steps,) + self.sg.shape, np.float64)
self.history_baseline_g = np.empty_like(self.history_v)
self.history_g_residual = np.empty_like(self.history_v)
def _validate(self, tick, s):
require(not self.finished and isinstance(tick, (int, np.integer)) and
not isinstance(tick, (bool, np.bool_)) and tick == self.ticks and tick < self.steps,
"Missing, duplicate or out-of-order callback")
require(isinstance(s, Mapping), "stage must be a mapping")
for keys, dtype in ((FLOAT_KEYS, np.float64), (INT_KEYS, np.int32), (BOOL_KEYS, np.bool_)):
for key in keys:
value = s.get(key)
require(isinstance(value, np.ndarray) and value.shape == (self.n,) and value.dtype == dtype
and np.isfinite(value).all(), "Missing/invalid stage field: " + key)
h = s.get("h32")
require(isinstance(h, np.ndarray) and h.dtype == np.float32 and h.shape == (self.n,)
and np.all(h == 0) and np.all(s["delta_si"] == 0), "MBON requires zero graded APL h/delta")
require(np.all((s["ref_before"] >= 0) & (s["ref_before"] <= 22)), "Invalid refractory value")
if self.previous is not None:
require(all(bit_equal(s[a], self.previous[b]) for a, b in
(("old_v", "v"), ("old_g", "g"), ("ref_before", "ref"))), "Discontinuous observed state")
else:
require(np.all(s["ref_before"] == 0), "AH replay starts nonrefractory")
ref = np.maximum(s["ref_before"] - np.int32(1), np.int32(0))
available = ref == 0
require(np.array_equal(s["available"], available), "Availability mismatch")
require(np.array_equal(s["request_accepted"], s["request"] & available) and
np.array_equal(s["request_rejected"], s["request"] & ~available), "Request guard mismatch")
c = self.coeff
v, g = s["old_v"].copy(), s["old_g"].copy()
g[available] = np.multiply(c["gd"], s["old_g"][available])
v[available] = np.add(c["c0"], np.add(np.multiply(c["cg"], s["old_g"][available]),
np.multiply(c["cv"], s["old_v"][available])))
require(bit_equal(v, s["v_integrated"]) and bit_equal(g, s["g_decayed"]), "Integration mismatch")
v[s["request_accepted"]] = c["seed_volt"]
require(bit_equal(v, s["v_threshold"]), "Request override mismatch")
fired = available & (v > c["threshold"])
require(np.array_equal(s["fired"], fired), "Strict threshold mismatch")
v[fired], g[fired], ref[fired] = c["reset"], np.float64(0), np.int32(22)
for key, value in (("v_after_reset", v), ("g_after_reset", g), ("ref_after_reset", ref)):
require(bit_equal(s[key], value), "Reset mismatch: " + key)
due, accepted = s.get("due_edge_indices"), s.get("arrival_accepted")
require(isinstance(due, np.ndarray) and due.dtype == np.int64 and due.ndim == 1 and
np.all((due >= 0) & (due < self.e)) and np.all(due[1:] > due[:-1]), "Noncanonical due edge rows")
require(isinstance(s.get("due_edge_id"), np.ndarray) and
bit_equal(s["due_edge_id"], self.edge_id[due]), "Due edge-ID mismatch")
require(isinstance(accepted, np.ndarray) and accepted.dtype == np.bool_ and accepted.shape == due.shape
and np.array_equal(accepted, ref[self.dst_local[due]] == 0), "Arrival acceptance mismatch")
require(tick >= 18 or not len(due), "Arrival before delay 18")
rows = due[accepted]
np.add.at(g, self.dst_local[rows], self.weights[rows])
for key, value in (("g_pre_apl", g), ("v_pre_apl", v), ("ref_pre_apl", ref), ("v", v), ("ref", ref)):
require(bit_equal(s[key], value), "Arrival/final mismatch: " + key)
require(bit_equal(s["g"], np.subtract(g, s["delta_si"])), "Final g mismatch")
return due, accepted
def _residual(self, voltage, g):
# No repair: anatomical lanes are never contaminated with roundoff.
vr = voltage - np.add(self.bv, self.sv.sum(axis=(1, 2)))
gr = g - np.add(self.bg, self.sg.sum(axis=(1, 2)))
self.max_v_residual = np.maximum(self.max_v_residual, np.abs(vr))
self.max_g_residual = np.maximum(self.max_g_residual, np.abs(gr))
return gr
def __call__(self, tick, s):
due, accepted = self._validate(tick, s)
if self.previous is None:
self.bv, self.bg = s["old_v"].copy(), s["old_g"].copy()
c, available, fired = self.coeff, s["available"], s["fired"]
self.sv[available] = np.add(np.multiply(c["cg"], self.sg[available]),
np.multiply(c["cv"], self.sv[available]))
self.bv[available] = np.add(c["c0"], np.add(np.multiply(c["cg"], self.bg[available]),
np.multiply(c["cv"], self.bv[available])))
self.sg[available] = np.multiply(c["gd"], self.sg[available])
self.bg[available] = np.multiply(c["gd"], self.bg[available])
self._residual(s["v_integrated"], s["g_decayed"])
req = s["request_accepted"]
self.sv[req], self.bv[req] = 0., c["seed_volt"]
self._residual(s["v_threshold"], s["g_decayed"])
self.sv[fired], self.sg[fired] = 0., 0.
self.bv[fired], self.bg[fired] = c["reset"], 0.
self.first[(self.first < 0) & fired] = np.int32(tick)
self.spike_count += fired.astype(np.int32)
for key, stage_key in (("request_count", "request"), ("request_accepted_count", "request_accepted"),
("request_rejected_count", "request_rejected")):
self.requests[key] += s[stage_key].astype(np.int64)
dst, codes, weights = self.dst_local[due], self.groups[due], self.weights[due]
self.edge_due[due] += 1
self.edge_accepted[due[accepted]] += 1
for key, keep in (("due_count", np.ones(len(due), bool)), ("accepted_count", accepted),
("rejected_count", ~accepted), ("accepted_zero_count", accepted & (weights == 0)),
("rejected_zero_count", ~accepted & (weights == 0))):
np.add.at(self.counts[key], (dst[keep], codes[keep]), 1)
for key, keep in (("accepted_positive_V", accepted & (weights > 0)),
("accepted_negative_V", accepted & (weights < 0)),
("rejected_positive_V", ~accepted & (weights > 0)),
("rejected_negative_V", ~accepted & (weights < 0))):
np.add.at(self.increments[key], (dst[keep], codes[keep]), weights[keep])
nonzero = accepted & (weights != 0)
# add.at is sequential for repeated lane indices, preserving every
# lane's relative canonical edge order. Never pre-sum edge weights.
np.add.at(self.sg, (dst[nonzero], codes[nonzero], (weights[nonzero] < 0).astype(np.int32)), weights[nonzero])
gr = self._residual(s["v"], s["g"])
self.integral_g += np.multiply(self.sg, np.float64(DT_MS))
self.integral_v += np.multiply(self.sv, np.float64(DT_MS))
for value in (self.sg, self.sv, self.bg, self.bv, self.integral_g, self.integral_v,
self.max_v_residual, self.max_g_residual, *self.increments.values()):
require(np.isfinite(value).all(), "Nonfinite source accounting")
self.history_v[tick], self.history_g[tick], self.history_ref[tick] = s["v"], s["g"], s["ref"]
self.history_group_g[tick] = self.sg
self.history_baseline_g[tick], self.history_g_residual[tick] = self.bg, gr
self.previous = {key: s[key].copy() for key in ("v", "g", "ref")}
self.ticks += 1
def finish(self):
require(not self.finished and self.ticks == self.steps and self.previous is not None,
"Observer must cover exactly all ticks before finish")
require(np.array_equal(self.counts["due_count"], self.counts["accepted_count"] + self.counts["rejected_count"]),
"Group count conservation mismatch")
for key, edge in (("due_count", self.edge_due), ("accepted_count", self.edge_accepted)):
grouped = np.zeros_like(self.counts[key])
np.add.at(grouped, (self.dst_local, self.groups), edge)
require(np.array_equal(grouped, self.counts[key]), "Per-edge/group count mismatch")
data = {"target_local": np.arange(self.n, dtype=np.int32), "spike_count": self.spike_count.copy(),
"first_spike_tick": self.first.copy(), **{key: value.copy() for key, value in self.requests.items()},
**{key: value.copy() for key, value in self.counts.items()},
**{key: value.copy() for key, value in self.increments.items()},
**{"final_" + key: value.copy() for key, value in self.previous.items()},
"final_group_g": self.sg.copy(), "final_group_v": self.sv.copy(),
"g_integral_V_ms": self.integral_g.sum(axis=2), "g_sign_integral_V_ms": self.integral_g.copy(),
"v_sign_integral_V_ms": self.integral_v.copy(),
"max_g_lane_residual_V": self.max_g_residual.copy(), "max_v_lane_residual_V": self.max_v_residual.copy(),
"history_v": self.history_v.copy(), "history_g": self.history_g.copy(), "history_ref": self.history_ref.copy(),
"history_group_g": self.history_group_g.copy(), "history_baseline_g": self.history_baseline_g.copy(),
"history_g_residual": self.history_g_residual.copy()}
edges = {"edge_id": self.edge_id.copy(), "source_neuron_idx": self.src_global.copy(),
"target_local": self.dst_local.copy(), "group": self.groups.copy(), "weight_V": self.weights.copy(),
"due_count": self.edge_due.copy(), "accepted_count": self.edge_accepted.copy(),
"rejected_count": self.edge_due - self.edge_accepted}
groups = {name: {**{key: int(value[:, gi].sum()) for key, value in self.counts.items()},
**{key: float(value[:, gi].sum()) for key, value in self.increments.items()},
"g_integral_V_ms": float(self.integral_g[:, gi].sum()),
"g_positive_integral_V_ms": float(self.integral_g[:, gi, 0].sum()),
"g_negative_integral_V_ms": float(self.integral_g[:, gi, 1].sum())}
for gi, name in enumerate(self.names)}
summary = {"complete": True, "pass_all": True,
"pass_all_scope": "Observer callback coverage and internal count/state-accounting checks only",
"steps": self.steps, "callback_count": self.ticks, "targets": self.n, "incoming_edges": self.e,
"group_names": list(self.names), "sign_order": ["positive", "negative"], "dt_ms": DT_MS,
"due_count": int(self.edge_due.sum()), "accepted_count": int(self.edge_accepted.sum()),
"rejected_count": int((self.edge_due - self.edge_accepted).sum()), "groups": groups,
"max_g_lane_residual_V": float(self.max_g_residual.max(initial=0.)),
"max_v_lane_residual_V": float(self.max_v_residual.max(initial=0.)),
"residual_policy": "Observed state minus uncorrected float64 lane sum; no fitted tolerance or anatomical reassignment",
"integral_contract": "Sum of end-of-tick source g or v lanes times dt_ms; units V*ms, not conductance or energy",
"increment_contract": "Nominal accepted/rejected signed per-edge increments; negative fields retain negative sign",
"lane_contract": "Observed availability controls decay/integration; observed request/reset clears voltage/source history; fired clears g; canonical accepted adds only",
"pending_coverage_checked_by_observer": False, "original_events_verified_by_observer": False,
"caller_must_verify_original_events_final_and_queue_coverage": True,
"nonfeedback": True, "parameter_changes": False, "learning": False,
"functional_transfer_claim": False, "whole_brain_intervention_claim": False}
# Includes aggregate overflows: a finite per-target array alone must
# never permit an Infinity/NaN JSON report with pass_all=True.
json.dumps(summary, allow_nan=False)
self.finished = True
return data, edges, summary
@@ -0,0 +1,136 @@
"""Preregister all AK trials before credit/MBON outcomes; never train."""
from datetime import datetime, timezone
import platform
import shutil
import subprocess
import sys
import numpy as np
import pandas as pd
import yaml
from context import (ROOT, MODEL, EXP, AK, H, AG, META, CORE, COEFF, CFG, PINS,
read, save, digest, sources, integrity, write_npz)
def main():
assert not EXP.exists() or (EXP.is_dir() and not any(EXP.iterdir())), 'Populated experiment exists: preserve it, do not regenerate'
assert shutil.disk_usage(EXP.parent).free > CFG['output_budget_bytes'] + 10 * 1024**3
for relative, expected in PINS.items():
assert digest(ROOT / relative) == expected, relative
assert read(AK / 'audit/final-status.json')['status'] == 'PASS-FROZEN-D18-INDEPENDENT-VALIDATION'
assert not CFG['training'] and not CFG['candidate_generation']
jc = yaml.safe_load((ROOT / 'models/male-cns-lif/mb-physiology-v1/phase4j.yaml').read_text())
j = jc['eligibility_v2']
assert j['kc_trace_tau_ms'] == CFG['kc_trace_tau_ms'] and j['trace_cap'] == CFG['trace_cap']
assert j['max_fractional_depression'] == CFG['conditional_depression_fraction'] == .25
assert jc['direct_kc']['dan_time_ms'] / CFG['dt_ms'] == CFG['virtual_gate_tick']
inventory = read(AK / 'protocol/validation-cases.json')
assert len(inventory) == CFG['primary_trials']
seeds = sorted({r['seed'] for r in inventory})
assert len(seeds) == 5
planned = []
for row in inventory:
for repeat in (0, 1) if row['seed'] == seeds[0] else (0,):
planned.append(dict(row, repeat=repeat))
assert len(planned) == CFG['primary_trials'] + CFG['technical_repeats']
inputs = dict(PINS)
h_manifest = {line.split(' ', 1)[1]: line.split(' ', 1)[0]
for line in (H / 'manifests/checksums.sha256').read_text().splitlines()}
static_h = ('plastic-mask/plastic-edge-candidates.parquet', 'plastic-mask/plastic-edge-ids.int64.npy',
'plastic-mask/plastic-edge-mask.bool.npy', 'mappings/dan-to-mbon-confirmed.parquet',
'mappings/mbon-to-compartment.parquet', 'mappings/kc-to-mbon-actual-edges.parquet')
for rel in static_h:
assert digest(H / rel) == h_manifest[rel]
inputs[(H / rel).relative_to(ROOT).as_posix()] = h_manifest[rel]
for row in planned:
case = AK / 'cases' / row['cohort'] / row['name']
trial = AK / 'gpu' / row['cohort'] / (row['name'] + '-r' + str(row['repeat']))
for p in (case / 'spec.json', case / 'requests.npz', trial / 'state.npz', trial / 'summary.json'):
inputs[p.relative_to(ROOT).as_posix()] = digest(p)
paths = [COEFF] + 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'
for p in paths + [raw_path]:
inputs[p.relative_to(ROOT).as_posix()] = digest(p)
for name in ('protocol', 'trials', 'audit', 'regression', 'logs', 'pairs'):
(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)
assert test.returncode == 0, 'Unit tests failed; preserve incomplete experiment'
assert 'skipped=' not in test.stderr, 'Server arithmetic comparison must not be skipped'
save(EXP / 'protocol/preregistered.json', dict(timestamp_utc=datetime.now(timezone.utc).isoformat(),
sources=sources(), config=CFG, 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(),
planned=planned, prior_AK_representation_known=True, before_new_credit_and_MBON_outcomes=True,
training=False, learning_achieved=False, virtual_gate_is_dopamine_measurement=False,
MBON_recurrent_and_all_other_presynaptic_events_clamped=True,
expected_functional_effect_not_a_preregistered_learning_gate=True,
python=sys.version, platform=platform.platform(), numpy=np.__version__))
save(EXP / 'regression/old-integrity-before.json', integrity())
meta = pd.read_parquet(META).sort_values('neuron_idx').reset_index(drop=True)
assert np.array_equal(meta.neuron_idx, np.arange(CFG['neurons'], dtype=np.int32))
kc = np.flatnonzero(meta.annotation_class.eq('Kenyon_Cell')).astype(np.int32)
mbon = np.flatnonzero(meta.annotation_class.eq('MBON')).astype(np.int32)
apl = np.flatnonzero(meta.annotation_type.eq('APL')).astype(np.int32)
assert len(kc) == 4064 and len(mbon) == 97
assert np.array_equal(kc, np.load(AG / 'apl-bundle/kc_globals.npy'))
assert np.array_equal(apl, np.load(AG / 'apl-bundle/apl_globals.npy'))
folder = AG / 'graphs/full'
arrays = {k: np.load(folder / (k + '.npy'), mmap_mode='r') for k in ('nodes', 'src', 'dst', 'edge_id', 'weights')}
assert np.array_equal(arrays['nodes'], np.arange(CFG['neurons'], dtype=np.int32))
assert np.array_equal(arrays['edge_id'], np.arange(CFG['edges'], dtype=np.int64))
raw = np.load(raw_path, mmap_mode='r')
candidate = pd.read_parquet(H / static_h[0])
ids = np.load(H / static_h[1]); mask = np.load(H / static_h[2])
assert ids.dtype == np.int64 and ids.shape == (CFG['candidate_edges'],)
assert np.array_equal(candidate.edge_id, ids) and np.array_equal(np.flatnonzero(mask), ids)
for prefix, key in (('source', 'src'), ('target', 'dst')):
assert np.array_equal(candidate[prefix + '_neuron_idx'], arrays[key][ids])
assert np.array_equal(candidate[prefix + '_body_id'], meta.iloc[arrays[key][ids]].body_id)
assert np.array_equal(candidate.raw_synapse_count, raw[ids])
assert np.isin(candidate.source_neuron_idx, kc).all() and np.isin(candidate.target_neuron_idx, mbon).all()
assert (candidate.plasticity_polarity == -1).all() and candidate.target_compartment_verbatim.notna().all()
confirmed = pd.read_parquet(H / static_h[3])
assert confirmed.exact_compartment_match.all()
assert np.array_equal(confirmed.source_compartment_verbatim, confirmed.target_compartment_verbatim)
for row in confirmed.itertuples():
assert arrays['src'][row.edge_id] == row.source_neuron_idx and arrays['dst'][row.edge_id] == row.target_neuron_idx
assert raw[row.edge_id] == row.raw_synapse_count and meta.iloc[row.source_neuron_idx].annotation_class == 'DAN'
assert set(candidate.target_neuron_idx).issubset(set(confirmed.target_neuron_idx))
compartments = pd.read_parquet(H / static_h[4]).sort_values('neuron_idx').reset_index(drop=True)
assert np.array_equal(compartments.neuron_idx, mbon)
compartments.to_parquet(EXP / 'protocol/mbon-metadata.parquet', index=False)
candidate.to_parquet(EXP / 'protocol/candidate-static.parquet', index=False)
lookup = np.full(CFG['neurons'], -1, np.int32); lookup[mbon] = np.arange(len(mbon), dtype=np.int32)
selected = np.flatnonzero(lookup[arrays['dst']] >= 0)
src, dst = arrays['src'][selected], lookup[arrays['dst'][selected]]
weights = arrays['weights'][selected].copy()
assert weights.dtype == np.float64 and raw.dtype == np.int64
weights[np.isin(src, apl)] = 0.0
is_candidate = mask[selected]
assert int(is_candidate.sum()) == CFG['candidate_edges'] and (weights[is_candidate] > 0).all()
groups = np.full(len(selected), CFG['groups'].index('other'), np.int32)
for name, chosen in (('nonplastic_KC', np.isin(src, kc)), ('DAN', meta.iloc[src].annotation_class.eq('DAN').to_numpy()),
('MBON', np.isin(src, mbon)), ('APL', np.isin(src, apl)), ('plastic_KC', is_candidate)):
groups[chosen] = CFG['groups'].index(name)
kcall = pd.read_parquet(H / static_h[5])
assert np.array_equal(selected[np.isin(src, kc)], kcall.edge_id.to_numpy())
assert np.array_equal(raw[kcall.edge_id.to_numpy()], kcall.raw_synapse_count.to_numpy())
write_npz(EXP / 'protocol/incoming.npz', src_global=src, dst_local=dst, edge_id=selected.astype(np.int64),
weights=weights, raw_contacts=raw[selected], groups=groups, is_candidate=is_candidate,
targets=mbon, kc_globals=kc, edge_source_local=np.searchsorted(kc, candidate.source_neuron_idx).astype(np.int32))
save(EXP / 'protocol/graph-audit.json', dict(neurons=len(meta), all_edges=CFG['edges'], KC=len(kc), MBON=len(mbon),
all_MBON_incoming_edges=len(selected), all_KC_MBON_edges=len(kcall), candidate_edges=len(ids),
candidate_targets=int(candidate.target_neuron_idx.nunique()), confirmed_DAN_MBON_edges=len(confirmed),
compartments={str(c): dict(edges=len(g), targets=int(g.target_neuron_idx.nunique()), sources=int(g.source_neuron_idx.nunique()))
for c, g in candidate.groupby('target_compartment_verbatim')},
fast_APL_incoming_edges_zeroed=int(np.isin(src, apl).sum()), groups={name: int((groups == i).sum()) for i, name in enumerate(CFG['groups'])},
mask_all_edges_identity_verified=True, mapping_basis='Frozen explicit annotation token + real DAN-to-MBON edge; not synapse ROI',
feedback_neuron_class_not_inferred=True, weight_units='float64 SI volts; not measured conductance'))
save(EXP / 'protocol/prepared-hashes.json', [dict(path=p.relative_to(EXP).as_posix(), bytes=p.stat().st_size, sha256=digest(p))
for p in sorted((EXP / 'protocol').iterdir()) if p.is_file() and p.name != 'prepared-hashes.json'])
print('PREREGISTERED 120 primary +24 repeats; static edge identities validated; no new trajectory or training', flush=True)
if __name__ == '__main__':
main()
@@ -0,0 +1,160 @@
"""CPU-only read-only baseline and one predeclared conditional sensitivity."""
from concurrent.futures import ProcessPoolExecutor, as_completed
from functools import lru_cache
import time
import traceback
import numpy as np
from context import (ROOT, EXP, AK, CORE, COEFF, CFG, PINS, read, save, digest,
load_module, check, budget, bit_equal, write_npz)
from credit import replay_credit
from mbon_accounting import MBONAccounting
@lru_cache(maxsize=1)
def prepared():
core = load_module('sealed_ah_core_al', CORE, PINS[CORE.relative_to(ROOT).as_posix()])
with np.load(EXP / 'protocol/incoming.npz', allow_pickle=False) as f:
data = {k: f[k].copy() for k in f.files}
incoming = core.IncomingIndex(data['src_global'], data['dst_local'], data['edge_id'], data['weights'], len(data['targets']))
altered = data['weights'].copy()
altered[data['is_candidate']] *= np.float64(1.0 - CFG['conditional_depression_fraction'])
assert bit_equal(data['weights'][~data['is_candidate']], altered[~data['is_candidate']])
assert np.array_equal(np.sign(data['weights']), np.sign(altered))
depressed = core.IncomingIndex(data['src_global'], data['dst_local'], data['edge_id'], altered, len(data['targets']))
for array in data.values():
array.setflags(write=False)
return core, data, incoming, depressed, read(COEFF)
def compare_results(base, changed, base_history, changed_history):
event_equal = all(bit_equal(base[k], changed[k]) for k in ('step', 'neuron_idx'))
count_changes = base['counts'] != changed['counts']
latency_changes = base['first_spike'] != changed['first_spike']
return dict(event_sequence_changed=not event_equal,
spike_count_L1=int(np.abs(base['counts'].astype(np.int64) - changed['counts']).sum()),
neurons_with_count_change=int(count_changes.sum()), neurons_with_first_latency_change=int(latency_changes.sum()),
baseline_spikes=int(base['counts'].sum()), depressed_spikes=int(changed['counts'].sum()),
max_abs_V_difference_SI=float(np.max(np.abs(base_history['history_v'] - changed_history['history_v']))),
max_abs_g_difference_SI=float(np.max(np.abs(base_history['history_g'] - changed_history['history_g']))),
final_V_max_abs_difference_SI=float(np.max(np.abs(base['v'] - changed['v']))),
final_g_max_abs_difference_SI=float(np.max(np.abs(base['g'] - changed['g']))),
functional_count_or_first_latency_change=bool(count_changes.any() or latency_changes.any()),
result_scope='Conditional clamped-upstream replay only; all target threshold/reset/ref recalculated; not whole-brain or learning')
def audit_trial(row, stage):
start = time.perf_counter()
key = row['name'] + '-r' + str(row['repeat'])
out = EXP / 'trials' / key
assert stage in ('baseline', 'conditional25')
if stage == 'baseline':
out.mkdir(exist_ok=False)
else:
assert read(EXP / 'audit/baselines-complete.json')['pass_all'], 'All 144 baseline replays must pass first'
assert out.is_dir() and not (out / 'summary.json').exists()
case = AK / 'cases' / row['cohort'] / row['name']
trial = AK / 'gpu' / row['cohort'] / key
spec = read(case / 'spec.json')
assert spec['steps'] == CFG['steps'] and spec['dt_ms'] == CFG['dt_ms'] and spec['apl_delay_steps'] == 18
assert all(spec[k] == row[k] for k in ('name', 'seed', 'mode', 'odor_id'))
assert read(trial / 'summary.json')['complete'] and read(trial / 'summary.json')['pass_all']
with np.load(trial / 'state.npz', allow_pickle=False) as f:
state = {k: f[k].copy() for k in f.files}
assert set(state) == {'step', 'neuron_idx', 'counts', 'v', 'g', 'ref'}
with np.load(case / 'requests.npz', allow_pickle=False) as f:
requests = {k: f[k].copy() for k in f.files}
core, data, incoming, depressed, coeff = prepared()
targets, steps = data['targets'], spec['steps']
assert not np.isin(requests['neuron_idx'], targets).any(), 'Unexpected MBON injection: do not reinterpret it'
v0 = np.full(len(targets), spec['model']['resting_potential_mv'], np.float64) * np.float64(.001)
g0 = np.zeros(len(targets), np.float64)
events = dict(step=state['step'], neuron_idx=state['neuron_idx'])
if stage == 'baseline':
observer = MBONAccounting(incoming, data['groups'], tuple(CFG['groups']), coeff, steps)
result = core.replay(v0, g0, coeff, steps, targets, requests, events, incoming,
np.zeros((steps, len(targets)), np.float32), observer)
selected = np.isin(state['neuron_idx'], targets)
exact = {k: bit_equal(result[k], state[k][targets]) for k in ('counts', 'v', 'g', 'ref')}
exact.update({k: bit_equal(result[k], state[k][selected]) for k in ('step', 'neuron_idx')})
assert all(exact.values()), 'Baseline MBON divergence: ' + str(exact)
bt, be, bs = observer.finish()
write_npz(out / 'baseline-replay.npz', **result)
write_npz(out / 'baseline-target-accounting.npz', **bt)
pending = np.zeros(len(incoming.edge_id), np.int64)
for tick in range(steps - 18, steps):
due = incoming.due(state['neuron_idx'][state['step'] == tick])
pending[due] += 1
be['pending_after_window_count'] = pending
# Independent source-spike multiplicity covers due plus pending events.
source_counts = np.bincount(state['neuron_idx'], minlength=CFG['neurons'])
assert np.array_equal(be['due_count'] + pending, source_counts[incoming.src_global])
write_npz(out / 'baseline-edge-accounting.npz', **be)
save(out / 'baseline-accounting.json', bs)
trace = replay_credit(state['step'], state['neuron_idx'], data['kc_globals'],
steps=steps, gate_tick=CFG['virtual_gate_tick'], dt_ms=CFG['dt_ms'],
tau_ms=CFG['kc_trace_tau_ms'], cap=CFG['trace_cap'])
arrays = {k: value for k, value in trace.items() if isinstance(value, np.ndarray)}
write_npz(out / 'credit.npz', **arrays)
save(out / 'credit.json', {k: value for k, value in trace.items() if not isinstance(value, np.ndarray)})
report = dict(**row, pass_all=True, baseline_exact=exact, due_plus_pending_source_events_exact=True,
CPU_wall_seconds=time.perf_counter() - start)
save(out / 'baseline-summary.json', report)
return report
with np.load(out / 'baseline-replay.npz', allow_pickle=False) as f:
result = {k: f[k].copy() for k in f.files}
with np.load(out / 'baseline-target-accounting.npz', allow_pickle=False) as f:
bt = {k: f[k].copy() for k in f.files}
baseline_report = read(out / 'baseline-summary.json')
assert baseline_report['pass_all'] and all(baseline_report['baseline_exact'].values())
exact = baseline_report['baseline_exact']
d_observer = MBONAccounting(depressed, data['groups'], tuple(CFG['groups']), coeff, steps)
changed = core.replay(v0, g0, coeff, steps, targets, requests, events, depressed,
np.zeros((steps, len(targets)), np.float32), d_observer)
dt, de, ds = d_observer.finish()
write_npz(out / 'conditional25-replay.npz', **changed)
write_npz(out / 'conditional25-target-accounting.npz', **dt)
write_npz(out / 'conditional25-edge-accounting.npz', **de)
save(out / 'conditional25-accounting.json', ds)
sensitivity = compare_results(result, changed, bt, dt)
save(out / 'sensitivity.json', sensitivity)
report = dict(**row, pass_all=True, baseline_exact=exact, sensitivity=sensitivity,
credit_gate='Virtual tick900 post-update; no DAN injection or plastic update',
biological_duration_ms=spec['duration_ms'], CPU_wall_seconds=time.perf_counter() - start,
baseline_CPU_wall_seconds=baseline_report['CPU_wall_seconds'],
source_sha256={f: digest(trial / f) for f in ('state.npz', 'summary.json')},
conditional_weight_copy_only=True, training=False, candidate_generated=False)
save(out / 'summary.json', report)
save(out / 'output-hashes.json', [dict(path=p.name, bytes=p.stat().st_size, sha256=digest(p))
for p in sorted(out.iterdir()) if p.is_file()])
return report
def main():
reg = check()
assert not any((EXP / 'trials').iterdir()), 'No automatic resume/overwrite of diagnostic outputs'
complete = []
try:
for stage in ('baseline', 'conditional25'):
complete = []
with ProcessPoolExecutor(max_workers=CFG['workers']) as pool:
futures = {pool.submit(audit_trial, row, stage): row for row in reg['planned']}
for task in as_completed(futures):
result = task.result()
complete.append(dict(name=result['name'], repeat=result['repeat'], pass_all=True))
budget()
print(stage, len(complete), result['name'], result['repeat'],
round(result['CPU_wall_seconds'], 3), 'CPU s', flush=True)
assert len(complete) == 144
if stage == 'baseline':
save(EXP / 'audit/baselines-complete.json', dict(pass_all=True, trials=complete, primary=120, repeats=24))
except Exception as error:
save(EXP / 'audit/failure.json', dict(error=type(error).__name__, message=str(error),
traceback=traceback.format_exc(), stage=stage, completed=complete))
raise
assert len(complete) == 144
save(EXP / 'audit/replays-complete.json', dict(pass_all=True, trials=complete, primary=120, repeats=24,
meaning='All exact conditional baseline reconstructions; not an overall biological or learning PASS'))
if __name__ == '__main__':
main()
@@ -0,0 +1,177 @@
"""All predeclared pairs and all trial outputs; never choose a winning odor."""
from itertools import combinations
import numpy as np
import pandas as pd
from context import EXP, AK, CFG, read, save as _save, digest, bit_equal, write_npz as _write_npz
from credit import pair_identity
def load_arrays(path):
with np.load(path, allow_pickle=False) as data:
return {key: data[key].copy() for key in data.files}
def metrics(values):
values = np.asarray(values, np.float64)
if not len(values):
return dict(min=None, mean=None, max=None, undefined_reason='No defined denominator observations')
return dict(min=float(values.min()), mean=float(values.mean()), max=float(values.max()))
def primary_rows(reg):
rows = [r for r in reg['planned'] if r['repeat'] == 0]
assert len(rows) == CFG['primary_trials'] and len({r['name'] for r in rows}) == len(rows)
return rows
def run(verify_only=False):
def save(path, value):
if verify_only:
assert read(path) == value, 'Summary JSON differs from recomputed evidence: ' + str(path)
else:
_save(path, value)
def write_npz(path, **value):
if verify_only:
old = load_arrays(path)
assert set(old) == set(value) and all(bit_equal(old[k], value[k]) for k in old)
else:
_write_npz(path, **value)
reg = read(EXP / 'protocol/preregistered.json')
assert read(EXP / 'audit/replays-complete.json')['pass_all']
data = load_arrays(EXP / 'protocol/incoming.npz')
for row in reg['planned']:
folder = EXP / 'trials' / (row['name'] + '-r' + str(row['repeat']))
hashes = read(folder / 'output-hashes.json')
assert {r['path'] for r in hashes} == {p.name for p in folder.iterdir() if p.name != 'output-hashes.json'}
for entry in hashes:
assert digest(folder / entry['path']) == entry['sha256']
original = load_arrays(AK / 'gpu' / row['cohort'] / (row['name'] + '-r' + str(row['repeat'])) / 'state.npz')
base = load_arrays(folder / 'baseline-replay.npz')
selected = np.isin(original['neuron_idx'], data['targets'])
assert all(bit_equal(base[k], original[k][data['targets']]) for k in ('v', 'g', 'ref', 'counts'))
assert all(bit_equal(base[k], original[k][selected]) for k in ('step', 'neuron_idx'))
candidate = pd.read_parquet(EXP / 'protocol/candidate-static.parquet')
target_meta = pd.read_parquet(EXP / 'protocol/mbon-metadata.parquet')
primary = primary_rows(reg)
repeats = []
for row in reg['planned']:
if row['repeat'] != 1:
continue
paths = [EXP / 'trials' / (row['name'] + '-r' + str(i)) for i in (0, 1)]
checks = {}
for filename in ('baseline-replay.npz', 'baseline-target-accounting.npz', 'baseline-edge-accounting.npz',
'conditional25-replay.npz', 'conditional25-target-accounting.npz', 'conditional25-edge-accounting.npz', 'credit.npz'):
a, b = (load_arrays(p / filename) for p in paths)
checks[filename] = set(a) == set(b) and all(bit_equal(a[k], b[k]) for k in a)
for filename in ('baseline-accounting.json', 'conditional25-accounting.json', 'credit.json', 'sensitivity.json'):
checks[filename] = read(paths[0] / filename) == read(paths[1] / filename)
assert all(checks.values()), 'Repeat accounting differs: ' + row['name']
repeats.append(dict(name=row['name'], checks=checks, pass_all=True))
assert len(repeats) == CFG['technical_repeats']
save(EXP / 'audit/repeat-checks.json', dict(pass_all=True, repeats=repeats))
summaries, target_rows, trial_metrics = [], [], []
for row in primary:
folder = EXP / 'trials' / (row['name'] + '-r0')
report = read(folder / 'summary.json')
assert report['pass_all'] and all(report['baseline_exact'].values())
summaries.append(report)
targets = load_arrays(folder / 'baseline-target-accounting.npz')
change = load_arrays(folder / 'conditional25-target-accounting.npz')
edges = load_arrays(folder / 'baseline-edge-accounting.npz')
exc = targets['accepted_positive_V'].sum(axis=1)
plastic_exc = targets['accepted_positive_V'][:, 0]
area_exc = targets['g_sign_integral_V_ms'][:, :, 0].sum(axis=1)
area_plastic = targets['g_sign_integral_V_ms'][:, 0, 0]
for i, meta in enumerate(target_meta.itertuples()):
entry = dict(name=row['name'], cohort=row['cohort'], mode=row['mode'], odor_id=row['odor_id'], seed=row['seed'],
neuron_idx=int(meta.neuron_idx), body_id=int(meta.body_id), annotation_type=meta.annotation_type,
compartment_verbatim=meta.mb_compartment_verbatim,
candidate_target=bool(np.any(candidate.target_neuron_idx.to_numpy() == meta.neuron_idx)),
accepted_total_positive_V=float(exc[i]), accepted_plastic_positive_V=float(plastic_exc[i]),
plastic_accepted_positive_fraction=None if exc[i] == 0 else float(plastic_exc[i] / exc[i]),
positive_source_g_area_V_ms=float(area_exc[i]), plastic_positive_g_area_V_ms=float(area_plastic[i]),
plastic_positive_g_area_fraction=None if area_exc[i] == 0 else float(area_plastic[i] / area_exc[i]),
baseline_spikes=int(targets['spike_count'][i]), conditional25_spikes=int(change['spike_count'][i]),
baseline_first_tick=int(targets['first_spike_tick'][i]), conditional25_first_tick=int(change['first_spike_tick'][i]),
max_abs_V_delta_SI=float(np.max(np.abs(targets['history_v'][:, i] - change['history_v'][:, i]))),
max_abs_g_delta_SI=float(np.max(np.abs(targets['history_g'][:, i] - change['history_g'][:, i]))))
target_rows.append(entry)
pick = data['is_candidate']
summary = dict(name=row['name'], cohort=row['cohort'], mode=row['mode'], odor_id=row['odor_id'], seed=row['seed'],
plastic_due_events=int(edges['due_count'][pick].sum()), plastic_accepted_events=int(edges['accepted_count'][pick].sum()),
plastic_rejected_events=int(edges['rejected_count'][pick].sum()),
plastic_pending_events=int(edges['pending_after_window_count'][pick].sum()),
plastic_edges_with_accepted_events=int(np.count_nonzero(edges['accepted_count'][pick])),
accepted_positive_V=float(exc.sum()), plastic_accepted_positive_V=float(plastic_exc.sum()),
plastic_accepted_positive_fraction=None if exc.sum() == 0 else float(plastic_exc.sum() / exc.sum()),
plastic_positive_g_area_fraction=None if area_exc.sum() == 0 else float(area_plastic.sum() / area_exc.sum()),
**report['sensitivity'])
trial_metrics.append(summary)
target_table = pd.DataFrame(target_rows)
if verify_only:
assert target_table.equals(pd.read_parquet(EXP / 'audit/per-MBON-primary.parquet'))
else:
target_table.to_parquet(EXP / 'audit/per-MBON-primary.parquet', index=False)
save(EXP / 'audit/primary-trial-metrics.json', trial_metrics)
pair_reports = []
for cohort in sorted({r['cohort'] for r in primary}):
for mode in sorted({r['mode'] for r in primary}):
rows = [r for r in primary if r['cohort'] == cohort and r['mode'] == mode]
odors = sorted({r['odor_index'] for r in rows})
assert len(rows) == 30 and len(odors) == 6
traces = {}; whole = {}; ids = {}
for odor in odors:
series = sorted((r for r in rows if r['odor_index'] == odor), key=lambda r: r['seed'])
assert len(series) == 5
ids[odor] = series[0]['odor_id']
credit = [load_arrays(EXP / 'trials' / (r['name'] + '-r0') / 'credit.npz') for r in series]
traces[odor] = np.stack([c['trace_gate'] for c in credit])
whole[odor] = np.stack([(c['spike_count_total'] > 0).astype(np.float32) for c in credit])
for a, b in combinations(odors, 2):
name = f'{cohort}-{mode}-{a:02d}-vs-{b:02d}'
result = dict(cohort=cohort, mode=mode, odor_a=ids[a], odor_b=ids[b], odor_index_a=a, odor_index_b=b)
for definition, series in (('virtual_gate900', traces), ('whole_window_auxiliary', whole)):
identities = pair_identity(series[a], series[b], data['edge_source_local'])
save_arrays = {k: v for k, v in identities.items() if isinstance(v, np.ndarray)}
write_npz(EXP / 'pairs' / (name + '-' + definition + '.npz'), **save_arrays)
summary = identities['summary']
# Historic local existence criterion only, no new overall pass rate.
summary['local_specific_KC_and_edges_exist_both_sides'] = all(
summary[unit]['category_counts'][side] >= 1 for unit in ('KC', 'edges') for side in ('A_specific', 'B_specific'))
summary['compartments'] = {}
for comp, group in candidate.groupby('target_compartment_verbatim', sort=True):
sub = pair_identity(series[a], series[b], data['edge_source_local'][group.index.to_numpy()])
summary['compartments'][str(comp)] = sub['summary']['edges']
result[definition] = summary
pair_reports.append(result)
assert len(pair_reports) == 60
save(EXP / 'audit/pair-identities.json', pair_reports)
blocks = {}
for cohort in sorted({r['cohort'] for r in primary}):
for mode in sorted({r['mode'] for r in primary}):
trials = [r for r in trial_metrics if r['cohort'] == cohort and r['mode'] == mode]
pairs = [p for p in pair_reports if p['cohort'] == cohort and p['mode'] == mode]
blocks[cohort + '/' + mode] = dict(trials=len(trials), pairs=len(pairs),
pairs_with_both_specific_KC_and_edge_sets=sum(p['virtual_gate900']['local_specific_KC_and_edges_exist_both_sides'] for p in pairs),
KC_empty_union_pairs=sum(p['virtual_gate900']['KC']['empty_union'] for p in pairs),
edge_empty_union_pairs=sum(p['virtual_gate900']['edges']['empty_union'] for p in pairs),
KC_active_set_Jaccard=metrics([p['virtual_gate900']['KC']['active_set_jaccard'] for p in pairs if not p['virtual_gate900']['KC']['empty_union']]),
candidate_edge_active_set_Jaccard=metrics([p['virtual_gate900']['edges']['active_set_jaccard'] for p in pairs if not p['virtual_gate900']['edges']['empty_union']]),
plastic_accepted_events=metrics([r['plastic_accepted_events'] for r in trials]),
plastic_fraction_of_all_MBON_accepted_positive=metrics([r['plastic_accepted_positive_fraction'] for r in trials if r['plastic_accepted_positive_fraction'] is not None]),
conditional25_event_changed_trials=sum(r['event_sequence_changed'] for r in trials),
conditional25_count_or_first_latency_changed_trials=sum(r['functional_count_or_first_latency_change'] for r in trials),
max_abs_V_difference_SI=max(r['max_abs_V_difference_SI'] for r in trials),
max_abs_g_difference_SI=max(r['max_abs_g_difference_SI'] for r in trials))
outcome = dict(arithmetic_and_coverage_pass=True, primary_trials=120, exact_repeats=24, pairs=60, blocks=blocks,
candidate_generated=False, learning_achieved=False, conditioning_executed=False, scientific_overall_PASS_threshold_defined=False,
maximum_observed_g_lane_roundoff_residual_SI=max(read(EXP / 'trials' / (r['name'] + '-r0') / 'baseline-accounting.json')['max_g_lane_residual_V'] for r in primary),
interpretation='Read-only credit delivery and fixed conditional sensitivity; not closed-loop memory or dopamine measurement')
save(EXP / 'audit/scientific-summary.json', outcome)
return outcome
if __name__ == '__main__':
print(run(), flush=True)
@@ -0,0 +1,237 @@
"""Pure synthetic credit tests; no brain execution or historical dynamic read."""
import ast
import hashlib
import math
from pathlib import Path
import types
import unittest
import numpy as np
from credit import CATEGORY_CODES, N_GLOBAL, pair_identity, replay_credit
SOURCE_PINS = {
'phase4o_event_replay.py': 'b169937bab495c77d8ee6c1013877e7500e7354140d9fcc90595d984238ba44e',
'phase4l.py': 'e97d58d8c1f44920d0e953f8f5831ddc86fa0198784e86460e315de4116ac553',
}
def frozen_tree(filename):
"""Only static source bytes are read; never import the old script or its main."""
here = Path(__file__).absolute().parent
candidates = [here.parent / 'models/male-cns-lif' / filename,
here.parent / filename,
here.parent / ('runtime-credit-v1' if filename.startswith('phase4o') else 'olfactory-grounding-v1') / filename]
path = next((path for path in candidates if path.is_file()), None)
if path is None:
raise unittest.SkipTest('Frozen arithmetic source not installed at declared static paths: ' + filename)
source = path.read_bytes()
if hashlib.sha256(source).hexdigest() != SOURCE_PINS[filename]:
raise AssertionError('Frozen arithmetic source SHA mismatch: ' + str(path))
return ast.parse(source, filename=str(path)), path
def assert_bits(test, first, second):
test.assertEqual(first.dtype, second.dtype)
test.assertEqual(first.shape, second.shape)
np.testing.assert_array_equal(first.view(np.uint8), second.view(np.uint8))
def events(rows):
if not rows:
return np.empty(0, np.int32), np.empty(0, np.int32)
array = np.asarray(rows, np.int32)
return array[:, 0].copy(), array[:, 1].copy()
class ReplayTests(unittest.TestCase):
def setUp(self):
self.kc = np.array([3, 9, 12], np.int32)
def test_gate_900_includes_same_tick_901_is_final_only_no_18_shift(self):
ticks, nodes = events([(882, 12), (900, 3), (901, 9)])
result = replay_credit(ticks, nodes, self.kc)
self.assertEqual(result['trace_gate'][0], np.float32(1))
self.assertEqual(result['trace_gate'][1], np.float32(0))
decay = np.float32(math.exp(-.1 / 100))
expected = np.float32(1)
for _ in range(18):
expected = np.float32(expected * decay)
self.assertEqual(result['trace_gate'][2].view(np.uint32), expected.view(np.uint32))
self.assertTrue(np.all(result['trace_final'] > 0))
np.testing.assert_array_equal(result['spike_count_gate'], [1, 0, 1])
np.testing.assert_array_equal(result['spike_count_total'], [1, 1, 1])
self.assertEqual(result['event_shift_steps'], 0)
self.assertTrue(result['gate_is_virtual_readout_not_DAN_activity'])
def test_spike_cap_resets_recency_not_accumulated_count(self):
ticks, nodes = events([(0, 3), (7, 3), (7, 9)])
result = replay_credit(ticks, nodes, self.kc, steps=15, gate_tick=7)
np.testing.assert_array_equal(result['trace_gate'], np.array([1, 1, 0], np.float32))
self.assertEqual(result['trace_final'][0].view(np.uint32), result['trace_final'][1].view(np.uint32))
np.testing.assert_array_equal(result['spike_count_total'], [2, 1, 0])
def test_tick_zero_gate_and_last_tick_have_no_extra_decay(self):
ticks, nodes = events([(0, 3), (2, 9)])
result = replay_credit(ticks, nodes, self.kc, steps=3, gate_tick=0)
self.assertEqual(result['trace_gate'][0], np.float32(1))
self.assertEqual(result['trace_final'][1], np.float32(1))
self.assertEqual(result['spike_count_gate'].tolist(), [1, 0, 0])
def test_empty_events_are_positive_zero_with_exact_dtypes(self):
ticks, nodes = events([])
result = replay_credit(ticks, nodes, self.kc, steps=2, gate_tick=1)
for key in ('trace_gate', 'trace_final'):
self.assertEqual(result[key].dtype, np.dtype('float32'))
self.assertTrue(np.all(result[key].view(np.uint32) == 0))
for key in ('spike_count_gate', 'spike_count_total'):
self.assertEqual(result[key].dtype, np.dtype('int32'))
self.assertFalse(result[key].any())
def test_non_kc_events_ignored_only_after_full_validation(self):
ticks, nodes = events([(0, 2), (0, 3), (1, 4)])
for value in (ticks, nodes, self.kc):
value.flags.writeable = False
before = [value.tobytes() for value in (ticks, nodes, self.kc)]
result = replay_credit(ticks, nodes, self.kc, steps=2, gate_tick=1)
self.assertEqual(result['total_input_events'], 3)
self.assertEqual(result['total_KC_events'], 1)
self.assertEqual(before, [value.tobytes() for value in (ticks, nodes, self.kc)])
result['trace_gate'][0] = 0
self.assertGreater(result['trace_final'][0], 0)
def test_invalid_event_order_duplicate_ranges_and_dtype(self):
bad_rows = [[(-1, 3)], [(1000, 3)], [(0, -1)], [(0, N_GLOBAL)],
[(1, 3), (0, 3)], [(0, 9), (0, 3)], [(0, 3), (0, 3)], [(0, 2), (0, 2)]]
for rows in bad_rows:
with self.subTest(rows=rows), self.assertRaises(ValueError):
replay_credit(*events(rows), self.kc)
for dtype in (np.int64, np.float32, np.float64):
with self.subTest(dtype=dtype), self.assertRaises(ValueError):
replay_credit(np.array([], dtype), np.array([], dtype), self.kc)
with self.assertRaises(ValueError):
replay_credit(np.array([0], np.int32), np.array([], np.int32), self.kc)
with self.assertRaises(ValueError):
replay_credit(np.array([np.nan]), np.array([3], np.int32), self.kc)
def test_invalid_kc_identities_and_frozen_parameters(self):
empty = np.empty(0, np.int32)
for kc in (empty, np.array([9, 3], np.int32), np.array([3, 3], np.int32),
np.array([-1], np.int32), np.array([N_GLOBAL], np.int32), self.kc.astype(np.int64)):
with self.subTest(kc=kc.tolist()), self.assertRaises(ValueError):
replay_credit(empty, empty, kc)
for keyword, value in (('steps', 0), ('steps', True), ('gate_tick', -1), ('gate_tick', 1000),
('gate_tick', 900.), ('dt_ms', .2), ('tau_ms', 20), ('tau_ms', np.inf),
('cap', .5), ('cap', True)):
with self.subTest(keyword=keyword, value=value), self.assertRaises(ValueError):
replay_credit(empty, empty, self.kc, **{keyword: value})
def test_independent_frozen_numpy_loop_ast_matches_all_trace_bits(self):
tree, path = frozen_tree('phase4o_event_replay.py')
loops = [node for node in ast.walk(tree) if isinstance(node, ast.For) and isinstance(node.target, ast.Name)
and node.target.id == 'step' and any(isinstance(child, ast.AugAssign) and
isinstance(child.target, ast.Name) and child.target.id == 'trace' for child in node.body)]
self.assertEqual(len(loops), 1)
self.assertEqual(len(loops[0].body), 4)
code = compile(ast.fix_missing_locations(ast.Module(body=[loops[0]], type_ignores=[])), str(path), 'exec')
ticks, nodes = events([(0, 3), (0, 12), (1, 9), (22, 3), (899, 9), (900, 3), (901, 12), (999, 3)])
result = replay_credit(ticks, nodes, self.kc)
lookup = {int(node): index for index, node in enumerate(self.kc)}
for gate, field in ((900, 'trace_gate'), (999, 'trace_final')):
by_step = {}
for tick, node in zip(ticks, nodes):
by_step.setdefault(int(tick), []).append(lookup[int(node)])
namespace = dict(np=np, trace=np.zeros(len(self.kc), np.float32),
decay=np.float32(math.exp(-.1 / 100)), by_step=by_step, step_gate=gate)
exec(code, namespace)
assert_bits(self, result[field], namespace['trace'])
def test_independent_frozen_torch_cpu_statement_matches_when_available(self):
try:
import torch
except (ImportError, OSError) as error:
self.skipTest('Torch CPU unavailable; comparison explicitly not performed: ' + str(error))
tree, path = frozen_tree('phase4l.py')
expressions = [node for node in ast.walk(tree) if isinstance(node, ast.Expr) and
'trace.mul_(trace_decay).add_' in ast.unparse(node)]
self.assertEqual(len(expressions), 1)
code = compile(ast.fix_missing_locations(ast.Module(body=expressions, type_ignores=[])), str(path), 'exec')
ticks, nodes = events([(0, 3), (22, 3), (899, 9), (900, 3), (901, 12), (999, 9)])
result = replay_credit(ticks, nodes, self.kc)
trace = torch.zeros(len(self.kc), dtype=torch.float32, device='cpu')
namespace = dict(trace=trace, trace_decay=math.exp(-.1 / 100), torch=torch,
self=types.SimpleNamespace(kc_gpu=torch.tensor(self.kc.astype(np.int64), device='cpu')))
gate = None
for tick in range(1000):
spikes = torch.zeros(13, dtype=torch.bool, device='cpu')
active = nodes[ticks == tick]
if len(active):
spikes[torch.tensor(active.astype(np.int64), device='cpu')] = True
namespace['spikes'] = spikes
exec(code, namespace)
if tick == 900:
gate = trace.numpy().copy()
assert_bits(self, result['trace_gate'], gate)
assert_bits(self, result['trace_final'], trace.numpy())
class IdentityTests(unittest.TestCase):
def setUp(self):
self.a, self.b = np.zeros((5, 4), np.float32), np.zeros((5, 4), np.float32)
self.a[:3, 0] = 1 # A specific
self.a[:3, 1] = .1; self.b[:4, 1] = .2 # shared
self.b[:3, 2] = .3 # B specific
self.a[:2, 3] = 1; self.b[:2, 3] = 1 # no majority
self.source = np.array([0, 1, 1, 2, 3], np.int32)
def test_three_of_five_categories_and_full_set_jaccard(self):
result = pair_identity(self.a, self.b, self.source)
np.testing.assert_array_equal(result['kc_category'], np.array([1, 3, 2, 0], np.int8))
np.testing.assert_array_equal(result['edge_category'], np.array([1, 3, 3, 2, 0], np.int8))
self.assertEqual(result['summary']['KC']['active_set_jaccard'], 1 / 3)
self.assertEqual(result['summary']['edges']['active_set_jaccard'], .5)
self.assertEqual(result['summary']['KC']['category_counts'], {name: 1 for name in CATEGORY_CODES})
self.assertTrue(result['summary']['edge_counts_are_source_degree_weighted'])
def test_tiny_positive_no_epsilon_and_no_weight_update_outputs(self):
self.a[:3, 0] = np.nextafter(np.float32(0), np.float32(1))
result = pair_identity(self.a, self.b, self.source)
self.assertTrue(result['kc_active_a'][0])
self.assertFalse(result['summary']['weights_modified'])
self.assertFalse(result['summary']['weight_delta_generated'])
self.assertFalse(any('delta' in key or 'weight' in key for key in result if key != 'summary'))
def test_empty_union_and_empty_edge_mapping_explicit(self):
result = pair_identity(np.zeros_like(self.a), np.zeros_like(self.b), np.empty(0, np.int32))
self.assertTrue(result['summary']['KC']['empty_union'])
self.assertEqual(result['summary']['KC']['active_set_jaccard'], 1.)
self.assertTrue(result['summary']['edges']['empty_union'])
self.assertEqual(result['edge_category'].dtype, np.dtype('int8'))
def test_readonly_inputs_and_returned_mapping_owns_memory(self):
for value in (self.a, self.b, self.source):
value.flags.writeable = False
before = [value.tobytes() for value in (self.a, self.b, self.source)]
result = pair_identity(self.a, self.b, self.source)
result['edge_category'][0] = 0
self.assertEqual(result['kc_category'][0], 1)
self.assertEqual(before, [value.tobytes() for value in (self.a, self.b, self.source)])
def test_reject_wrong_dtype_shape_nonfinite_negative_cap_and_source(self):
for bad in (self.a.astype(np.float64), self.a[:4], np.zeros((5, 0), np.float32)):
with self.subTest(shape=bad.shape), self.assertRaises(ValueError):
pair_identity(bad, self.b, self.source)
for value in (np.nan, np.inf, -.1, 1.01):
bad = self.a.copy(); bad[0, 0] = value
with self.subTest(value=value), self.assertRaises(ValueError):
pair_identity(bad, self.b, self.source)
with self.assertRaises(ValueError):
pair_identity(self.a, self.b[:, :3], self.source)
for source in (np.array([-1], np.int32), np.array([4], np.int32), self.source.astype(np.int64)):
with self.subTest(source=source.tolist()), self.assertRaises(ValueError):
pair_identity(self.a, self.b, source)
if __name__ == '__main__':
unittest.main()
@@ -0,0 +1,56 @@
"""Synthetic interface and fail-closed schedule checks, no scientific records."""
import ast
from pathlib import Path
import unittest
import numpy as np
from run_audit import compare_results
from summarize import metrics, primary_rows
class IntegrationTests(unittest.TestCase):
def test_per_neuron_count_difference_not_cancelled_by_equal_total(self):
a = dict(step=np.array([0, 1], np.int32), neuron_idx=np.array([1, 1], np.int32),
counts=np.array([2, 0], np.int32), first_spike=np.array([0, -1], np.int32),
v=np.array([-.052, -.052], np.float64), g=np.zeros(2, np.float64))
b = {k: v.copy() for k, v in a.items()}
b['neuron_idx'][:] = 2; b['counts'][:] = [0, 2]; b['first_spike'][:] = [-1, 0]
h = dict(history_v=np.zeros((2, 2)), history_g=np.zeros((2, 2)))
result = compare_results(a, b, h, h)
self.assertEqual(result['spike_count_L1'], 4)
self.assertEqual(result['neurons_with_count_change'], 2)
self.assertTrue(result['functional_count_or_first_latency_change'])
def test_event_difference_with_same_counts_and_first_is_retained(self):
a = dict(step=np.array([0, 3], np.int32), neuron_idx=np.array([1, 1], np.int32),
counts=np.array([2], np.int32), first_spike=np.array([0], np.int32),
v=np.zeros(1), g=np.zeros(1))
b = {k: v.copy() for k, v in a.items()}; b['step'][1] = 4
h = dict(history_v=np.zeros((5, 1)), history_g=np.zeros((5, 1)))
result = compare_results(a, b, h, h)
self.assertTrue(result['event_sequence_changed'])
self.assertFalse(result['functional_count_or_first_latency_change'])
def test_undefined_denominator_is_not_silently_zero(self):
self.assertIsNone(metrics([])['mean'])
def test_repeat_not_an_independent_primary_trial(self):
rows = [dict(name=str(i), repeat=0) for i in range(120)] + [dict(name=str(i), repeat=1) for i in range(24)]
self.assertEqual(len(primary_rows(dict(planned=rows))), 120)
rows[1]['name'] = '0'
with self.assertRaises(AssertionError):
primary_rows(dict(planned=rows))
def test_all_baseline_gate_precedes_conditional_schedule(self):
tree = ast.parse((Path(__file__).parent / 'run_audit.py').read_text())
main = next(n for n in tree.body if isinstance(n, ast.FunctionDef) and n.name == 'main')
loops = [n for n in ast.walk(main) if isinstance(n, ast.For) and isinstance(n.target, ast.Name) and n.target.id == 'stage']
self.assertEqual(len(loops), 1)
self.assertEqual(ast.literal_eval(loops[0].iter), ('baseline', 'conditional25'))
audit = next(n for n in tree.body if isinstance(n, ast.FunctionDef) and n.name == 'audit_trial')
source = ast.unparse(audit)
self.assertIn("baselines-complete.json", source)
self.assertLess(source.index("baselines-complete.json"), source.index('core.replay'))
if __name__ == '__main__':
unittest.main()
@@ -0,0 +1,179 @@
"""Synthetic-only tests; no recorded scientific data, GPU, or file writes."""
import importlib.util
import json
from pathlib import Path
import unittest
import numpy as np
from mbon_accounting import MBONAccounting, bit_equal
_source = Path(__file__).resolve().parents[1] / "kc-drive-audit-v1" / "replay_core.py"
_spec = importlib.util.spec_from_file_location("_mbon_test_frozen_ah_replay", _source)
ah = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(ah)
def coeff(**changes):
values = dict(gd=.5, c0=0., cg=1., cv=1., threshold=100., reset=0., seed_volt=101.)
values.update(changes)
return values
def events(rows=()):
array = np.asarray(rows, np.int32).reshape(-1, 2)
return dict(tick=array[:, 0], neuron_idx=array[:, 1])
def graph(weights=(), destinations=None, n=1):
e = len(weights)
return ah.IncomingIndex(np.arange(10, 10 + e, dtype=np.int32),
np.zeros(e, np.int32) if destinations is None else np.array(destinations, np.int32),
np.arange(1000, 1000 + e, dtype=np.int64), np.array(weights, np.float64), n)
def run(index, *, groups=None, names=("plastic_KC",), steps=25, constants=None,
spikes=(), requests=(), v=None, g=None, observer_hook=None):
constants = coeff() if constants is None else constants
groups = np.zeros(len(index.edge_id), np.int32) if groups is None else np.array(groups, np.int32)
obs = MBONAccounting(index, groups, names, constants, steps)
seen = []
def callback(tick, stage):
# Read-only views also catch accidental assignments to callback data.
original = {key: value.copy() for key, value in stage.items()}
for value in stage.values():
value.setflags(write=False)
obs(tick, stage)
for key in original:
if not bit_equal(stage[key], original[key]):
raise AssertionError("Observer modified callback field " + key)
if observer_hook:
observer_hook(tick, stage)
seen.append(original)
result = ah.replay(np.zeros(index.target_count, np.float64) if v is None else np.asarray(v, np.float64),
np.zeros(index.target_count, np.float64) if g is None else np.asarray(g, np.float64),
constants, steps, np.arange(100, 100 + index.target_count, dtype=np.int32),
events(requests), events(spikes), index,
np.zeros((steps, index.target_count), np.float32), callback)
data, edges, summary = obs.finish()
return data, edges, summary, result, seen, obs
class AccountingTests(unittest.TestCase):
def test_empty_graph_initial_baseline_and_json_summary(self):
data, edge, summary, result, _, _ = run(graph(), steps=3, g=[2.], v=[-.052])
self.assertEqual(summary["due_count"], 0)
self.assertEqual(len(edge["edge_id"]), 0)
np.testing.assert_array_equal(data["history_group_g"], 0.)
np.testing.assert_array_equal(data["history_g"], data["history_baseline_g"])
self.assertTrue(bit_equal(data["final_g"], result["g"]))
json.dumps(summary, allow_nan=False)
self.assertFalse(summary["functional_transfer_claim"])
self.assertFalse(summary["original_events_verified_by_observer"])
def test_real_delay_old_g_for_voltage_and_end_tick_integral(self):
data, edge, summary, _, _, _ = run(graph([2.]), steps=21, spikes=[(0, 10)])
np.testing.assert_array_equal(data["history_g"][:18], 0.)
np.testing.assert_array_equal(data["history_g"][18:, 0], [2., 1., .5])
np.testing.assert_array_equal(data["history_v"][18:, 0], [0., 2., 3.])
self.assertAlmostEqual(data["g_integral_V_ms"][0, 0], .35)
np.testing.assert_array_equal(edge["accepted_count"], [1])
self.assertEqual(summary["max_g_lane_residual_V"], 0.)
def test_arrival_rejected_on_fire_and_refractory_release(self):
data, edge, summary, result, _, _ = run(graph([2., -1.]), steps=41,
spikes=[(t, source) for t in (0, 1, 22) for source in (10, 11)], requests=[(18, 100)])
np.testing.assert_array_equal(edge["due_count"], [3, 3])
np.testing.assert_array_equal(edge["accepted_count"], [1, 1])
np.testing.assert_array_equal(edge["rejected_count"], [2, 2])
np.testing.assert_array_equal(data["history_g"][18:40], 0.)
self.assertEqual(data["history_g"][40, 0], 1.)
self.assertEqual(data["rejected_positive_V"][0, 0], 4.)
self.assertEqual(data["rejected_negative_V"][0, 0], -2.)
self.assertEqual(data["accepted_negative_V"][0, 0], -1.)
np.testing.assert_array_equal(data["spike_count"], result["counts"])
self.assertEqual(summary["accepted_count"], 2)
def test_firing_clears_source_g_and_voltage_lanes(self):
data, _, _, result, _, _ = run(graph([2.]), steps=21, constants=coeff(threshold=1., seed_volt=2.),
spikes=[(0, 10)])
self.assertEqual(data["first_spike_tick"][0], 19)
np.testing.assert_array_equal(data["history_group_g"][19:], 0.)
np.testing.assert_array_equal(data["final_group_v"], 0.)
self.assertEqual(data["history_ref"][19, 0], 22)
self.assertEqual(data["history_ref"][20, 0], 21)
self.assertTrue(bit_equal(data["final_ref"], result["ref"]))
def test_group_sign_totals_and_zero_weight_event_counts(self):
index = graph([2., -3., 0., 4.], destinations=[0, 0, 1, 1], n=2)
data, edge, summary, _, _, _ = run(index, groups=[0, 1, 0, 1], names=("plastic_KC", "other"),
steps=19, spikes=[(0, x) for x in range(10, 14)])
np.testing.assert_array_equal(data["accepted_count"], [[1, 1], [1, 1]])
np.testing.assert_array_equal(data["accepted_zero_count"], [[0, 0], [1, 0]])
np.testing.assert_array_equal(data["accepted_positive_V"], [[2., 0.], [0., 4.]])
np.testing.assert_array_equal(data["accepted_negative_V"], [[0., -3.], [0., 0.]])
self.assertEqual(summary["groups"]["other"]["accepted_negative_V"], -3.)
self.assertEqual(int(edge["due_count"].sum()), 4)
def test_cross_lane_roundoff_is_visible_not_reassigned(self):
data, _, summary, _, _, _ = run(graph([1.e16, 1., -1.e16]), groups=[0, 1, 0],
names=("plastic_KC", "other"), steps=19, constants=coeff(threshold=1.e30, seed_volt=2.e30),
spikes=[(0, 10), (0, 11), (0, 12)])
self.assertEqual(data["history_g"][18, 0], 0.)
self.assertEqual(data["history_group_g"][18, 0, 1, 0], 1.)
self.assertEqual(data["history_g_residual"][18, 0], -1.)
self.assertEqual(summary["max_g_lane_residual_V"], 1.)
def test_callback_and_constructor_inputs_never_modified(self):
index = graph([1., -.25])
snapshots = {name: getattr(index, name).copy() for name in ("src_global", "dst_local", "edge_id", "weights")}
run(index, spikes=[(0, 10), (0, 11)], requests=[(1, 100)])
for name, original in snapshots.items():
self.assertTrue(bit_equal(getattr(index, name), original))
def test_incomplete_duplicate_tick_and_repeated_finish_fail(self):
index = graph()
with self.assertRaises(ValueError):
MBONAccounting(index, np.empty(0, np.int32), ("other",), coeff(), 2).finish()
_, _, _, _, stages, finished = run(index, steps=2)
with self.assertRaises(ValueError):
finished.finish()
obs = MBONAccounting(index, np.empty(0, np.int32), ("other",), coeff(), 2)
obs(0, stages[0])
with self.assertRaises(ValueError):
obs(0, stages[0])
def test_bad_dtype_groups_coefficients_and_nonfinite_fail(self):
index = graph([1.])
for codes in (np.array([0], np.int64), np.array([1], np.int32), np.array([], np.int32)):
with self.assertRaises(ValueError):
MBONAccounting(index, codes, ("other",), coeff(), 2)
for bad in (coeff(gd=np.float32(.5)), coeff(gd=float("nan")), coeff(dt_ms=.2)):
with self.assertRaises(ValueError):
MBONAccounting(index, np.array([0], np.int32), ("other",), bad, 2)
def test_stage_guard_apl_and_canonical_order_corruption_fail(self):
index = graph([1., 2.])
_, _, _, _, stages, _ = run(index, steps=19, spikes=[(0, 10), (0, 11)])
for corruption in ("guard", "order", "apl", "state"):
obs = MBONAccounting(index, np.array([0, 0], np.int32), ("other",), coeff(), 19)
for tick in range(18):
obs(tick, stages[tick])
stage = {k: v.copy() for k, v in stages[18].items()}
if corruption == "guard":
stage["arrival_accepted"][0] = False
elif corruption == "order":
stage["due_edge_indices"] = stage["due_edge_indices"][::-1].copy()
elif corruption == "apl":
stage["h32"][0] = 1.
else:
stage["g_pre_apl"][0] += 1.
with self.subTest(corruption=corruption), self.assertRaises(ValueError):
obs(18, stage)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,61 @@
"""Rerun old deterministic-v2 references without changing their semantics."""
import json
import os
import sys
import time
import urllib.request
import numpy as np
import pandas as pd
import yaml
from context import ROOT, EXP, check, save, bit_equal
def preflight():
os.environ['CUDA_VISIBLE_DEVICES'] = 'GPU-79f3a85d-adce-7833-7003-6488140ca596'
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:
queue = json.load(response)
assert not queue['queue_running'] and not queue['queue_pending'], 'Comfy busy: defer, do not evict'
def main():
check(); preflight()
assert not (EXP / 'regression/frozen-five.json').exists()
runtime_path = ROOT / 'models/male-cns-lif/deterministic-runtime-v2'
config = 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(config, runtime_path, EXP)
rows = []
for spec in config['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={k: bit_equal(v, result[k]) for k, v in reference.items()},
dtype_shape={k: dict(reference_dtype=str(v.dtype), actual_dtype=str(result[k].dtype),
reference_shape=list(v.shape), actual_shape=list(result[k].shape)) for k, v 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 reference only, not D18 equality to old dynamics'))
assert passed
if __name__ == '__main__':
main()