tnfr 4.5.2__py3-none-any.whl → 7.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.

Potentially problematic release.


This version of tnfr might be problematic. Click here for more details.

Files changed (195) hide show
  1. tnfr/__init__.py +275 -51
  2. tnfr/__init__.pyi +33 -0
  3. tnfr/_compat.py +10 -0
  4. tnfr/_generated_version.py +34 -0
  5. tnfr/_version.py +49 -0
  6. tnfr/_version.pyi +7 -0
  7. tnfr/alias.py +117 -31
  8. tnfr/alias.pyi +108 -0
  9. tnfr/cache.py +6 -572
  10. tnfr/cache.pyi +16 -0
  11. tnfr/callback_utils.py +16 -38
  12. tnfr/callback_utils.pyi +79 -0
  13. tnfr/cli/__init__.py +34 -14
  14. tnfr/cli/__init__.pyi +26 -0
  15. tnfr/cli/arguments.py +211 -28
  16. tnfr/cli/arguments.pyi +27 -0
  17. tnfr/cli/execution.py +470 -50
  18. tnfr/cli/execution.pyi +70 -0
  19. tnfr/cli/utils.py +18 -3
  20. tnfr/cli/utils.pyi +8 -0
  21. tnfr/config/__init__.py +13 -0
  22. tnfr/config/__init__.pyi +10 -0
  23. tnfr/{constants_glyphs.py → config/constants.py} +26 -20
  24. tnfr/config/constants.pyi +12 -0
  25. tnfr/config/feature_flags.py +83 -0
  26. tnfr/{config.py → config/init.py} +11 -7
  27. tnfr/config/init.pyi +8 -0
  28. tnfr/config/operator_names.py +93 -0
  29. tnfr/config/operator_names.pyi +28 -0
  30. tnfr/config/presets.py +84 -0
  31. tnfr/config/presets.pyi +7 -0
  32. tnfr/constants/__init__.py +80 -29
  33. tnfr/constants/__init__.pyi +92 -0
  34. tnfr/constants/aliases.py +31 -0
  35. tnfr/constants/core.py +4 -4
  36. tnfr/constants/core.pyi +17 -0
  37. tnfr/constants/init.py +1 -1
  38. tnfr/constants/init.pyi +12 -0
  39. tnfr/constants/metric.py +7 -15
  40. tnfr/constants/metric.pyi +19 -0
  41. tnfr/dynamics/__init__.py +165 -633
  42. tnfr/dynamics/__init__.pyi +82 -0
  43. tnfr/dynamics/adaptation.py +267 -0
  44. tnfr/dynamics/aliases.py +23 -0
  45. tnfr/dynamics/coordination.py +385 -0
  46. tnfr/dynamics/dnfr.py +2283 -400
  47. tnfr/dynamics/dnfr.pyi +24 -0
  48. tnfr/dynamics/integrators.py +406 -98
  49. tnfr/dynamics/integrators.pyi +34 -0
  50. tnfr/dynamics/runtime.py +881 -0
  51. tnfr/dynamics/sampling.py +10 -5
  52. tnfr/dynamics/sampling.pyi +7 -0
  53. tnfr/dynamics/selectors.py +719 -0
  54. tnfr/execution.py +70 -48
  55. tnfr/execution.pyi +45 -0
  56. tnfr/flatten.py +13 -9
  57. tnfr/flatten.pyi +21 -0
  58. tnfr/gamma.py +66 -53
  59. tnfr/gamma.pyi +34 -0
  60. tnfr/glyph_history.py +110 -52
  61. tnfr/glyph_history.pyi +35 -0
  62. tnfr/glyph_runtime.py +16 -0
  63. tnfr/glyph_runtime.pyi +9 -0
  64. tnfr/immutable.py +69 -28
  65. tnfr/immutable.pyi +34 -0
  66. tnfr/initialization.py +16 -16
  67. tnfr/initialization.pyi +65 -0
  68. tnfr/io.py +6 -240
  69. tnfr/io.pyi +16 -0
  70. tnfr/locking.pyi +7 -0
  71. tnfr/mathematics/__init__.py +81 -0
  72. tnfr/mathematics/backend.py +426 -0
  73. tnfr/mathematics/dynamics.py +398 -0
  74. tnfr/mathematics/epi.py +254 -0
  75. tnfr/mathematics/generators.py +222 -0
  76. tnfr/mathematics/metrics.py +119 -0
  77. tnfr/mathematics/operators.py +233 -0
  78. tnfr/mathematics/operators_factory.py +71 -0
  79. tnfr/mathematics/projection.py +78 -0
  80. tnfr/mathematics/runtime.py +173 -0
  81. tnfr/mathematics/spaces.py +247 -0
  82. tnfr/mathematics/transforms.py +292 -0
  83. tnfr/metrics/__init__.py +10 -10
  84. tnfr/metrics/__init__.pyi +20 -0
  85. tnfr/metrics/coherence.py +993 -324
  86. tnfr/metrics/common.py +23 -16
  87. tnfr/metrics/common.pyi +46 -0
  88. tnfr/metrics/core.py +251 -35
  89. tnfr/metrics/core.pyi +13 -0
  90. tnfr/metrics/diagnosis.py +708 -111
  91. tnfr/metrics/diagnosis.pyi +85 -0
  92. tnfr/metrics/export.py +27 -15
  93. tnfr/metrics/glyph_timing.py +232 -42
  94. tnfr/metrics/reporting.py +33 -22
  95. tnfr/metrics/reporting.pyi +12 -0
  96. tnfr/metrics/sense_index.py +987 -43
  97. tnfr/metrics/sense_index.pyi +9 -0
  98. tnfr/metrics/trig.py +214 -23
  99. tnfr/metrics/trig.pyi +13 -0
  100. tnfr/metrics/trig_cache.py +115 -22
  101. tnfr/metrics/trig_cache.pyi +10 -0
  102. tnfr/node.py +542 -136
  103. tnfr/node.pyi +178 -0
  104. tnfr/observers.py +152 -35
  105. tnfr/observers.pyi +31 -0
  106. tnfr/ontosim.py +23 -19
  107. tnfr/ontosim.pyi +28 -0
  108. tnfr/operators/__init__.py +601 -82
  109. tnfr/operators/__init__.pyi +45 -0
  110. tnfr/operators/definitions.py +513 -0
  111. tnfr/operators/definitions.pyi +78 -0
  112. tnfr/operators/grammar.py +760 -0
  113. tnfr/operators/jitter.py +107 -38
  114. tnfr/operators/jitter.pyi +11 -0
  115. tnfr/operators/registry.py +75 -0
  116. tnfr/operators/registry.pyi +13 -0
  117. tnfr/operators/remesh.py +149 -88
  118. tnfr/py.typed +0 -0
  119. tnfr/rng.py +46 -143
  120. tnfr/rng.pyi +14 -0
  121. tnfr/schemas/__init__.py +8 -0
  122. tnfr/schemas/grammar.json +94 -0
  123. tnfr/selector.py +25 -19
  124. tnfr/selector.pyi +19 -0
  125. tnfr/sense.py +72 -62
  126. tnfr/sense.pyi +23 -0
  127. tnfr/structural.py +522 -262
  128. tnfr/structural.pyi +69 -0
  129. tnfr/telemetry/__init__.py +35 -0
  130. tnfr/telemetry/cache_metrics.py +226 -0
  131. tnfr/telemetry/nu_f.py +423 -0
  132. tnfr/telemetry/nu_f.pyi +123 -0
  133. tnfr/telemetry/verbosity.py +37 -0
  134. tnfr/tokens.py +1 -3
  135. tnfr/tokens.pyi +36 -0
  136. tnfr/trace.py +270 -113
  137. tnfr/trace.pyi +40 -0
  138. tnfr/types.py +574 -6
  139. tnfr/types.pyi +331 -0
  140. tnfr/units.py +69 -0
  141. tnfr/units.pyi +16 -0
  142. tnfr/utils/__init__.py +217 -0
  143. tnfr/utils/__init__.pyi +202 -0
  144. tnfr/utils/cache.py +2395 -0
  145. tnfr/utils/cache.pyi +468 -0
  146. tnfr/utils/chunks.py +104 -0
  147. tnfr/utils/chunks.pyi +21 -0
  148. tnfr/{collections_utils.py → utils/data.py} +147 -90
  149. tnfr/utils/data.pyi +64 -0
  150. tnfr/utils/graph.py +85 -0
  151. tnfr/utils/graph.pyi +10 -0
  152. tnfr/utils/init.py +770 -0
  153. tnfr/utils/init.pyi +78 -0
  154. tnfr/utils/io.py +456 -0
  155. tnfr/{helpers → utils}/numeric.py +51 -24
  156. tnfr/utils/numeric.pyi +21 -0
  157. tnfr/validation/__init__.py +113 -0
  158. tnfr/validation/__init__.pyi +77 -0
  159. tnfr/validation/compatibility.py +95 -0
  160. tnfr/validation/compatibility.pyi +6 -0
  161. tnfr/validation/grammar.py +71 -0
  162. tnfr/validation/grammar.pyi +40 -0
  163. tnfr/validation/graph.py +138 -0
  164. tnfr/validation/graph.pyi +17 -0
  165. tnfr/validation/rules.py +281 -0
  166. tnfr/validation/rules.pyi +55 -0
  167. tnfr/validation/runtime.py +263 -0
  168. tnfr/validation/runtime.pyi +31 -0
  169. tnfr/validation/soft_filters.py +170 -0
  170. tnfr/validation/soft_filters.pyi +37 -0
  171. tnfr/validation/spectral.py +159 -0
  172. tnfr/validation/spectral.pyi +46 -0
  173. tnfr/validation/syntax.py +40 -0
  174. tnfr/validation/syntax.pyi +10 -0
  175. tnfr/validation/window.py +39 -0
  176. tnfr/validation/window.pyi +1 -0
  177. tnfr/viz/__init__.py +9 -0
  178. tnfr/viz/matplotlib.py +246 -0
  179. tnfr-7.0.0.dist-info/METADATA +179 -0
  180. tnfr-7.0.0.dist-info/RECORD +185 -0
  181. {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/licenses/LICENSE.md +1 -1
  182. tnfr/grammar.py +0 -344
  183. tnfr/graph_utils.py +0 -84
  184. tnfr/helpers/__init__.py +0 -71
  185. tnfr/import_utils.py +0 -228
  186. tnfr/json_utils.py +0 -162
  187. tnfr/logging_utils.py +0 -116
  188. tnfr/presets.py +0 -60
  189. tnfr/validators.py +0 -84
  190. tnfr/value_utils.py +0 -59
  191. tnfr-4.5.2.dist-info/METADATA +0 -379
  192. tnfr-4.5.2.dist-info/RECORD +0 -67
  193. {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/WHEEL +0 -0
  194. {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/entry_points.txt +0 -0
  195. {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/top_level.txt +0 -0
tnfr/trace.py CHANGED
@@ -7,25 +7,37 @@ structures as immutable snapshots.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- from functools import partial
11
- from typing import Any, Callable, Optional, Protocol, NamedTuple, TypedDict, cast
10
+ import warnings
12
11
  from collections.abc import Iterable, Mapping
12
+ from types import MappingProxyType
13
+ from typing import Any, Callable, NamedTuple, Protocol, cast
13
14
 
14
15
  from .constants import TRACE
15
- from .glyph_history import ensure_history, count_glyphs, append_metric
16
- from .import_utils import cached_import
17
- from .graph_utils import get_graph_mapping
18
- from .collections_utils import is_non_string_sequence
16
+ from .glyph_history import append_metric, count_glyphs, ensure_history
17
+ from .metrics.sense_index import _normalise_si_sensitivity_mapping
18
+ from .telemetry.verbosity import (
19
+ TELEMETRY_VERBOSITY_DEFAULT,
20
+ TelemetryVerbosity,
21
+ )
22
+ from .types import (
23
+ SigmaVector,
24
+ TNFRGraph,
25
+ TraceCallback,
26
+ TraceFieldFn,
27
+ TraceFieldMap,
28
+ TraceFieldRegistry,
29
+ TraceMetadata,
30
+ TraceSnapshot,
31
+ )
32
+ from .utils import cached_import, get_graph_mapping, is_non_string_sequence
19
33
 
20
34
 
21
35
  class _KuramotoFn(Protocol):
22
- def __call__(self, G: Any) -> tuple[float, float]: ...
36
+ def __call__(self, G: TNFRGraph) -> tuple[float, float]: ...
23
37
 
24
38
 
25
39
  class _SigmaVectorFn(Protocol):
26
- def __call__(
27
- self, G: Any, weight_mode: str | None = None
28
- ) -> dict[str, float]: ...
40
+ def __call__(self, G: TNFRGraph, weight_mode: str | None = None) -> SigmaVector: ...
29
41
 
30
42
 
31
43
  class CallbackSpec(NamedTuple):
@@ -35,30 +47,77 @@ class CallbackSpec(NamedTuple):
35
47
  func: Callable[..., Any]
36
48
 
37
49
 
38
- class TraceMetadata(TypedDict, total=False):
39
- """Metadata captured by trace field functions."""
50
+ class TraceFieldSpec(NamedTuple):
51
+ """Declarative specification for a trace field producer."""
52
+
53
+ name: str
54
+ phase: str
55
+ producer: TraceFieldFn
56
+ tiers: tuple[TelemetryVerbosity, ...]
40
57
 
41
- gamma: Mapping[str, Any]
42
- grammar: Mapping[str, Any]
43
- selector: str | None
44
- dnfr_weights: Mapping[str, Any]
45
- si_weights: Mapping[str, Any]
46
- si_sensitivity: Mapping[str, Any]
47
- callbacks: Mapping[str, list[str] | None]
48
- thol_open_nodes: int
49
- kuramoto: Mapping[str, float]
50
- sigma: Mapping[str, float]
51
- glyphs: Mapping[str, int]
52
58
 
59
+ TRACE_VERBOSITY_DEFAULT = TELEMETRY_VERBOSITY_DEFAULT
60
+ TRACE_VERBOSITY_PRESETS: dict[str, tuple[str, ...]] = {}
61
+ _TRACE_CAPTURE_ALIASES: Mapping[str, str] = MappingProxyType(
62
+ {
63
+ "glyphs": "glyph_counts",
64
+ }
65
+ )
53
66
 
54
- class TraceSnapshot(TraceMetadata, total=False):
55
- """Trace snapshot stored in the history."""
56
67
 
57
- t: float
58
- phase: str
68
+ def _canonical_capture_name(name: str) -> str:
69
+ """Return the canonical capture field name for ``name``."""
70
+
71
+ stripped = name.strip()
72
+ alias = _TRACE_CAPTURE_ALIASES.get(stripped)
73
+ if alias is not None:
74
+ return alias
75
+
76
+ lowered = stripped.lower()
77
+ alias = _TRACE_CAPTURE_ALIASES.get(lowered)
78
+ if alias is not None:
79
+ return alias
80
+
81
+ return stripped
82
+
83
+
84
+ def _normalise_capture_spec(raw: Any) -> set[str]:
85
+ """Coerce custom capture payloads to a ``set`` of field names."""
59
86
 
87
+ if raw is None:
88
+ return set()
89
+ if isinstance(raw, Mapping):
90
+ return {_canonical_capture_name(str(name)) for name in raw.keys()}
91
+ if isinstance(raw, str):
92
+ return {_canonical_capture_name(raw)}
93
+ if isinstance(raw, Iterable):
94
+ return {_canonical_capture_name(str(name)) for name in raw}
95
+ return {_canonical_capture_name(str(raw))}
60
96
 
61
- def _kuramoto_fallback(G: Any) -> tuple[float, float]:
97
+
98
+ def _resolve_trace_capture(cfg: Mapping[str, Any]) -> set[str]:
99
+ """Return the capture set declared by ``cfg`` respecting verbosity."""
100
+
101
+ if "capture" in cfg:
102
+ return _normalise_capture_spec(cfg.get("capture"))
103
+
104
+ raw_verbosity = cfg.get("verbosity", TRACE_VERBOSITY_DEFAULT)
105
+ verbosity = str(raw_verbosity).lower()
106
+ fields = TRACE_VERBOSITY_PRESETS.get(verbosity)
107
+ if fields is None:
108
+ warnings.warn(
109
+ (
110
+ "Unknown TRACE verbosity %r; falling back to %s"
111
+ % (raw_verbosity, TRACE_VERBOSITY_DEFAULT)
112
+ ),
113
+ UserWarning,
114
+ stacklevel=3,
115
+ )
116
+ fields = TRACE_VERBOSITY_PRESETS[TRACE_VERBOSITY_DEFAULT]
117
+ return set(fields)
118
+
119
+
120
+ def _kuramoto_fallback(G: TNFRGraph) -> tuple[float, float]:
62
121
  return 0.0, 0.0
63
122
 
64
123
 
@@ -68,9 +127,7 @@ kuramoto_R_psi: _KuramotoFn = cast(
68
127
  )
69
128
 
70
129
 
71
- def _sigma_fallback(
72
- G: Any, _weight_mode: str | None = None
73
- ) -> dict[str, float]:
130
+ def _sigma_fallback(G: TNFRGraph, _weight_mode: str | None = None) -> SigmaVector:
74
131
  """Return a null sigma vector regardless of ``_weight_mode``."""
75
132
 
76
133
  return {"x": 0.0, "y": 0.0, "mag": 0.0, "angle": 0.0, "n": 0}
@@ -79,6 +136,7 @@ def _sigma_fallback(
79
136
  # Public exports for this module
80
137
  __all__ = (
81
138
  "CallbackSpec",
139
+ "TraceFieldSpec",
82
140
  "TraceMetadata",
83
141
  "TraceSnapshot",
84
142
  "register_trace",
@@ -94,24 +152,28 @@ __all__ = (
94
152
 
95
153
 
96
154
  def _trace_setup(
97
- G,
155
+ G: TNFRGraph,
98
156
  ) -> tuple[
99
- Optional[dict[str, Any]], set[str], Optional[dict[str, Any]], Optional[str]
157
+ Mapping[str, Any] | None,
158
+ set[str],
159
+ dict[str, Any] | None,
160
+ str | None,
100
161
  ]:
101
- """Common configuration for trace snapshots.
162
+ """Prepare common configuration for trace snapshots.
102
163
 
103
164
  Returns the active configuration, capture set, history and key under
104
165
  which metadata will be stored. If tracing is disabled returns
105
166
  ``(None, set(), None, None)``.
106
167
  """
107
168
 
108
- cfg = G.graph.get("TRACE", TRACE)
169
+ cfg_raw = G.graph.get("TRACE", TRACE)
170
+ cfg = cfg_raw if isinstance(cfg_raw, Mapping) else TRACE
109
171
  if not cfg.get("enabled", True):
110
172
  return None, set(), None, None
111
173
 
112
- capture: set[str] = set(cfg.get("capture", []))
174
+ capture = _resolve_trace_capture(cfg)
113
175
  hist = ensure_history(G)
114
- key = cfg.get("history_key", "trace_meta")
176
+ key = cast(str | None, cfg.get("history_key", "trace_meta"))
115
177
  return cfg, capture, hist, key
116
178
 
117
179
 
@@ -122,21 +184,22 @@ def _callback_names(
122
184
  if isinstance(callbacks, Mapping):
123
185
  callbacks = callbacks.values()
124
186
  return [
125
- cb.name
126
- if cb.name is not None
127
- else str(getattr(cb.func, "__name__", "fn"))
187
+ cb.name if cb.name is not None else str(getattr(cb.func, "__name__", "fn"))
128
188
  for cb in callbacks
129
189
  ]
130
190
 
131
191
 
132
- def mapping_field(G: Any, graph_key: str, out_key: str) -> TraceMetadata:
133
- """Helper to copy mappings from ``G.graph`` into trace output."""
192
+ EMPTY_MAPPING: Mapping[str, Any] = MappingProxyType({})
193
+
194
+
195
+ def mapping_field(G: TNFRGraph, graph_key: str, out_key: str) -> TraceMetadata:
196
+ """Copy mappings from ``G.graph`` into trace output."""
134
197
  mapping = get_graph_mapping(
135
- G, graph_key, f"G.graph[{graph_key!r}] no es un mapeo; se ignora"
198
+ G, graph_key, f"G.graph[{graph_key!r}] is not a mapping; ignoring"
136
199
  )
137
200
  if mapping is None:
138
201
  return {}
139
- return cast(TraceMetadata, {out_key: mapping})
202
+ return {out_key: mapping}
140
203
 
141
204
 
142
205
  # -------------------------
@@ -145,10 +208,8 @@ def mapping_field(G: Any, graph_key: str, out_key: str) -> TraceMetadata:
145
208
 
146
209
 
147
210
  def _new_trace_meta(
148
- G, phase: str
149
- ) -> Optional[
150
- tuple[TraceSnapshot, set[str], Optional[dict[str, Any]], Optional[str]]
151
- ]:
211
+ G: TNFRGraph, phase: str
212
+ ) -> tuple[TraceSnapshot, set[str], dict[str, Any] | None, str | None] | None:
152
213
  """Initialise trace metadata for a ``phase``.
153
214
 
154
215
  Wraps :func:`_trace_setup` and creates the base structure with timestamp
@@ -168,9 +229,7 @@ def _new_trace_meta(
168
229
  # -------------------------
169
230
 
170
231
 
171
- def _trace_capture(
172
- G, phase: str, fields: Mapping[str, Callable[[Any], TraceMetadata]]
173
- ) -> None:
232
+ def _trace_capture(G: TNFRGraph, phase: str, fields: TraceFieldMap) -> None:
174
233
  """Capture ``fields`` for ``phase`` and store the snapshot.
175
234
 
176
235
  A :class:`TraceSnapshot` is appended to the configured history when
@@ -187,7 +246,7 @@ def _trace_capture(
187
246
  return
188
247
  for name, getter in fields.items():
189
248
  if name in capture:
190
- meta.update(cast(TraceSnapshot, getter(G)))
249
+ meta.update(getter(G))
191
250
  if hist is None or key is None:
192
251
  return
193
252
  append_metric(hist, key, meta)
@@ -198,54 +257,70 @@ def _trace_capture(
198
257
  # -------------------------
199
258
 
200
259
 
201
- TRACE_FIELDS: dict[str, dict[str, Callable[[Any], TraceMetadata]]] = {}
260
+ TRACE_FIELDS: TraceFieldRegistry = {}
202
261
 
203
262
 
204
- def register_trace_field(
205
- phase: str, name: str, func: Callable[[Any], TraceMetadata]
206
- ) -> None:
263
+ def register_trace_field(phase: str, name: str, func: TraceFieldFn) -> None:
207
264
  """Register ``func`` to populate trace field ``name`` during ``phase``."""
208
265
 
209
266
  TRACE_FIELDS.setdefault(phase, {})[name] = func
210
267
 
211
268
 
212
- gamma_field = partial(mapping_field, graph_key="GAMMA", out_key="gamma")
269
+ def gamma_field(G: TNFRGraph) -> TraceMetadata:
270
+ """Expose γ-field metadata stored under ``G.graph['GAMMA']``."""
213
271
 
272
+ return mapping_field(G, "GAMMA", "gamma")
214
273
 
215
- grammar_field = partial(mapping_field, graph_key="GRAMMAR_CANON", out_key="grammar")
216
274
 
275
+ def grammar_field(G: TNFRGraph) -> TraceMetadata:
276
+ """Expose canonical grammar metadata for trace emission."""
217
277
 
218
- dnfr_weights_field = partial(
219
- mapping_field, graph_key="DNFR_WEIGHTS", out_key="dnfr_weights"
220
- )
278
+ return mapping_field(G, "GRAMMAR_CANON", "grammar")
221
279
 
222
280
 
223
- def selector_field(G: Any) -> TraceMetadata:
281
+ def dnfr_weights_field(G: TNFRGraph) -> TraceMetadata:
282
+ return mapping_field(G, "DNFR_WEIGHTS", "dnfr_weights")
283
+
284
+
285
+ def selector_field(G: TNFRGraph) -> TraceMetadata:
224
286
  sel = G.graph.get("glyph_selector")
225
- return cast(TraceMetadata, {"selector": getattr(sel, "__name__", str(sel)) if sel else None})
287
+ selector_name = getattr(sel, "__name__", str(sel)) if sel else None
288
+ return {"selector": selector_name}
226
289
 
227
290
 
228
- _si_weights_field = partial(mapping_field, graph_key="_Si_weights", out_key="si_weights")
291
+ def _si_weights_field(G: TNFRGraph) -> TraceMetadata:
292
+ weights = mapping_field(G, "_Si_weights", "si_weights")
293
+ if weights:
294
+ return weights
295
+ return {"si_weights": EMPTY_MAPPING}
229
296
 
230
297
 
231
- _si_sensitivity_field = partial(
232
- mapping_field, graph_key="_Si_sensitivity", out_key="si_sensitivity"
233
- )
298
+ def _si_sensitivity_field(G: TNFRGraph) -> TraceMetadata:
299
+ mapping = get_graph_mapping(
300
+ G,
301
+ "_Si_sensitivity",
302
+ "G.graph['_Si_sensitivity'] is not a mapping; ignoring",
303
+ )
304
+ if mapping is None:
305
+ return {"si_sensitivity": EMPTY_MAPPING}
306
+
307
+ normalised = _normalise_si_sensitivity_mapping(mapping, warn=True)
308
+
309
+ if normalised != mapping:
310
+ G.graph["_Si_sensitivity"] = normalised
234
311
 
312
+ return {"si_sensitivity": MappingProxyType(normalised)}
235
313
 
236
- def si_weights_field(G: Any) -> TraceMetadata:
314
+
315
+ def si_weights_field(G: TNFRGraph) -> TraceMetadata:
237
316
  """Return sense-plane weights and sensitivity."""
238
317
 
239
- return cast(
240
- TraceMetadata,
241
- {
242
- **(_si_weights_field(G) or {"si_weights": {}}),
243
- **(_si_sensitivity_field(G) or {"si_sensitivity": {}}),
244
- },
245
- )
318
+ weights = _si_weights_field(G)
319
+ sensitivity = _si_sensitivity_field(G)
320
+ return {**weights, **sensitivity}
246
321
 
247
322
 
248
- def callbacks_field(G: Any) -> TraceMetadata:
323
+ def callbacks_field(G: TNFRGraph) -> TraceMetadata:
249
324
  cb = G.graph.get("callbacks")
250
325
  if not isinstance(cb, Mapping):
251
326
  return {}
@@ -255,24 +330,24 @@ def callbacks_field(G: Any) -> TraceMetadata:
255
330
  out[phase] = _callback_names(cb_map)
256
331
  else:
257
332
  out[phase] = None
258
- return cast(TraceMetadata, {"callbacks": out})
333
+ return {"callbacks": out}
259
334
 
260
335
 
261
- def thol_state_field(G: Any) -> TraceMetadata:
336
+ def thol_state_field(G: TNFRGraph) -> TraceMetadata:
262
337
  th_open = 0
263
338
  for _, nd in G.nodes(data=True):
264
339
  st = nd.get("_GRAM", {})
265
340
  if st.get("thol_open", False):
266
341
  th_open += 1
267
- return cast(TraceMetadata, {"thol_open_nodes": th_open})
342
+ return {"thol_open_nodes": th_open}
268
343
 
269
344
 
270
- def kuramoto_field(G: Any) -> TraceMetadata:
345
+ def kuramoto_field(G: TNFRGraph) -> TraceMetadata:
271
346
  R, psi = kuramoto_R_psi(G)
272
- return cast(TraceMetadata, {"kuramoto": {"R": float(R), "psi": float(psi)}})
347
+ return {"kuramoto": {"R": float(R), "psi": float(psi)}}
273
348
 
274
349
 
275
- def sigma_field(G: Any) -> TraceMetadata:
350
+ def sigma_field(G: TNFRGraph) -> TraceMetadata:
276
351
  sigma_vector_from_graph: _SigmaVectorFn = cast(
277
352
  _SigmaVectorFn,
278
353
  cached_import(
@@ -282,20 +357,17 @@ def sigma_field(G: Any) -> TraceMetadata:
282
357
  ),
283
358
  )
284
359
  sv = sigma_vector_from_graph(G)
285
- return cast(
286
- TraceMetadata,
287
- {
288
- "sigma": {
289
- "x": float(sv.get("x", 0.0)),
290
- "y": float(sv.get("y", 0.0)),
291
- "mag": float(sv.get("mag", 0.0)),
292
- "angle": float(sv.get("angle", 0.0)),
293
- }
294
- },
295
- )
360
+ return {
361
+ "sigma": {
362
+ "x": float(sv.get("x", 0.0)),
363
+ "y": float(sv.get("y", 0.0)),
364
+ "mag": float(sv.get("mag", 0.0)),
365
+ "angle": float(sv.get("angle", 0.0)),
366
+ }
367
+ }
296
368
 
297
369
 
298
- def glyph_counts_field(G: Any) -> TraceMetadata:
370
+ def glyph_counts_field(G: TNFRGraph) -> TraceMetadata:
299
371
  """Return glyph count snapshot.
300
372
 
301
373
  ``count_glyphs`` already produces a fresh mapping so no additional copy
@@ -303,21 +375,107 @@ def glyph_counts_field(G: Any) -> TraceMetadata:
303
375
  """
304
376
 
305
377
  cnt = count_glyphs(G, window=1)
306
- return cast(TraceMetadata, {"glyphs": cnt})
307
-
378
+ return {"glyphs": cnt}
379
+
380
+
381
+ TRACE_FIELD_SPECS: tuple[TraceFieldSpec, ...] = (
382
+ TraceFieldSpec(
383
+ name="gamma",
384
+ phase="before",
385
+ producer=gamma_field,
386
+ tiers=(
387
+ TelemetryVerbosity.BASIC,
388
+ TelemetryVerbosity.DETAILED,
389
+ TelemetryVerbosity.DEBUG,
390
+ ),
391
+ ),
392
+ TraceFieldSpec(
393
+ name="grammar",
394
+ phase="before",
395
+ producer=grammar_field,
396
+ tiers=(
397
+ TelemetryVerbosity.BASIC,
398
+ TelemetryVerbosity.DETAILED,
399
+ TelemetryVerbosity.DEBUG,
400
+ ),
401
+ ),
402
+ TraceFieldSpec(
403
+ name="selector",
404
+ phase="before",
405
+ producer=selector_field,
406
+ tiers=(
407
+ TelemetryVerbosity.BASIC,
408
+ TelemetryVerbosity.DETAILED,
409
+ TelemetryVerbosity.DEBUG,
410
+ ),
411
+ ),
412
+ TraceFieldSpec(
413
+ name="dnfr_weights",
414
+ phase="before",
415
+ producer=dnfr_weights_field,
416
+ tiers=(
417
+ TelemetryVerbosity.BASIC,
418
+ TelemetryVerbosity.DETAILED,
419
+ TelemetryVerbosity.DEBUG,
420
+ ),
421
+ ),
422
+ TraceFieldSpec(
423
+ name="si_weights",
424
+ phase="before",
425
+ producer=si_weights_field,
426
+ tiers=(
427
+ TelemetryVerbosity.BASIC,
428
+ TelemetryVerbosity.DETAILED,
429
+ TelemetryVerbosity.DEBUG,
430
+ ),
431
+ ),
432
+ TraceFieldSpec(
433
+ name="callbacks",
434
+ phase="before",
435
+ producer=callbacks_field,
436
+ tiers=(
437
+ TelemetryVerbosity.BASIC,
438
+ TelemetryVerbosity.DETAILED,
439
+ TelemetryVerbosity.DEBUG,
440
+ ),
441
+ ),
442
+ TraceFieldSpec(
443
+ name="thol_open_nodes",
444
+ phase="before",
445
+ producer=thol_state_field,
446
+ tiers=(
447
+ TelemetryVerbosity.BASIC,
448
+ TelemetryVerbosity.DETAILED,
449
+ TelemetryVerbosity.DEBUG,
450
+ ),
451
+ ),
452
+ TraceFieldSpec(
453
+ name="kuramoto",
454
+ phase="after",
455
+ producer=kuramoto_field,
456
+ tiers=(TelemetryVerbosity.DETAILED, TelemetryVerbosity.DEBUG),
457
+ ),
458
+ TraceFieldSpec(
459
+ name="sigma",
460
+ phase="after",
461
+ producer=sigma_field,
462
+ tiers=(TelemetryVerbosity.DETAILED, TelemetryVerbosity.DEBUG),
463
+ ),
464
+ TraceFieldSpec(
465
+ name="glyph_counts",
466
+ phase="after",
467
+ producer=glyph_counts_field,
468
+ tiers=(TelemetryVerbosity.DEBUG,),
469
+ ),
470
+ )
308
471
 
309
- # Pre-register default fields
310
- register_trace_field("before", "gamma", gamma_field)
311
- register_trace_field("before", "grammar", grammar_field)
312
- register_trace_field("before", "selector", selector_field)
313
- register_trace_field("before", "dnfr_weights", dnfr_weights_field)
314
- register_trace_field("before", "si_weights", si_weights_field)
315
- register_trace_field("before", "callbacks", callbacks_field)
316
- register_trace_field("before", "thol_open_nodes", thol_state_field)
472
+ TRACE_VERBOSITY_PRESETS = {
473
+ level.value: tuple(spec.name for spec in TRACE_FIELD_SPECS if level in spec.tiers)
474
+ for level in TelemetryVerbosity
475
+ }
317
476
 
318
- register_trace_field("after", "kuramoto", kuramoto_field)
319
- register_trace_field("after", "sigma", sigma_field)
320
- register_trace_field("after", "glyph_counts", glyph_counts_field)
477
+ for spec in TRACE_FIELD_SPECS:
478
+ register_trace_field(spec.phase, spec.name, spec.producer)
321
479
 
322
480
 
323
481
  # -------------------------
@@ -325,9 +483,8 @@ register_trace_field("after", "glyph_counts", glyph_counts_field)
325
483
  # -------------------------
326
484
 
327
485
 
328
- def register_trace(G) -> None:
329
- """Enable before/after-step snapshots and dump operational metadata
330
- to history.
486
+ def register_trace(G: TNFRGraph) -> None:
487
+ """Enable before/after-step snapshots and dump operational metadata to history.
331
488
 
332
489
  Trace snapshots are stored as :class:`TraceSnapshot` entries in
333
490
  ``G.graph['history'][TRACE.history_key]`` with:
@@ -354,11 +511,11 @@ def register_trace(G) -> None:
354
511
  for phase in TRACE_FIELDS.keys():
355
512
  event = f"{phase}_step"
356
513
 
357
- def _make_cb(ph):
358
- def _cb(G, ctx: dict[str, Any] | None = None):
514
+ def _make_cb(ph: str) -> TraceCallback:
515
+ def _cb(graph: TNFRGraph, ctx: dict[str, Any]) -> None:
359
516
  del ctx
360
517
 
361
- _trace_capture(G, ph, TRACE_FIELDS.get(ph, {}))
518
+ _trace_capture(graph, ph, TRACE_FIELDS.get(ph, {}))
362
519
 
363
520
  return _cb
364
521
 
tnfr/trace.pyi ADDED
@@ -0,0 +1,40 @@
1
+ from collections.abc import Iterable, Mapping
2
+ from typing import Any, Callable, NamedTuple
3
+
4
+ from .types import (
5
+ TNFRGraph,
6
+ TraceFieldFn,
7
+ TraceFieldMap,
8
+ TraceFieldRegistry,
9
+ TraceMetadata,
10
+ TraceSnapshot,
11
+ )
12
+
13
+ __all__: tuple[str, ...]
14
+
15
+ def __getattr__(name: str) -> Any: ...
16
+
17
+ class CallbackSpec(NamedTuple):
18
+ name: str | None
19
+ func: Callable[..., Any]
20
+
21
+ kuramoto_R_psi: Callable[[TNFRGraph], tuple[float, float]]
22
+ TRACE_FIELDS: TraceFieldRegistry
23
+
24
+ def _callback_names(
25
+ callbacks: Mapping[str, CallbackSpec] | Iterable[CallbackSpec],
26
+ ) -> list[str]: ...
27
+ def mapping_field(G: TNFRGraph, graph_key: str, out_key: str) -> TraceMetadata: ...
28
+ def _trace_capture(G: TNFRGraph, phase: str, fields: TraceFieldMap) -> None: ...
29
+ def register_trace_field(phase: str, name: str, func: TraceFieldFn) -> None: ...
30
+ def gamma_field(G: TNFRGraph) -> TraceMetadata: ...
31
+ def grammar_field(G: TNFRGraph) -> TraceMetadata: ...
32
+ def dnfr_weights_field(G: TNFRGraph) -> TraceMetadata: ...
33
+ def selector_field(G: TNFRGraph) -> TraceMetadata: ...
34
+ def si_weights_field(G: TNFRGraph) -> TraceMetadata: ...
35
+ def callbacks_field(G: TNFRGraph) -> TraceMetadata: ...
36
+ def thol_state_field(G: TNFRGraph) -> TraceMetadata: ...
37
+ def kuramoto_field(G: TNFRGraph) -> TraceMetadata: ...
38
+ def sigma_field(G: TNFRGraph) -> TraceMetadata: ...
39
+ def glyph_counts_field(G: TNFRGraph) -> TraceMetadata: ...
40
+ def register_trace(G: TNFRGraph) -> None: ...