Repository navigation
Expand file tree
/
Copy pathexecution.py
More file actions
343 lines (279 loc) · 10.3 KB
/
Copy pathexecution.py
File metadata and controls
343 lines (279 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
"""Execution helpers for canonical TNFR programs."""
from __future__ import annotations
from collections import deque
from collections.abc import Callable, Iterable, Sequence
from typing import Any, TypeAlias, cast
from ._exact_time import finite_represented_real
from .constants import get_param
from .dynamics import step
from .flatten import _flatten
from .glyph_history import ensure_history
from .tokens import TARGET, THOL, WAIT, BlockBoundary, OpTag, Token, _program_count
from .types import Glyph, NodeId, TNFRGraph
from .utils import MAX_MATERIALIZE_DEFAULT, ensure_collection, is_non_string_sequence
from .validation import apply_glyph_with_grammar
AdvanceFn = Callable[[TNFRGraph], None]
TraceEntry = dict[str, Any]
ProgramTrace: TypeAlias = deque[TraceEntry]
HandlerFn = Callable[
[TNFRGraph, Any, Sequence[NodeId] | None, ProgramTrace, AdvanceFn],
Sequence[NodeId] | None,
]
__all__ = [
"AdvanceFn",
"CANONICAL_PRESET_NAME",
"CANONICAL_PROGRAM_TOKENS",
"HANDLERS",
"_apply_glyph_to_targets",
"_record_trace",
"compile_sequence",
"basic_canonical_example",
"block",
"play",
"seq",
"target",
"wait",
]
CANONICAL_PRESET_NAME = "canonical_example"
CANONICAL_PROGRAM_TOKENS: tuple[Token, ...] = (
Glyph.AL,
Glyph.IL,
Glyph.SHA,
)
def _window(G: TNFRGraph) -> int:
from .validation.window import validate_window
return validate_window(get_param(G, "GLYPH_HYSTERESIS_WINDOW"))
def _rejection_mode(G: TNFRGraph) -> str:
mode = G.graph.get("GRAMMAR_REJECTION_MODE", "fallback")
if type(mode) is not str:
raise TypeError("GRAMMAR_REJECTION_MODE must be 'fallback' or 'raise'")
if mode not in {"fallback", "raise"}:
raise ValueError("GRAMMAR_REJECTION_MODE must be 'fallback' or 'raise'")
return mode
def _apply_glyph_to_targets(
G: TNFRGraph, g: Glyph | str, nodes: Iterable[NodeId] | None = None
) -> None:
"""Apply the requested program glyph or reject; never substitute a glyph.
This is the native glyph route, not an atomic network stage. No future
handler context is inferred across targets or nested program scopes.
"""
nodes_iter = G.nodes() if nodes is None else nodes
w = _window(G)
mode = _rejection_mode(G)
had_mode = "GRAMMAR_REJECTION_MODE" in G.graph
G.graph["GRAMMAR_REJECTION_MODE"] = "raise"
try:
apply_glyph_with_grammar(G, nodes_iter, g, w)
finally:
if had_mode:
G.graph["GRAMMAR_REJECTION_MODE"] = mode
else:
G.graph.pop("GRAMMAR_REJECTION_MODE", None)
def _advance(G: TNFRGraph, step_fn: AdvanceFn) -> None:
step_fn(G)
def _record_trace(
trace: ProgramTrace, G: TNFRGraph, op: OpTag, **data: Any
) -> TraceEntry:
"""Append an operation snapshot to ``trace`` using graph time metadata."""
entry = {"t": _trace_time(G), "op": op.name, **data}
trace.append(entry)
return entry
def _trace_time(G: TNFRGraph) -> float:
"""Admit the retained structural clock; absence uses the zero convention."""
return finite_represented_real(G.graph.get("_t", 0.0), "program trace clock")[0]
def _advance_and_record(
G: TNFRGraph,
trace: ProgramTrace,
label: OpTag,
step_fn: AdvanceFn,
*,
times: int = 1,
**data: Any,
) -> None:
entry = _record_trace(
trace,
G,
label,
selection_origin="supplied_program",
step_origin="native_runtime" if step_fn is step else "supplied_step_function",
record_boundary="before_step",
step_status="pending",
step_calls_requested=times,
step_calls_started=0,
step_calls_completed=0,
clock_sample_available=True,
**data,
)
try:
for _ in range(times):
_trace_time(G)
entry["step_calls_started"] += 1
_advance(G, step_fn)
entry["step_calls_completed"] += 1
entry["t"] = _trace_time(G)
entry["step_status"] = "completed"
entry["record_boundary"] = "after_step"
except BaseException:
# Preserve the supplied event/request even when the following step
# fails. Callback effects remain; no invalid clock enters the trace.
entry["step_status"] = "failed"
entry["record_boundary"] = "step_failed"
try:
entry["t"] = _trace_time(G)
except Exception:
entry["clock_sample_available"] = False
raise
def _handle_target(
G: TNFRGraph,
payload: TARGET,
_curr_target: Sequence[NodeId] | None,
trace: ProgramTrace,
_step_fn: AdvanceFn,
) -> Sequence[NodeId]:
"""Handle a ``TARGET`` token and return the active node set."""
nodes_src = G.nodes() if payload.nodes is None else payload.nodes
nodes = ensure_collection(nodes_src, max_materialize=None)
if is_non_string_sequence(nodes):
curr_target = cast(Sequence[NodeId], nodes)
else:
curr_target = tuple(nodes)
_record_trace(
trace,
G,
OpTag.TARGET,
n=len(curr_target),
targets=tuple(curr_target),
selection_origin="supplied_program",
record_boundary="target_selection",
)
return curr_target
def _handle_wait(
G: TNFRGraph,
steps: int,
curr_target: Sequence[NodeId] | None,
trace: ProgramTrace,
step_fn: AdvanceFn,
) -> Sequence[NodeId] | None:
_advance_and_record(G, trace, OpTag.WAIT, step_fn, times=steps, k=steps)
return curr_target
def _handle_glyph(
G: TNFRGraph,
g: Glyph | str,
curr_target: Sequence[NodeId] | None,
trace: ProgramTrace,
step_fn: AdvanceFn,
label: OpTag = OpTag.GLYPH,
) -> Sequence[NodeId] | None:
# Resolve the actual ordered population before dispatch: a child created
# during an operation cannot retroactively become one of its targets.
targets = tuple(G.nodes()) if curr_target is None else tuple(curr_target)
# A tuple may itself be a valid node ID; dispatch an unambiguous collection.
_apply_glyph_to_targets(G, g, list(targets))
_advance_and_record(
G,
trace,
label,
step_fn,
g=g,
targets=targets,
event_time=_trace_time(G),
event_boundary="after_requested_glyph",
)
return curr_target
def _handle_thol(
G: TNFRGraph,
g: Glyph | str | None,
curr_target: Sequence[NodeId] | None,
trace: ProgramTrace,
step_fn: AdvanceFn,
) -> Sequence[NodeId] | None:
return _handle_glyph(
G, g or Glyph.THOL.value, curr_target, trace, step_fn, label=OpTag.THOL
)
HANDLERS: dict[OpTag, HandlerFn] = {
OpTag.TARGET: _handle_target,
OpTag.WAIT: _handle_wait,
OpTag.GLYPH: _handle_glyph,
OpTag.THOL: _handle_thol,
}
def play(
G: TNFRGraph, sequence: Sequence[Token], step_fn: AdvanceFn | None = None
) -> None:
"""Execute a scoped program through native glyphs and the declared step.
Syntax is admitted in full before graph mutation. Block target changes
are local to each body repetition; no child target is inferred. Explicit
glyphs reject grammar violations instead of falling back. Live failures
do not roll back already accepted operations or step-function effects.
The step function runs after every glyph, including the THOL opening and
explicit closure. WAIT requests additional calls. The default runtime
step can itself select operators and update phase/capacity; none of these
counts declares an event-free dwell, physical duration or complete law.
"""
operations = _flatten(sequence)
step_fn = step if step_fn is None else step_fn
if not callable(step_fn):
raise TypeError("step_fn must be callable")
_window(G)
_rejection_mode(G)
_trace_time(G)
maxlen = _program_count(
get_param(G, "PROGRAM_TRACE_MAXLEN"), "PROGRAM_TRACE_MAXLEN", minimum=0
)
curr_target: Sequence[NodeId] | None = None
history = ensure_history(G)
trace_obj = history.get("program_trace")
trace: ProgramTrace
if not isinstance(trace_obj, deque) or trace_obj.maxlen != maxlen:
trace = cast(ProgramTrace, deque(trace_obj or [], maxlen=maxlen))
history["program_trace"] = trace
else:
trace = cast(ProgramTrace, trace_obj)
scopes: list[tuple[BlockBoundary, Sequence[NodeId] | None]] = []
for op, payload in operations:
if op is OpTag.BLOCK_ENTER:
scopes.append((payload, curr_target))
elif op is OpTag.BLOCK_EXIT:
boundary, curr_target = scopes.pop()
if boundary != payload:
raise ValueError("Compiled block boundaries do not match")
else:
handler: HandlerFn | None = HANDLERS.get(op)
if handler is None:
raise ValueError(f"Unknown operation: {op}")
curr_target = handler(G, payload, curr_target, trace, step_fn)
continue
_record_trace(
trace,
G,
op,
path=payload.path,
iteration=payload.iteration,
repeat=payload.repeat,
)
def compile_sequence(
sequence: Iterable[Token] | Sequence[Token] | Any,
*,
max_materialize: int | None = MAX_MATERIALIZE_DEFAULT,
) -> list[tuple[OpTag, Any]]:
"""Return admitted operations, including non-operator block boundaries.
This is compilation, not complete-word or live-state admission.
"""
return _flatten(sequence, max_materialize=max_materialize)
def seq(*tokens: Token) -> list[Token]:
"""Return a mutable list of ``tokens`` for explicit sequence editing."""
return list(tokens)
def block(*tokens: Token, repeat: int = 1, close: Glyph | None = None) -> THOL:
"""Build a lexically scoped THOL block; ``close=None`` adds no glyph."""
return THOL(body=list(tokens), repeat=repeat, force_close=close)
def target(nodes: Iterable[NodeId] | None = None) -> TARGET:
"""Return a TARGET token selecting ``nodes`` (defaults to all nodes)."""
return TARGET(nodes=nodes)
def wait(steps: int = 1) -> WAIT:
"""Return a WAIT token forcing ``steps`` structural updates before resuming."""
return WAIT(steps=steps)
def basic_canonical_example() -> list[Token]:
"""Return the canonical preset sequence.
Returns a copy of the canonical preset tokens to keep CLI defaults aligned
with :func:`tnfr.config.presets.get_preset`.
"""
return list(CANONICAL_PROGRAM_TOKENS)