|
| 1 | +"""Bounded selection, dispatch and pressure-consumption audit on causal C8. |
| 2 | +
|
| 3 | +Selectors keep their built-in policies. Explicit THOL interventions are |
| 4 | +separate controls, never relabeled as selector decisions or autonomous birth. |
| 5 | +Each branch executes a fresh copy of the shared preparation procedure: live |
| 6 | +graphs contain locks and must not be shallow-copied or reconstructed by history |
| 7 | +assignment. The exact symmetry examples are detached declared models. |
| 8 | +""" |
| 9 | + |
| 10 | +from __future__ import annotations |
| 11 | + |
| 12 | +import argparse |
| 13 | +from copy import deepcopy |
| 14 | +from dataclasses import asdict |
| 15 | +from fractions import Fraction |
| 16 | +import json |
| 17 | +from pathlib import Path |
| 18 | +import platform |
| 19 | +import sys |
| 20 | + |
| 21 | +ROOT = Path(__file__).resolve().parents[1] |
| 22 | +sys.path.insert(0, str(ROOT)) |
| 23 | +sys.path.insert(0, str(ROOT / "src")) |
| 24 | + |
| 25 | +import networkx as nx # noqa: E402 |
| 26 | +import numpy as np # noqa: E402 |
| 27 | + |
| 28 | +from benchmarks.thol_birth_transport import ( # noqa: E402 |
| 29 | + CASES, prepare_birth_selection_source, run_birth_transport_case, |
| 30 | +) |
| 31 | +from benchmarks.thol_pressure_feedback import ( # noqa: E402 |
| 32 | + NEXT_STEP, _RecordingIntegrator, _integration_record, _payload, _state, |
| 33 | +) |
| 34 | +from tnfr.alias import get_attr # noqa: E402 |
| 35 | +from tnfr.constants.aliases import ALIAS_DSI, ALIAS_SI # noqa: E402 |
| 36 | +from tnfr.dynamics.dnfr import default_compute_delta_nfr # noqa: E402 |
| 37 | +from tnfr.dynamics.integrators import update_epi_via_nodal_equation # noqa: E402 |
| 38 | +from tnfr.dynamics.runtime import step # noqa: E402 |
| 39 | +from tnfr.dynamics.selectors import ( # noqa: E402 |
| 40 | + AbstractSelector, DefaultGlyphSelector, ParametricGlyphSelector, |
| 41 | +) |
| 42 | +from tnfr.operators import apply_glyph # noqa: E402 |
| 43 | +from tnfr.operators.definitions import SelfOrganization # noqa: E402 |
| 44 | +from tnfr.operators.factor_contracts import resolve_runtime_operator_factors # noqa: E402 |
| 45 | +from tnfr.operators.grammar_dynamics import validate_candidate # noqa: E402 |
| 46 | +from tnfr.physics.selector_symmetry import derive_selector_symmetry # noqa: E402 |
| 47 | +from tnfr.research.claims import ClaimStatus # noqa: E402 |
| 48 | +from tnfr.research.core_manifests import ( # noqa: E402 |
| 49 | + CoreExperimentManifest, current_git_source_provenance, |
| 50 | +) |
| 51 | +from tnfr.selector import _selector_thresholds # noqa: E402 |
| 52 | +from tnfr.types import Glyph # noqa: E402 |
| 53 | +from tnfr.utils.io import safe_write # noqa: E402 |
| 54 | + |
| 55 | +SELECTORS = {"default": DefaultGlyphSelector, "parametric": ParametricGlyphSelector} |
| 56 | +DISPATCHES = ("none", "primitive", "public") |
| 57 | + |
| 58 | + |
| 59 | +def _policy(graph, *, preparation=None): |
| 60 | + prefixes = ("SELECTOR_", "GLYPH_", "GRAMMAR_", "THOL_", "UM_", "PHASE_", "VF_") |
| 61 | + names = ("RANDOM_SEED", "EPI_MIN", "EPI_MAX", "CLIP_MODE", "SORT_NODES", |
| 62 | + "GRAMMAR", "AL_MAX_LAG", "EN_MAX_LAG", "SI_WEIGHTS", |
| 63 | + "VALIDATE_OPERATOR_PRECONDITIONS", "GAMMA", "_gamma_spec", |
| 64 | + "use_extended_dynamics", "INTEGRATOR_METHOD", "DT", "DT_MIN", "DT_MAX", |
| 65 | + "EPS_DNFR_STABLE") |
| 66 | + targets = (0,) if preparation is None else preparation["prep_targets"] |
| 67 | + prefix = ("IL", "OZ") if preparation is None else tuple( |
| 68 | + row["glyph"] for row in preparation["actual_prefix"] |
| 69 | + ) |
| 70 | + return { |
| 71 | + "declared_graph_settings": { |
| 72 | + key: deepcopy(value) for key, value in graph.graph.items() |
| 73 | + if key.startswith(prefixes) or key in names |
| 74 | + }, |
| 75 | + "resolved_selector_thresholds": dict(_selector_thresholds(graph)), |
| 76 | + "normalized_pressure_weights": dict(graph.graph["_dnfr_weights"]), |
| 77 | + "resolved_thol_factors": dict(resolve_runtime_operator_factors( |
| 78 | + graph.graph.get("GLYPH_FACTORS"), Glyph.THOL, graph.graph, |
| 79 | + )), |
| 80 | + "external_target": targets[0] if len(targets) == 1 else None, |
| 81 | + "external_targets": targets, |
| 82 | + "external_prefix": prefix, |
| 83 | + "scope": "Configured choices and preparation, not constants derived here", |
| 84 | + } |
| 85 | + |
| 86 | + |
| 87 | +def _decision_history(graph): |
| 88 | + history = graph.graph.get("history", {}) |
| 89 | + return { |
| 90 | + "since_AL": dict(history.get("since_AL", {})), |
| 91 | + "since_EN": dict(history.get("since_EN", {})), |
| 92 | + "sense_sigma_mag": tuple(history.get("sense_sigma_mag", ())), |
| 93 | + "mutation_abstentions": deepcopy(history.get("mutation_abstentions", ())), |
| 94 | + } |
| 95 | + |
| 96 | + |
| 97 | +class _RecordingSelector(AbstractSelector): |
| 98 | + """Delegate the actual lifecycle without changing its returned decisions.""" |
| 99 | + |
| 100 | + def __init__(self, delegate): |
| 101 | + self.delegate = delegate |
| 102 | + self.preparations = [] |
| 103 | + self.decisions = {} |
| 104 | + |
| 105 | + def prepare(self, graph, nodes): |
| 106 | + self.delegate.prepare(graph, nodes) |
| 107 | + preselection = self.delegate._preselection |
| 108 | + self.preparations.append({ |
| 109 | + "state": _state(graph), "nodes": tuple(nodes), |
| 110 | + "decision_history_before_lag_increment": _decision_history(graph), |
| 111 | + "sense_index": tuple(float(get_attr(graph.nodes[n], ALIAS_SI, 0.5)) |
| 112 | + for n in nodes), |
| 113 | + "sense_delta": tuple(float(get_attr(graph.nodes[n], ALIAS_DSI, 0.0)) |
| 114 | + for n in nodes), |
| 115 | + "preselection": asdict(preselection), |
| 116 | + "normalization": dict(graph.graph["_sel_norms"]), |
| 117 | + "selector_weights": dict(graph.graph.get("_selector_weights", {})), |
| 118 | + }) |
| 119 | + |
| 120 | + def select(self, graph, node): |
| 121 | + result = self.delegate.select(graph, node) |
| 122 | + self.decisions[node] = str(result) |
| 123 | + return result |
| 124 | + |
| 125 | + |
| 126 | +def run_selector_case(name): |
| 127 | + """One ordinary all-node runtime step with refreshed pressure and Si.""" |
| 128 | + if name not in SELECTORS: |
| 129 | + raise ValueError(f"selector must be one of {tuple(SELECTORS)}") |
| 130 | + graph, preparation = prepare_birth_selection_source() |
| 131 | + return _run_prepared_selector(graph, preparation, name) |
| 132 | + |
| 133 | + |
| 134 | +def _run_prepared_selector(graph, preparation, name): |
| 135 | + """Shared instrumentation of the built-in lifecycle on a prepared graph.""" |
| 136 | + if name not in SELECTORS: |
| 137 | + raise ValueError(f"selector must be one of {tuple(SELECTORS)}") |
| 138 | + graph.graph["GLYPH_SELECTOR_N_JOBS"] = 1 |
| 139 | + selector = _RecordingSelector(SELECTORS[name]()) |
| 140 | + integrator = _RecordingIntegrator() |
| 141 | + graph.graph.update( |
| 142 | + glyph_selector=selector, integrator=integrator, INTEGRATOR_METHOD="euler", |
| 143 | + ) |
| 144 | + policy = _policy(graph, preparation=preparation) |
| 145 | + step(graph, dt=NEXT_STEP, use_Si=True, apply_glyphs=True) |
| 146 | + return { |
| 147 | + "selector": name, "preparation": preparation, "policy": policy, |
| 148 | + "selection_contexts": selector.preparations, |
| 149 | + "actual_selector_proposals": selector.decisions, |
| 150 | + "integration": integrator.records, |
| 151 | + "endpoint": _state(graph), |
| 152 | + "decision_history_after_step": _decision_history(graph), |
| 153 | + "scope": ( |
| 154 | + "Built-in proposals followed by ordinary lag/grammar/primitive " |
| 155 | + "dispatch and integration; committed histories and integrator " |
| 156 | + "input are recorded separately. Subsequent runtime phase/capacity " |
| 157 | + "updates are included only in the whole-step endpoint" |
| 158 | + ), |
| 159 | + } |
| 160 | + |
| 161 | + |
| 162 | +def run_dispatch_case(dispatch, *, refresh): |
| 163 | + """An explicitly requested parent THOL, separate from selector selection.""" |
| 164 | + if dispatch not in DISPATCHES: |
| 165 | + raise ValueError(f"dispatch must be one of {DISPATCHES}") |
| 166 | + graph, preparation = prepare_birth_selection_source() |
| 167 | + policy = _policy(graph) |
| 168 | + before = _state(graph) |
| 169 | + admission = validate_candidate(graph, 0, "THOL") |
| 170 | + if not admission.allowed: |
| 171 | + raise RuntimeError("the causal source did not admit the THOL control") |
| 172 | + if dispatch == "primitive": |
| 173 | + apply_glyph(graph, 0, "THOL") |
| 174 | + elif dispatch == "public": |
| 175 | + SelfOrganization()(graph, 0, collect_metrics=True) |
| 176 | + raw = _state(graph) |
| 177 | + if refresh: |
| 178 | + default_compute_delta_nfr(graph) |
| 179 | + consumed = _state(graph) |
| 180 | + update_epi_via_nodal_equation(graph, dt=NEXT_STEP, method="euler") |
| 181 | + return { |
| 182 | + "dispatch": dispatch, "refresh_before_flow": refresh, |
| 183 | + "preparation": preparation, "policy": policy, |
| 184 | + "admission_allowed": admission.allowed, |
| 185 | + "before": before, "raw_after_dispatch": raw, |
| 186 | + "integration": _integration_record(consumed, _state(graph), graph), |
| 187 | + "scope": ( |
| 188 | + "Explicit parent intervention and one held-input shared Euler " |
| 189 | + "step, not a selector-selected birth or a complete operator word" |
| 190 | + ), |
| 191 | + } |
| 192 | + |
| 193 | + |
| 194 | +def symmetry_examples(): |
| 195 | + """Exact necessary-condition examples, not symmetry of the runtime C8.""" |
| 196 | + count = 8 |
| 197 | + group = tuple(tuple((offset + sign * i) % count for i in range(count)) |
| 198 | + for sign in (1, -1) for offset in range(count)) |
| 199 | + support = tuple(tuple((i - j) % count in (1, count - 1) |
| 200 | + for j in range(count)) for i in range(count)) |
| 201 | + # Full state of this abstract example: uniform triad, empty history, no marks. |
| 202 | + # It has no runtime sampling order, hidden phase gauge or selector adapter. |
| 203 | + uniform = ((Fraction(1), Fraction(1), Fraction(0), (), None),) * count |
| 204 | + marked = tuple((*label[:-1], "prepared-parent" if i == 0 else None) |
| 205 | + for i, label in enumerate(uniform)) |
| 206 | + results = {} |
| 207 | + for name, labels in (("uniform_unmarked", uniform), ("marked_parent", marked)): |
| 208 | + result = derive_selector_symmetry( |
| 209 | + state_labels=labels, relation_labels=support, |
| 210 | + permutations=group, candidates=tuple(range(count)), |
| 211 | + ) |
| 212 | + results[name] = { |
| 213 | + **asdict(result), |
| 214 | + "unique_equivariant_selection_obstructed": ( |
| 215 | + result.unique_equivariant_selection_obstructed |
| 216 | + ), |
| 217 | + "scope": result.scope, |
| 218 | + } |
| 219 | + return results |
| 220 | + |
| 221 | + |
| 222 | +def run_study(): |
| 223 | + return { |
| 224 | + "selectors": [run_selector_case(name) for name in SELECTORS], |
| 225 | + "dispatch_controls": [run_dispatch_case(dispatch, refresh=refresh) |
| 226 | + for dispatch in DISPATCHES for refresh in (False, True)], |
| 227 | + "transport_controls": [run_birth_transport_case(case) for case in CASES], |
| 228 | + "detached_symmetry_examples": symmetry_examples(), |
| 229 | + "limitations": ( |
| 230 | + "State readouts are declared projections, not complete runtime seals", |
| 231 | + "Prepared IL/OZ target already breaks parent equivalence", |
| 232 | + "Positive public birth/UM controls have externally supplied targets and words", |
| 233 | + "No autonomous selector-to-public-birth adapter is derived", |
| 234 | + "No sustained pattern, physical particle or physical correspondence is certified", |
| 235 | + ), |
| 236 | + } |
| 237 | + |
| 238 | + |
| 239 | +def main(): |
| 240 | + parser = argparse.ArgumentParser(description=__doc__) |
| 241 | + parser.add_argument("--output", type=Path, default=( |
| 242 | + ROOT / "artifacts/research/selection_birth_closure_2026_09_18.json" |
| 243 | + )) |
| 244 | + args = parser.parse_args() |
| 245 | + scope = ( |
| 246 | + "src/tnfr", "benchmarks/selection_birth_closure.py", |
| 247 | + "benchmarks/thol_birth_transport.py", "benchmarks/thol_pressure_feedback.py", |
| 248 | + "benchmarks/capacity_localization.py", |
| 249 | + ) |
| 250 | + provenance = current_git_source_provenance(ROOT, scope) |
| 251 | + sha, dirty, digest = provenance |
| 252 | + manifest = CoreExperimentManifest( |
| 253 | + claim_id="O1.b-O3.a-selection-birth-closure-audit", git_sha=sha, |
| 254 | + source_dirty=dirty, dirty_source_hash=digest, |
| 255 | + versions={"python": platform.python_version(), "networkx": nx.__version__, |
| 256 | + "numpy": np.__version__}, |
| 257 | + graph_construction="Existing marked causal C8 checkerboard preparation", |
| 258 | + capacity_specification="Unit preparation; unchanged operator factors", |
| 259 | + solver="Shared Euler; recorded held or refreshed pressure", |
| 260 | + timestep=NEXT_STEP, seed=17, result_status=ClaimStatus.MEASURED, |
| 261 | + operator_sequence=("IL", "OZ", "branch-specific recorded continuation"), |
| 262 | + telemetry=("actual proposals and commits", "birth/link support", "consumed pressure"), |
| 263 | + controls=("no THOL", "primitive versus public", "held versus refreshed", |
| 264 | + "disabled links", "stale sample"), artifacts=(str(args.output),), |
| 265 | + ) |
| 266 | + manifest.validate_for_admission() |
| 267 | + report = {"manifest": manifest.to_dict(), "source_scope": scope, **run_study()} |
| 268 | + if current_git_source_provenance(ROOT, scope) != provenance: |
| 269 | + raise RuntimeError("source changed while executing the study") |
| 270 | + args.output.parent.mkdir(parents=True, exist_ok=True) |
| 271 | + encoded = json.dumps(_payload(report), indent=2, allow_nan=False) + "\n" |
| 272 | + safe_write(args.output, lambda stream: stream.write(encoded)) |
| 273 | + print(f"Wrote bounded selection/birth audit to {args.output}") |
| 274 | + |
| 275 | + |
| 276 | +if __name__ == "__main__": |
| 277 | + main() |
0 commit comments