Files
2026-09-30 17:57:02 +08:00

295 lines
16 KiB
Python

"""Pure pre-outcome DRAFT snapshot policy; no I/O, inputs, execution or RNG.
The caller must supply the complete existing trial_plan payload. This module
does not select a master seed. Its fixed policy choices are newly proposed
planning choices, not inherited biological measurements or scientific gates.
build_evaluation_policy(trial_payload) returns a config directly consumable by
evaluation_plan.build_evaluation_plan, plus a separate ordered prefix audit.
Every snapshot boundary is AFTER the complete selected epoch and its scheduled
delta update, not immediately after the US tick. All returned timing and counts
describe planned exposures, never observed or accepted DAN spikes.
"""
from __future__ import annotations
from collections import Counter
import hashlib
import json
import evaluation_plan
import trial_plan
SCHEMA = "phase4an-evaluation-policy-draft-v1"
ALGORITHM = "fixed-ordered-CS-prefix-completed-epoch-v1"
POINTS = (0, 1, 2, 5, 10, 20, 50)
CS_FIELDS = frozenset(("cue", "odor", "occurrence", "stream_id", "source_stream_id",
"seed", "seed_bits", "seed_conversion", "role", "start_tick", "stop_tick_exclusive"))
IDENTITY_FIELDS = tuple(sorted(CS_FIELDS - {"stream_id"}))
SAME_POSITION_ARMS = frozenset(("d_zero", "frozen", "random_reward", "cs_only"))
def _require(condition, message):
if not condition:
raise ValueError(message)
def _canonical(value):
return json.dumps(value, sort_keys=True, separators=(",", ":"),
ensure_ascii=False, allow_nan=False)
def _sha(value):
return hashlib.sha256(_canonical(value).encode("utf-8")).hexdigest()
def _registry_index(trial):
manifest = trial["seed_manifest"]
independent = {(r["split"], tuple(r["key"])): r for r in manifest["independent_streams"]}
aliases = {(r["split"], tuple(r["key"])): r for r in manifest["aliases"]}
_require(len(independent) == len(manifest["independent_streams"]) and
len(aliases) == len(manifest["aliases"]) and not (independent.keys() & aliases.keys()),
"Duplicate or overlapping seed registry keys")
return independent, aliases
def _stream_id(master, key):
return "training:" + _sha([trial_plan.SCHEMA, "stream-id", master, "training", list(key)])
def _cs_identity(cs, *, block_id, assay, arm, base_arm, master, registry):
"""Verify actual alias source/seed; omit only the control's own alias ID."""
_require(type(cs) is dict and set(cs) == CS_FIELDS, "CS binding schema differs")
_require(cs["cue"] in trial_plan.CUES and cs["odor"] == trial_plan.ODORS[cs["cue"]] and
type(cs["occurrence"]) is int and 0 <= cs["occurrence"] < 50 and
type(cs["seed"]) is int and 0 <= cs["seed"] < 2**63 and
type(cs["seed_bits"]) is int and cs["seed_bits"] == 63 and cs["role"] == "CS" and
type(cs["start_tick"]) is int and cs["start_tick"] == 0 and
type(cs["stop_tick_exclusive"]) is int and cs["stop_tick_exclusive"] == 1000 and
cs["seed_conversion"] == "exact integer; no truncation or addition",
"CS cue/occurrence/full seed/role/timing differs")
key = (block_id, assay, "CS", cs["cue"], f'{cs["occurrence"]:02d}', arm)
source_key = key[:-1] + (base_arm,)
independent, aliases = registry
source = independent.get(("training", source_key))
_require(source is not None and source["seed"] == cs["seed"] and
cs["source_stream_id"] == _stream_id(master, source_key) and
cs["stream_id"] == _stream_id(master, key), "CS seed/source stream authority differs")
if arm == base_arm:
_require(("training", key) not in aliases and cs["stream_id"] == cs["source_stream_id"],
"Original CS must be its own independent source")
else:
alias = aliases.get(("training", key))
_require(alias is not None and alias["source_split"] == "training" and
alias["source_key"] == list(source_key) and alias["seed"] == cs["seed"] and
alias["independent_replicate"] is False,
"CS alias is not registered to this block/assay/cue/occurrence source")
return {key: cs[key] for key in IDENTITY_FIELDS}
def _prefixes(arm, *, block_id, assay, base_arm, master, registry):
"""Return verified ordered CS records indexed by actual completed epochs."""
identities = []
prefixes = [[]]
for epoch in arm["epochs"]:
if epoch["cs"] is not None:
identities.append(_cs_identity(epoch["cs"], block_id=block_id, assay=assay,
arm=arm["arm"], base_arm=base_arm, master=master, registry=registry))
prefixes.append(list(identities))
if arm["arm"] == "dan_only":
_require(not identities, "DAN-only must contain no delivered CS")
else:
_require(len(identities) == 100, "Every CS-present arm must retain all 100 CS epochs")
counters = Counter()
for row in identities:
_require(row["occurrence"] == counters[row["cue"]], "CS occurrence order is not canonical")
counters[row["cue"]] += 1
_require(dict(counters) == {"A": 50, "B": 50}, "Both original odors require 50 CS occurrences")
return prefixes
def _reinforcement_boundary(arm, point):
"""The whole epoch containing the kth requested US; terminal includes all."""
if point == 0:
return 0
epochs = arm["epochs"]
us_indices = [i for i, e in enumerate(epochs) if e["us"]["requested"]]
_require(len(us_indices) == 50, "Reference arm must have exactly 50 requested US epochs")
return len(epochs) if point == 50 else us_indices[point - 1] + 1
def _exposure(arm, count, cs_prefix):
epochs = arm["epochs"][:count]
return dict(completed_epochs=count, CS_epochs=len(cs_prefix),
CS_counts_by_cue={cue: sum(c["cue"] == cue for c in cs_prefix) for cue in trial_plan.CUES},
ordered_CS_source_identity_sha256=_sha(cs_prefix),
requested_US_epochs=sum(e["us"]["requested"] for e in epochs),
enabled_nonzero_D_update_opportunities=sum(e["update_enabled"] and e["D_command"] != 0 for e in epochs),
elapsed_training_ticks=sum(e["steps"] for e in epochs),
actual_accepted_US_or_DAN_spikes_known=False)
def _suite_policy(block, suite, master, registry):
"""Independent prefix/accounting construction, separate from evaluation_plan."""
assay, block_id = suite["assay"], block["block_id"]
_require(assay in ("normal", "delayed", "reversal"), "Unexpected assay")
expected_arms = trial_plan.REVERSAL_ARMS if assay == "reversal" else trial_plan.ARMS
_require([a["arm"] for a in suite["arms"]] == list(expected_arms), "Missing, reordered or duplicate control arms")
arms = {a["arm"]: a for a in suite["arms"]}
base_name = "reversal" if assay == "reversal" else "paired"
base = arms[base_name]
_require(len(base["epochs"]) == 100, "Base arm must contain all 100 epochs")
prefixes = {name: _prefixes(arm, block_id=block_id, assay=assay, base_arm=base_name,
master=master, registry=registry) for name, arm in arms.items()}
base_full = prefixes[base_name][-1]
for name, arm in arms.items():
_require(len(arm["epochs"]) == (150 if name == "unpaired" else 100), "Unexpected epoch coverage")
if name == "dan_only":
for i, epoch in enumerate(arm["epochs"]):
original = base["epochs"][i]
_require(epoch["cs"] is None and
epoch["yoked_cue_removed"] == original["cs"]["cue"] and
type(epoch["yoked_occurrence_removed"]) is int and
epoch["yoked_occurrence_removed"] == original["cs"]["occurrence"] and
_canonical(epoch["us"]) == _canonical(original["us"]) and
epoch["steps"] == original["steps"],
"DAN-only omitted CS/yoked US/epoch position differs from paired")
else:
_require(_canonical(prefixes[name][-1]) == _canonical(base_full),
"Actual ordered CS cue/occurrence/seed/source sequence differs")
if name != "unpaired":
_require(all(_canonical(a) == _canonical(b)
for a, b in zip(prefixes[name], prefixes[base_name])),
"CS sequence differs at a paired epoch position")
bindings, audits = [], []
for name in expected_arms:
arm = arms[name]
previous = -1
for point in POINTS:
base_count = _reinforcement_boundary(base, point)
expected_prefix = prefixes[base_name][base_count]
if assay == "reversal":
count = _reinforcement_boundary(arm, point)
alignment = "own reinforced-US ordinal; intermediate CS prefixes need not match across arms"
elif name == "unpaired":
if point == 0:
count = 0
elif point == 50:
count = len(arm["epochs"])
else:
matches = [i for i, prefix in enumerate(prefixes[name]) if len(prefix) == len(expected_prefix)]
_require(bool(matches), "Unpaired arm never reaches the required CS prefix")
count = matches[0]
alignment = "earliest ordered-CS-prefix boundary; terminal includes all 150 epochs"
else:
count = base_count
alignment = ("paired yoked epoch positions; zero delivered CS, not equal CS exposure"
if name == "dan_only" else "exact paired completed-epoch and ordered-CS prefix")
_require(count > previous, "Policy snapshot epochs must advance strictly")
previous = count
own_prefix = prefixes[name][count]
same_cs = _canonical(own_prefix) == _canonical(expected_prefix)
if assay != "reversal" and name != "dan_only":
_require(same_cs, "Ordered CS prefix mismatch at a control checkpoint")
if name == "unpaired" and point not in (0, 50):
_require(count > 0 and len(prefixes[name][count - 1]) < len(expected_prefix),
"Unpaired snapshot is not the earliest matching completed-epoch boundary")
actual = _exposure(arm, count, own_prefix)
reference = _exposure(base, base_count, expected_prefix)
binding = dict(block_id=block_id, assay=assay, arm=name,
checkpoint=point, completed_epochs=count)
bindings.append(binding)
audits.append(dict(binding=binding, alignment=alignment,
reference_arm=base_name, reference_completed_epochs=base_count,
boundary="after complete epoch and scheduled delta update; not at the US tick",
terminal_includes_all_epochs=point == 50,
actual=actual, reference=reference,
ordered_CS_prefix_exact=same_cs if name != "dan_only" else None,
CS_exposure_equal=same_cs if name != "dan_only" else False,
DAN_only_yoke_verified=name == "dan_only",
requested_US_count_equal_to_reference=actual["requested_US_epochs"] == reference["requested_US_epochs"],
requested_US_count_difference=actual["requested_US_epochs"] - reference["requested_US_epochs"],
elapsed_ticks_equal_to_reference=actual["elapsed_training_ticks"] == reference["elapsed_training_ticks"],
elapsed_ticks_difference=actual["elapsed_training_ticks"] - reference["elapsed_training_ticks"],
nonzero_D_update_opportunity_difference=actual["enabled_nonzero_D_update_opportunities"] - reference["enabled_nonzero_D_update_opportunities"],
all_US_timing_or_total_exposure_matched_claimed=False))
return bindings, audits
def _assemble(trial):
registry = _registry_index(trial)
bindings, audits = [], []
blocks = trial["plan"]["blocks"]
_require(len(blocks) == 40, "Exactly 40 original training blocks are required")
for block in blocks:
_require(block["evaluation_checkpoints"] == list(POINTS) and
[s["assay"] for s in block["suites"]] == ["normal", "delayed", "reversal"],
"Original assay/checkpoint coverage differs")
for suite in block["suites"]:
suite_bindings, suite_audits = _suite_policy(block, suite, trial["plan"]["master"], registry)
bindings.extend(suite_bindings)
audits.extend(suite_audits)
config = dict(schema=evaluation_plan.CONFIG_SCHEMA, probe_steps=1200,
cs_start_tick=0, cs_stop_tick_exclusive=1000, reversal_checkpoints=list(POINTS),
baseline_execution="execute_each_snapshot", snapshot_binding_policy="explicit_completed_epochs",
snapshot_bindings=bindings)
# Check the unchanged consumer's declared schema/coverage as well as our
# independent ordered-prefix construction. Do not generate its probes here.
evaluation_plan._configuration(config)
evaluation_plan._binding_inventory(trial["plan"], config)
_require(len(bindings) == 4480 and len({_canonical(v) for v in bindings}) == 4480,
"Incomplete or duplicate snapshot coverage")
counts = Counter(row["assay"] for row in bindings)
result = dict(schema=SCHEMA, status="DRAFT-NOT-REGISTERED", algorithm=ALGORITHM,
trial_payload_sha256=trial["payload_sha256"],
seed_manifest_sha256=trial["seed_manifest"]["payload_sha256"],
config_sha256=_sha(config), config=config,
audit=dict(bindings=audits,
coverage=dict(training_blocks=40, training_replicates_per_cell=5,
nonretention_snapshots=4480, snapshots_by_assay=dict(sorted(counts.items())),
original_full_CS_alias_sequences_verified=True,
new_random_streams=0, new_master_seed_selected=False),
exposure_limits=[
"CS-present normal/delayed controls match actual ordered CS prefixes, not just counts.",
"US counts, nonzero-D opportunities and elapsed time are separately reported, not assumed matched.",
"Unpaired boundaries include earlier inserted US; terminal includes all 150 epochs and trailing US.",
"DAN-only has zero delivered CS; yoked positions are not equal CS exposure.",
"Reversal and continuation use their own kth US; intermediate CS prefixes can differ.",
"All snapshots are after a full epoch plus its delta update, not the US tick.",
"Requested US is not accepted DAN activity, dopamine concentration or learning evidence."],
unchanged_retention_scope=dict(arms=["normal/paired", "normal/frozen"],
idle_checkpoints_seconds=[0, 1, 10],
same_continuous_ten_second_lineage=True, probes_fork_and_do_not_advance_idle=True,
retained_snapshot_execution_or_lineage_verified=False),
conditional_evaluation_inventory=dict(nonretention_probes=8960,
retention_snapshots=240, retention_probes=480, all_snapshots=4720,
all_probes=9440, probe_steps=1200, evaluation_steps=11328000,
includes_full_seven_point_reversal=True,
baseline_output_deduplication=False,
actual_execution_count=0)),
policy_structural_checks_complete=True, choices_fixed_before_outcomes=True,
choices_are_new_draft_proposals_not_inherited_gates=True,
protocol_approval_claimed=False, execution_authorized=False, training_executed=False,
inputs_generated=False, input_generator_implementation_verified=False,
source_and_snapshot_authority_verified=False, candidate_generated=False,
learning_pass_claimed=False, scientific_gate_added=False)
result["payload_sha256"] = _sha(result)
return json.loads(_canonical(result))
def build_evaluation_policy(trial_payload):
"""Return fixed DRAFT config/audit; caller provides, we never choose, master."""
trial_plan.validate_trial_plan(trial_payload)
return _assemble(trial_payload)
def validate_evaluation_policy(payload, trial_payload):
"""Exact reconstruction, including JSON types; a replaced self-hash is not authority."""
_require(type(payload) is dict and "payload_sha256" in payload, "Invalid policy payload")
_require(payload["payload_sha256"] == _sha({k: v for k, v in payload.items() if k != "payload_sha256"}),
"Actual evaluation policy SHA mismatch")
expected = build_evaluation_policy(trial_payload)
_require(_canonical(payload) == _canonical(expected), "Policy config/audit/coverage differs from exact reconstruction")
return True