tnfr 4.5.1__py3-none-any.whl → 6.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tnfr/__init__.py +270 -90
- tnfr/__init__.pyi +40 -0
- tnfr/_compat.py +11 -0
- tnfr/_version.py +7 -0
- tnfr/_version.pyi +7 -0
- tnfr/alias.py +631 -0
- tnfr/alias.pyi +140 -0
- tnfr/cache.py +732 -0
- tnfr/cache.pyi +232 -0
- tnfr/callback_utils.py +381 -0
- tnfr/callback_utils.pyi +105 -0
- tnfr/cli/__init__.py +89 -0
- tnfr/cli/__init__.pyi +47 -0
- tnfr/cli/arguments.py +199 -0
- tnfr/cli/arguments.pyi +33 -0
- tnfr/cli/execution.py +322 -0
- tnfr/cli/execution.pyi +80 -0
- tnfr/cli/utils.py +34 -0
- tnfr/cli/utils.pyi +8 -0
- tnfr/config/__init__.py +12 -0
- tnfr/config/__init__.pyi +8 -0
- tnfr/config/constants.py +104 -0
- tnfr/config/constants.pyi +12 -0
- tnfr/config/init.py +36 -0
- tnfr/config/init.pyi +8 -0
- tnfr/config/operator_names.py +106 -0
- tnfr/config/operator_names.pyi +28 -0
- tnfr/config/presets.py +104 -0
- tnfr/config/presets.pyi +7 -0
- tnfr/constants/__init__.py +228 -0
- tnfr/constants/__init__.pyi +104 -0
- tnfr/constants/core.py +158 -0
- tnfr/constants/core.pyi +17 -0
- tnfr/constants/init.py +31 -0
- tnfr/constants/init.pyi +12 -0
- tnfr/constants/metric.py +102 -0
- tnfr/constants/metric.pyi +19 -0
- tnfr/constants_glyphs.py +16 -0
- tnfr/constants_glyphs.pyi +12 -0
- tnfr/dynamics/__init__.py +136 -0
- tnfr/dynamics/__init__.pyi +83 -0
- tnfr/dynamics/adaptation.py +201 -0
- tnfr/dynamics/aliases.py +22 -0
- tnfr/dynamics/coordination.py +343 -0
- tnfr/dynamics/dnfr.py +2315 -0
- tnfr/dynamics/dnfr.pyi +33 -0
- tnfr/dynamics/integrators.py +561 -0
- tnfr/dynamics/integrators.pyi +35 -0
- tnfr/dynamics/runtime.py +521 -0
- tnfr/dynamics/sampling.py +34 -0
- tnfr/dynamics/sampling.pyi +7 -0
- tnfr/dynamics/selectors.py +680 -0
- tnfr/execution.py +216 -0
- tnfr/execution.pyi +65 -0
- tnfr/flatten.py +283 -0
- tnfr/flatten.pyi +28 -0
- tnfr/gamma.py +320 -89
- tnfr/gamma.pyi +40 -0
- tnfr/glyph_history.py +337 -0
- tnfr/glyph_history.pyi +53 -0
- tnfr/grammar.py +23 -153
- tnfr/grammar.pyi +13 -0
- tnfr/helpers/__init__.py +151 -0
- tnfr/helpers/__init__.pyi +66 -0
- tnfr/helpers/numeric.py +88 -0
- tnfr/helpers/numeric.pyi +12 -0
- tnfr/immutable.py +214 -0
- tnfr/immutable.pyi +37 -0
- tnfr/initialization.py +199 -0
- tnfr/initialization.pyi +73 -0
- tnfr/io.py +311 -0
- tnfr/io.pyi +11 -0
- tnfr/locking.py +37 -0
- tnfr/locking.pyi +7 -0
- tnfr/metrics/__init__.py +41 -0
- tnfr/metrics/__init__.pyi +20 -0
- tnfr/metrics/coherence.py +1469 -0
- tnfr/metrics/common.py +149 -0
- tnfr/metrics/common.pyi +15 -0
- tnfr/metrics/core.py +259 -0
- tnfr/metrics/core.pyi +13 -0
- tnfr/metrics/diagnosis.py +840 -0
- tnfr/metrics/diagnosis.pyi +89 -0
- tnfr/metrics/export.py +151 -0
- tnfr/metrics/glyph_timing.py +369 -0
- tnfr/metrics/reporting.py +152 -0
- tnfr/metrics/reporting.pyi +12 -0
- tnfr/metrics/sense_index.py +294 -0
- tnfr/metrics/sense_index.pyi +9 -0
- tnfr/metrics/trig.py +216 -0
- tnfr/metrics/trig.pyi +12 -0
- tnfr/metrics/trig_cache.py +105 -0
- tnfr/metrics/trig_cache.pyi +10 -0
- tnfr/node.py +255 -177
- tnfr/node.pyi +161 -0
- tnfr/observers.py +154 -150
- tnfr/observers.pyi +46 -0
- tnfr/ontosim.py +135 -134
- tnfr/ontosim.pyi +33 -0
- tnfr/operators/__init__.py +452 -0
- tnfr/operators/__init__.pyi +31 -0
- tnfr/operators/definitions.py +181 -0
- tnfr/operators/definitions.pyi +92 -0
- tnfr/operators/jitter.py +266 -0
- tnfr/operators/jitter.pyi +11 -0
- tnfr/operators/registry.py +80 -0
- tnfr/operators/registry.pyi +15 -0
- tnfr/operators/remesh.py +569 -0
- tnfr/presets.py +10 -23
- tnfr/presets.pyi +7 -0
- tnfr/py.typed +0 -0
- tnfr/rng.py +440 -0
- tnfr/rng.pyi +14 -0
- tnfr/selector.py +217 -0
- tnfr/selector.pyi +19 -0
- tnfr/sense.py +307 -142
- tnfr/sense.pyi +30 -0
- tnfr/structural.py +69 -164
- tnfr/structural.pyi +46 -0
- tnfr/telemetry/__init__.py +13 -0
- tnfr/telemetry/verbosity.py +37 -0
- tnfr/tokens.py +61 -0
- tnfr/tokens.pyi +41 -0
- tnfr/trace.py +520 -95
- tnfr/trace.pyi +68 -0
- tnfr/types.py +382 -17
- tnfr/types.pyi +145 -0
- tnfr/utils/__init__.py +158 -0
- tnfr/utils/__init__.pyi +133 -0
- tnfr/utils/cache.py +755 -0
- tnfr/utils/cache.pyi +156 -0
- tnfr/utils/data.py +267 -0
- tnfr/utils/data.pyi +73 -0
- tnfr/utils/graph.py +87 -0
- tnfr/utils/graph.pyi +10 -0
- tnfr/utils/init.py +746 -0
- tnfr/utils/init.pyi +85 -0
- tnfr/utils/io.py +157 -0
- tnfr/utils/io.pyi +10 -0
- tnfr/utils/validators.py +130 -0
- tnfr/utils/validators.pyi +19 -0
- tnfr/validation/__init__.py +25 -0
- tnfr/validation/__init__.pyi +17 -0
- tnfr/validation/compatibility.py +59 -0
- tnfr/validation/compatibility.pyi +8 -0
- tnfr/validation/grammar.py +149 -0
- tnfr/validation/grammar.pyi +11 -0
- tnfr/validation/rules.py +194 -0
- tnfr/validation/rules.pyi +18 -0
- tnfr/validation/syntax.py +151 -0
- tnfr/validation/syntax.pyi +7 -0
- tnfr-6.0.0.dist-info/METADATA +135 -0
- tnfr-6.0.0.dist-info/RECORD +157 -0
- tnfr/cli.py +0 -322
- tnfr/config.py +0 -41
- tnfr/constants.py +0 -277
- tnfr/dynamics.py +0 -814
- tnfr/helpers.py +0 -264
- tnfr/main.py +0 -47
- tnfr/metrics.py +0 -597
- tnfr/operators.py +0 -525
- tnfr/program.py +0 -176
- tnfr/scenarios.py +0 -34
- tnfr/validators.py +0 -38
- tnfr-4.5.1.dist-info/METADATA +0 -221
- tnfr-4.5.1.dist-info/RECORD +0 -28
- {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/WHEEL +0 -0
- {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/entry_points.txt +0 -0
- {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/licenses/LICENSE.md +0 -0
- {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/top_level.txt +0 -0
tnfr/trace.py
CHANGED
|
@@ -1,134 +1,559 @@
|
|
|
1
|
+
"""Trace logging.
|
|
2
|
+
|
|
3
|
+
Field helpers avoid unnecessary copying by reusing dictionaries stored on
|
|
4
|
+
the graph whenever possible. Callers are expected to treat returned
|
|
5
|
+
structures as immutable snapshots.
|
|
6
|
+
"""
|
|
7
|
+
|
|
1
8
|
from __future__ import annotations
|
|
2
|
-
from typing import Any, Dict, List, Optional
|
|
3
|
-
from collections import Counter
|
|
4
9
|
|
|
5
|
-
|
|
6
|
-
|
|
10
|
+
import warnings
|
|
11
|
+
|
|
12
|
+
from typing import Any, Callable, Protocol, NamedTuple, TypedDict, cast
|
|
13
|
+
from collections.abc import Iterable, Mapping
|
|
14
|
+
from types import MappingProxyType
|
|
15
|
+
|
|
16
|
+
from .constants import TRACE
|
|
17
|
+
from .glyph_history import ensure_history, count_glyphs, append_metric
|
|
18
|
+
from .utils import cached_import, get_graph_mapping, is_non_string_sequence
|
|
19
|
+
from .metrics.sense_index import _normalise_si_sensitivity_mapping
|
|
20
|
+
from .telemetry.verbosity import (
|
|
21
|
+
TelemetryVerbosity,
|
|
22
|
+
TELEMETRY_VERBOSITY_DEFAULT,
|
|
23
|
+
)
|
|
24
|
+
from .types import (
|
|
25
|
+
SigmaVector,
|
|
26
|
+
TNFRGraph,
|
|
27
|
+
TraceCallback,
|
|
28
|
+
TraceFieldFn,
|
|
29
|
+
TraceFieldMap,
|
|
30
|
+
TraceFieldRegistry,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class _KuramotoFn(Protocol):
|
|
35
|
+
def __call__(self, G: TNFRGraph) -> tuple[float, float]:
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class _SigmaVectorFn(Protocol):
|
|
40
|
+
def __call__(
|
|
41
|
+
self, G: TNFRGraph, weight_mode: str | None = None
|
|
42
|
+
) -> SigmaVector:
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class CallbackSpec(NamedTuple):
|
|
47
|
+
"""Specification for a registered callback."""
|
|
48
|
+
|
|
49
|
+
name: str | None
|
|
50
|
+
func: Callable[..., Any]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TraceMetadata(TypedDict, total=False):
|
|
54
|
+
"""Metadata captured by trace field functions."""
|
|
55
|
+
|
|
56
|
+
gamma: Mapping[str, Any]
|
|
57
|
+
grammar: Mapping[str, Any]
|
|
58
|
+
selector: str | None
|
|
59
|
+
dnfr_weights: Mapping[str, Any]
|
|
60
|
+
si_weights: Mapping[str, Any]
|
|
61
|
+
si_sensitivity: Mapping[str, Any]
|
|
62
|
+
callbacks: Mapping[str, list[str] | None]
|
|
63
|
+
thol_open_nodes: int
|
|
64
|
+
kuramoto: Mapping[str, float]
|
|
65
|
+
sigma: Mapping[str, float]
|
|
66
|
+
glyphs: Mapping[str, int]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class TraceSnapshot(TraceMetadata, total=False):
|
|
70
|
+
"""Trace snapshot stored in the history."""
|
|
71
|
+
|
|
72
|
+
t: float
|
|
73
|
+
phase: str
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class TraceFieldSpec(NamedTuple):
|
|
77
|
+
"""Declarative specification for a trace field producer."""
|
|
78
|
+
|
|
79
|
+
name: str
|
|
80
|
+
phase: str
|
|
81
|
+
producer: TraceFieldFn
|
|
82
|
+
tiers: tuple[TelemetryVerbosity, ...]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
TRACE_VERBOSITY_DEFAULT = TELEMETRY_VERBOSITY_DEFAULT
|
|
86
|
+
TRACE_VERBOSITY_PRESETS: dict[str, tuple[str, ...]] = {}
|
|
87
|
+
_TRACE_CAPTURE_ALIASES: Mapping[str, str] = MappingProxyType(
|
|
88
|
+
{
|
|
89
|
+
"glyphs": "glyph_counts",
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _canonical_capture_name(name: str) -> str:
|
|
95
|
+
"""Return the canonical capture field name for ``name``."""
|
|
96
|
+
|
|
97
|
+
stripped = name.strip()
|
|
98
|
+
alias = _TRACE_CAPTURE_ALIASES.get(stripped)
|
|
99
|
+
if alias is not None:
|
|
100
|
+
return alias
|
|
101
|
+
|
|
102
|
+
lowered = stripped.lower()
|
|
103
|
+
alias = _TRACE_CAPTURE_ALIASES.get(lowered)
|
|
104
|
+
if alias is not None:
|
|
105
|
+
return alias
|
|
106
|
+
|
|
107
|
+
return stripped
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _normalise_capture_spec(raw: Any) -> set[str]:
|
|
111
|
+
"""Coerce custom capture payloads to a ``set`` of field names."""
|
|
7
112
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
113
|
+
if raw is None:
|
|
114
|
+
return set()
|
|
115
|
+
if isinstance(raw, Mapping):
|
|
116
|
+
return {_canonical_capture_name(str(name)) for name in raw.keys()}
|
|
117
|
+
if isinstance(raw, str):
|
|
118
|
+
return {_canonical_capture_name(raw)}
|
|
119
|
+
if isinstance(raw, Iterable):
|
|
120
|
+
return {_canonical_capture_name(str(name)) for name in raw}
|
|
121
|
+
return {_canonical_capture_name(str(raw))}
|
|
13
122
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
123
|
+
|
|
124
|
+
def _resolve_trace_capture(cfg: Mapping[str, Any]) -> set[str]:
|
|
125
|
+
"""Return the capture set declared by ``cfg`` respecting verbosity."""
|
|
126
|
+
|
|
127
|
+
if "capture" in cfg:
|
|
128
|
+
return _normalise_capture_spec(cfg.get("capture"))
|
|
129
|
+
|
|
130
|
+
raw_verbosity = cfg.get("verbosity", TRACE_VERBOSITY_DEFAULT)
|
|
131
|
+
verbosity = str(raw_verbosity).lower()
|
|
132
|
+
fields = TRACE_VERBOSITY_PRESETS.get(verbosity)
|
|
133
|
+
if fields is None:
|
|
134
|
+
warnings.warn(
|
|
135
|
+
(
|
|
136
|
+
"Unknown TRACE verbosity %r; falling back to %s"
|
|
137
|
+
% (raw_verbosity, TRACE_VERBOSITY_DEFAULT)
|
|
138
|
+
),
|
|
139
|
+
UserWarning,
|
|
140
|
+
stacklevel=3,
|
|
141
|
+
)
|
|
142
|
+
fields = TRACE_VERBOSITY_PRESETS[TRACE_VERBOSITY_DEFAULT]
|
|
143
|
+
return set(fields)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _kuramoto_fallback(G: TNFRGraph) -> tuple[float, float]:
|
|
147
|
+
return 0.0, 0.0
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
kuramoto_R_psi: _KuramotoFn = cast(
|
|
151
|
+
_KuramotoFn,
|
|
152
|
+
cached_import("tnfr.gamma", "kuramoto_R_psi", fallback=_kuramoto_fallback),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _sigma_fallback(
|
|
157
|
+
G: TNFRGraph, _weight_mode: str | None = None
|
|
158
|
+
) -> SigmaVector:
|
|
159
|
+
"""Return a null sigma vector regardless of ``_weight_mode``."""
|
|
160
|
+
|
|
161
|
+
return {"x": 0.0, "y": 0.0, "mag": 0.0, "angle": 0.0, "n": 0}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# Public exports for this module
|
|
165
|
+
__all__ = (
|
|
166
|
+
"CallbackSpec",
|
|
167
|
+
"TraceFieldSpec",
|
|
168
|
+
"TraceMetadata",
|
|
169
|
+
"TraceSnapshot",
|
|
170
|
+
"register_trace",
|
|
171
|
+
"register_trace_field",
|
|
172
|
+
"_callback_names",
|
|
173
|
+
"gamma_field",
|
|
174
|
+
"grammar_field",
|
|
175
|
+
)
|
|
19
176
|
|
|
20
177
|
# -------------------------
|
|
21
|
-
#
|
|
178
|
+
# Helpers
|
|
22
179
|
# -------------------------
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _trace_setup(
|
|
183
|
+
G: TNFRGraph,
|
|
184
|
+
) -> tuple[
|
|
185
|
+
Mapping[str, Any] | None,
|
|
186
|
+
set[str],
|
|
187
|
+
dict[str, Any] | None,
|
|
188
|
+
str | None,
|
|
189
|
+
]:
|
|
190
|
+
"""Common configuration for trace snapshots.
|
|
191
|
+
|
|
192
|
+
Returns the active configuration, capture set, history and key under
|
|
193
|
+
which metadata will be stored. If tracing is disabled returns
|
|
194
|
+
``(None, set(), None, None)``.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
cfg_raw = G.graph.get("TRACE", TRACE)
|
|
198
|
+
cfg = cfg_raw if isinstance(cfg_raw, Mapping) else TRACE
|
|
199
|
+
if not cfg.get("enabled", True):
|
|
200
|
+
return None, set(), None, None
|
|
201
|
+
|
|
202
|
+
capture = _resolve_trace_capture(cfg)
|
|
203
|
+
hist = ensure_history(G)
|
|
204
|
+
key = cast(str | None, cfg.get("history_key", "trace_meta"))
|
|
205
|
+
return cfg, capture, hist, key
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _callback_names(
|
|
209
|
+
callbacks: Mapping[str, CallbackSpec] | Iterable[CallbackSpec],
|
|
210
|
+
) -> list[str]:
|
|
211
|
+
"""Return callback names from ``callbacks``."""
|
|
212
|
+
if isinstance(callbacks, Mapping):
|
|
213
|
+
callbacks = callbacks.values()
|
|
214
|
+
return [
|
|
215
|
+
cb.name
|
|
216
|
+
if cb.name is not None
|
|
217
|
+
else str(getattr(cb.func, "__name__", "fn"))
|
|
218
|
+
for cb in callbacks
|
|
219
|
+
]
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
EMPTY_MAPPING: Mapping[str, Any] = MappingProxyType({})
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def mapping_field(G: TNFRGraph, graph_key: str, out_key: str) -> TraceMetadata:
|
|
226
|
+
"""Helper to copy mappings from ``G.graph`` into trace output."""
|
|
227
|
+
mapping = get_graph_mapping(
|
|
228
|
+
G, graph_key, f"G.graph[{graph_key!r}] is not a mapping; ignoring"
|
|
229
|
+
)
|
|
230
|
+
if mapping is None:
|
|
231
|
+
return {}
|
|
232
|
+
return {out_key: mapping}
|
|
233
|
+
|
|
28
234
|
|
|
29
235
|
# -------------------------
|
|
30
|
-
#
|
|
236
|
+
# Builders
|
|
31
237
|
# -------------------------
|
|
32
238
|
|
|
239
|
+
|
|
240
|
+
def _new_trace_meta(
|
|
241
|
+
G: TNFRGraph, phase: str
|
|
242
|
+
) -> tuple[TraceSnapshot, set[str], dict[str, Any] | None, str | None] | None:
|
|
243
|
+
"""Initialise trace metadata for a ``phase``.
|
|
244
|
+
|
|
245
|
+
Wraps :func:`_trace_setup` and creates the base structure with timestamp
|
|
246
|
+
and current phase. Returns ``None`` if tracing is disabled.
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
cfg, capture, hist, key = _trace_setup(G)
|
|
250
|
+
if not cfg:
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
meta: TraceSnapshot = {"t": float(G.graph.get("_t", 0.0)), "phase": phase}
|
|
254
|
+
return meta, capture, hist, key
|
|
255
|
+
|
|
256
|
+
|
|
33
257
|
# -------------------------
|
|
34
258
|
# Snapshots
|
|
35
259
|
# -------------------------
|
|
36
260
|
|
|
37
|
-
|
|
38
|
-
|
|
261
|
+
|
|
262
|
+
def _trace_capture(
|
|
263
|
+
G: TNFRGraph, phase: str, fields: TraceFieldMap
|
|
264
|
+
) -> None:
|
|
265
|
+
"""Capture ``fields`` for ``phase`` and store the snapshot.
|
|
266
|
+
|
|
267
|
+
A :class:`TraceSnapshot` is appended to the configured history when
|
|
268
|
+
tracing is active. If there is no active history or storage key the
|
|
269
|
+
capture is silently ignored.
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
res = _new_trace_meta(G, phase)
|
|
273
|
+
if not res:
|
|
39
274
|
return
|
|
40
|
-
cfg = G.graph.get("TRACE", DEFAULTS["TRACE"])
|
|
41
|
-
capture: List[str] = list(cfg.get("capture", []))
|
|
42
|
-
hist = ensure_history(G)
|
|
43
|
-
key = cfg.get("history_key", "trace_meta")
|
|
44
275
|
|
|
45
|
-
meta
|
|
276
|
+
meta, capture, hist, key = res
|
|
277
|
+
if not capture:
|
|
278
|
+
return
|
|
279
|
+
for name, getter in fields.items():
|
|
280
|
+
if name in capture:
|
|
281
|
+
meta.update(getter(G))
|
|
282
|
+
if hist is None or key is None:
|
|
283
|
+
return
|
|
284
|
+
append_metric(hist, key, meta)
|
|
285
|
+
|
|
46
286
|
|
|
47
|
-
|
|
48
|
-
|
|
287
|
+
# -------------------------
|
|
288
|
+
# Registry
|
|
289
|
+
# -------------------------
|
|
49
290
|
|
|
50
|
-
if "grammar" in capture:
|
|
51
|
-
meta["grammar"] = dict(G.graph.get("GRAMMAR_CANON", {}))
|
|
52
291
|
|
|
53
|
-
|
|
54
|
-
sel = G.graph.get("glyph_selector")
|
|
55
|
-
meta["selector"] = getattr(sel, "__name__", str(sel)) if sel else None
|
|
292
|
+
TRACE_FIELDS: TraceFieldRegistry = {}
|
|
56
293
|
|
|
57
|
-
if "dnfr_weights" in capture:
|
|
58
|
-
mix = G.graph.get("DNFR_WEIGHTS")
|
|
59
|
-
if isinstance(mix, dict):
|
|
60
|
-
meta["dnfr_weights"] = dict(mix)
|
|
61
294
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
295
|
+
def register_trace_field(
|
|
296
|
+
phase: str, name: str, func: TraceFieldFn
|
|
297
|
+
) -> None:
|
|
298
|
+
"""Register ``func`` to populate trace field ``name`` during ``phase``."""
|
|
65
299
|
|
|
66
|
-
|
|
67
|
-
# si el motor guarda los callbacks, exponer nombres por fase
|
|
68
|
-
cb = G.graph.get("_callbacks")
|
|
69
|
-
if isinstance(cb, dict):
|
|
70
|
-
out = {k: [getattr(f, "__name__", "fn") for (_, f, *_rest) in v] if isinstance(v, list) else None for k, v in cb.items()}
|
|
71
|
-
meta["callbacks"] = out
|
|
300
|
+
TRACE_FIELDS.setdefault(phase, {})[name] = func
|
|
72
301
|
|
|
73
|
-
if "thol_state" in capture:
|
|
74
|
-
# cuántos nodos tienen bloque T’HOL abierto
|
|
75
|
-
th_open = 0
|
|
76
|
-
for n in G.nodes():
|
|
77
|
-
st = G.nodes[n].get("_GRAM", {})
|
|
78
|
-
if st.get("thol_open", False):
|
|
79
|
-
th_open += 1
|
|
80
|
-
meta["thol_open_nodes"] = th_open
|
|
81
302
|
|
|
82
|
-
|
|
303
|
+
def gamma_field(G: TNFRGraph) -> TraceMetadata:
|
|
304
|
+
return mapping_field(G, "GAMMA", "gamma")
|
|
83
305
|
|
|
84
306
|
|
|
85
|
-
def
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
307
|
+
def grammar_field(G: TNFRGraph) -> TraceMetadata:
|
|
308
|
+
return mapping_field(G, "GRAMMAR_CANON", "grammar")
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def dnfr_weights_field(G: TNFRGraph) -> TraceMetadata:
|
|
312
|
+
return mapping_field(G, "DNFR_WEIGHTS", "dnfr_weights")
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def selector_field(G: TNFRGraph) -> TraceMetadata:
|
|
316
|
+
sel = G.graph.get("glyph_selector")
|
|
317
|
+
selector_name = getattr(sel, "__name__", str(sel)) if sel else None
|
|
318
|
+
return {"selector": selector_name}
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _si_weights_field(G: TNFRGraph) -> TraceMetadata:
|
|
322
|
+
weights = mapping_field(G, "_Si_weights", "si_weights")
|
|
323
|
+
if weights:
|
|
324
|
+
return weights
|
|
325
|
+
return {"si_weights": EMPTY_MAPPING}
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _si_sensitivity_field(G: TNFRGraph) -> TraceMetadata:
|
|
329
|
+
mapping = get_graph_mapping(
|
|
330
|
+
G,
|
|
331
|
+
"_Si_sensitivity",
|
|
332
|
+
"G.graph['_Si_sensitivity'] is not a mapping; ignoring",
|
|
333
|
+
)
|
|
334
|
+
if mapping is None:
|
|
335
|
+
return {"si_sensitivity": EMPTY_MAPPING}
|
|
336
|
+
|
|
337
|
+
normalised = _normalise_si_sensitivity_mapping(mapping, warn=True)
|
|
92
338
|
|
|
93
|
-
|
|
339
|
+
if normalised != mapping:
|
|
340
|
+
G.graph["_Si_sensitivity"] = normalised
|
|
94
341
|
|
|
95
|
-
|
|
96
|
-
R, psi = kuramoto_R_psi(G)
|
|
97
|
-
meta["kuramoto"] = {"R": float(R), "psi": float(psi)}
|
|
342
|
+
return {"si_sensitivity": MappingProxyType(normalised)}
|
|
98
343
|
|
|
99
|
-
if "sigma" in capture:
|
|
100
|
-
sv = sigma_vector_global(G)
|
|
101
|
-
meta["sigma"] = {"x": float(sv.get("x", 1.0)), "y": float(sv.get("y", 0.0)), "mag": float(sv.get("mag", 1.0)), "angle": float(sv.get("angle", 0.0))}
|
|
102
344
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
for n in G.nodes():
|
|
106
|
-
g = last_glifo(G.nodes[n])
|
|
107
|
-
if g:
|
|
108
|
-
cnt[g] += 1
|
|
109
|
-
meta["glifos"] = dict(cnt)
|
|
345
|
+
def si_weights_field(G: TNFRGraph) -> TraceMetadata:
|
|
346
|
+
"""Return sense-plane weights and sensitivity."""
|
|
110
347
|
|
|
111
|
-
|
|
348
|
+
weights = _si_weights_field(G)
|
|
349
|
+
sensitivity = _si_sensitivity_field(G)
|
|
350
|
+
return {**weights, **sensitivity}
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def callbacks_field(G: TNFRGraph) -> TraceMetadata:
|
|
354
|
+
cb = G.graph.get("callbacks")
|
|
355
|
+
if not isinstance(cb, Mapping):
|
|
356
|
+
return {}
|
|
357
|
+
out: dict[str, list[str] | None] = {}
|
|
358
|
+
for phase, cb_map in cb.items():
|
|
359
|
+
if isinstance(cb_map, Mapping) or is_non_string_sequence(cb_map):
|
|
360
|
+
out[phase] = _callback_names(cb_map)
|
|
361
|
+
else:
|
|
362
|
+
out[phase] = None
|
|
363
|
+
return {"callbacks": out}
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def thol_state_field(G: TNFRGraph) -> TraceMetadata:
|
|
367
|
+
th_open = 0
|
|
368
|
+
for _, nd in G.nodes(data=True):
|
|
369
|
+
st = nd.get("_GRAM", {})
|
|
370
|
+
if st.get("thol_open", False):
|
|
371
|
+
th_open += 1
|
|
372
|
+
return {"thol_open_nodes": th_open}
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def kuramoto_field(G: TNFRGraph) -> TraceMetadata:
|
|
376
|
+
R, psi = kuramoto_R_psi(G)
|
|
377
|
+
return {"kuramoto": {"R": float(R), "psi": float(psi)}}
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def sigma_field(G: TNFRGraph) -> TraceMetadata:
|
|
381
|
+
sigma_vector_from_graph: _SigmaVectorFn = cast(
|
|
382
|
+
_SigmaVectorFn,
|
|
383
|
+
cached_import(
|
|
384
|
+
"tnfr.sense",
|
|
385
|
+
"sigma_vector_from_graph",
|
|
386
|
+
fallback=_sigma_fallback,
|
|
387
|
+
),
|
|
388
|
+
)
|
|
389
|
+
sv = sigma_vector_from_graph(G)
|
|
390
|
+
return {
|
|
391
|
+
"sigma": {
|
|
392
|
+
"x": float(sv.get("x", 0.0)),
|
|
393
|
+
"y": float(sv.get("y", 0.0)),
|
|
394
|
+
"mag": float(sv.get("mag", 0.0)),
|
|
395
|
+
"angle": float(sv.get("angle", 0.0)),
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def glyph_counts_field(G: TNFRGraph) -> TraceMetadata:
|
|
401
|
+
"""Return glyph count snapshot.
|
|
402
|
+
|
|
403
|
+
``count_glyphs`` already produces a fresh mapping so no additional copy
|
|
404
|
+
is taken. Treat the returned mapping as read-only.
|
|
405
|
+
"""
|
|
406
|
+
|
|
407
|
+
cnt = count_glyphs(G, window=1)
|
|
408
|
+
return {"glyphs": cnt}
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
TRACE_FIELD_SPECS: tuple[TraceFieldSpec, ...] = (
|
|
412
|
+
TraceFieldSpec(
|
|
413
|
+
name="gamma",
|
|
414
|
+
phase="before",
|
|
415
|
+
producer=gamma_field,
|
|
416
|
+
tiers=(
|
|
417
|
+
TelemetryVerbosity.BASIC,
|
|
418
|
+
TelemetryVerbosity.DETAILED,
|
|
419
|
+
TelemetryVerbosity.DEBUG,
|
|
420
|
+
),
|
|
421
|
+
),
|
|
422
|
+
TraceFieldSpec(
|
|
423
|
+
name="grammar",
|
|
424
|
+
phase="before",
|
|
425
|
+
producer=grammar_field,
|
|
426
|
+
tiers=(
|
|
427
|
+
TelemetryVerbosity.BASIC,
|
|
428
|
+
TelemetryVerbosity.DETAILED,
|
|
429
|
+
TelemetryVerbosity.DEBUG,
|
|
430
|
+
),
|
|
431
|
+
),
|
|
432
|
+
TraceFieldSpec(
|
|
433
|
+
name="selector",
|
|
434
|
+
phase="before",
|
|
435
|
+
producer=selector_field,
|
|
436
|
+
tiers=(
|
|
437
|
+
TelemetryVerbosity.BASIC,
|
|
438
|
+
TelemetryVerbosity.DETAILED,
|
|
439
|
+
TelemetryVerbosity.DEBUG,
|
|
440
|
+
),
|
|
441
|
+
),
|
|
442
|
+
TraceFieldSpec(
|
|
443
|
+
name="dnfr_weights",
|
|
444
|
+
phase="before",
|
|
445
|
+
producer=dnfr_weights_field,
|
|
446
|
+
tiers=(
|
|
447
|
+
TelemetryVerbosity.BASIC,
|
|
448
|
+
TelemetryVerbosity.DETAILED,
|
|
449
|
+
TelemetryVerbosity.DEBUG,
|
|
450
|
+
),
|
|
451
|
+
),
|
|
452
|
+
TraceFieldSpec(
|
|
453
|
+
name="si_weights",
|
|
454
|
+
phase="before",
|
|
455
|
+
producer=si_weights_field,
|
|
456
|
+
tiers=(
|
|
457
|
+
TelemetryVerbosity.BASIC,
|
|
458
|
+
TelemetryVerbosity.DETAILED,
|
|
459
|
+
TelemetryVerbosity.DEBUG,
|
|
460
|
+
),
|
|
461
|
+
),
|
|
462
|
+
TraceFieldSpec(
|
|
463
|
+
name="callbacks",
|
|
464
|
+
phase="before",
|
|
465
|
+
producer=callbacks_field,
|
|
466
|
+
tiers=(
|
|
467
|
+
TelemetryVerbosity.BASIC,
|
|
468
|
+
TelemetryVerbosity.DETAILED,
|
|
469
|
+
TelemetryVerbosity.DEBUG,
|
|
470
|
+
),
|
|
471
|
+
),
|
|
472
|
+
TraceFieldSpec(
|
|
473
|
+
name="thol_open_nodes",
|
|
474
|
+
phase="before",
|
|
475
|
+
producer=thol_state_field,
|
|
476
|
+
tiers=(
|
|
477
|
+
TelemetryVerbosity.BASIC,
|
|
478
|
+
TelemetryVerbosity.DETAILED,
|
|
479
|
+
TelemetryVerbosity.DEBUG,
|
|
480
|
+
),
|
|
481
|
+
),
|
|
482
|
+
TraceFieldSpec(
|
|
483
|
+
name="kuramoto",
|
|
484
|
+
phase="after",
|
|
485
|
+
producer=kuramoto_field,
|
|
486
|
+
tiers=(TelemetryVerbosity.DETAILED, TelemetryVerbosity.DEBUG),
|
|
487
|
+
),
|
|
488
|
+
TraceFieldSpec(
|
|
489
|
+
name="sigma",
|
|
490
|
+
phase="after",
|
|
491
|
+
producer=sigma_field,
|
|
492
|
+
tiers=(TelemetryVerbosity.DETAILED, TelemetryVerbosity.DEBUG),
|
|
493
|
+
),
|
|
494
|
+
TraceFieldSpec(
|
|
495
|
+
name="glyph_counts",
|
|
496
|
+
phase="after",
|
|
497
|
+
producer=glyph_counts_field,
|
|
498
|
+
tiers=(TelemetryVerbosity.DEBUG,),
|
|
499
|
+
),
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
TRACE_VERBOSITY_PRESETS = {
|
|
503
|
+
level.value: tuple(
|
|
504
|
+
spec.name for spec in TRACE_FIELD_SPECS if level in spec.tiers
|
|
505
|
+
)
|
|
506
|
+
for level in TelemetryVerbosity
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
for spec in TRACE_FIELD_SPECS:
|
|
510
|
+
register_trace_field(spec.phase, spec.name, spec.producer)
|
|
112
511
|
|
|
113
512
|
|
|
114
513
|
# -------------------------
|
|
115
514
|
# API
|
|
116
515
|
# -------------------------
|
|
117
516
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
-
|
|
131
|
-
|
|
517
|
+
|
|
518
|
+
def register_trace(G: TNFRGraph) -> None:
|
|
519
|
+
"""Enable before/after-step snapshots and dump operational metadata
|
|
520
|
+
to history.
|
|
521
|
+
|
|
522
|
+
Trace snapshots are stored as :class:`TraceSnapshot` entries in
|
|
523
|
+
``G.graph['history'][TRACE.history_key]`` with:
|
|
524
|
+
- gamma: active Γi(R) specification
|
|
525
|
+
- grammar: canonical grammar configuration
|
|
526
|
+
- selector: glyph selector name
|
|
527
|
+
- dnfr_weights: ΔNFR mix declared in the engine
|
|
528
|
+
- si_weights: α/β/γ weights and Si sensitivity
|
|
529
|
+
- callbacks: callbacks registered per phase (if in
|
|
530
|
+
``G.graph['callbacks']``)
|
|
531
|
+
- thol_open_nodes: how many nodes have an open THOL block
|
|
532
|
+
- kuramoto: network ``(R, ψ)``
|
|
533
|
+
- sigma: global sense-plane vector
|
|
534
|
+
- glyphs: glyph counts after the step
|
|
535
|
+
|
|
536
|
+
Field helpers reuse graph dictionaries and expect them to be treated as
|
|
537
|
+
immutable snapshots by consumers.
|
|
132
538
|
"""
|
|
133
|
-
|
|
134
|
-
|
|
539
|
+
if G.graph.get("_trace_registered"):
|
|
540
|
+
return
|
|
541
|
+
|
|
542
|
+
from .callback_utils import callback_manager
|
|
543
|
+
|
|
544
|
+
for phase in TRACE_FIELDS.keys():
|
|
545
|
+
event = f"{phase}_step"
|
|
546
|
+
|
|
547
|
+
def _make_cb(ph: str) -> TraceCallback:
|
|
548
|
+
def _cb(graph: TNFRGraph, ctx: dict[str, Any]) -> None:
|
|
549
|
+
del ctx
|
|
550
|
+
|
|
551
|
+
_trace_capture(graph, ph, TRACE_FIELDS.get(ph, {}))
|
|
552
|
+
|
|
553
|
+
return _cb
|
|
554
|
+
|
|
555
|
+
callback_manager.register_callback(
|
|
556
|
+
G, event=event, func=_make_cb(phase), name=f"trace_{phase}"
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
G.graph["_trace_registered"] = True
|