tnfr 6.0.0__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 (176) hide show
  1. tnfr/__init__.py +50 -5
  2. tnfr/__init__.pyi +0 -7
  3. tnfr/_compat.py +0 -1
  4. tnfr/_generated_version.py +34 -0
  5. tnfr/_version.py +44 -2
  6. tnfr/alias.py +14 -13
  7. tnfr/alias.pyi +5 -37
  8. tnfr/cache.py +9 -729
  9. tnfr/cache.pyi +8 -224
  10. tnfr/callback_utils.py +16 -31
  11. tnfr/callback_utils.pyi +3 -29
  12. tnfr/cli/__init__.py +17 -11
  13. tnfr/cli/__init__.pyi +0 -21
  14. tnfr/cli/arguments.py +175 -14
  15. tnfr/cli/arguments.pyi +5 -11
  16. tnfr/cli/execution.py +434 -48
  17. tnfr/cli/execution.pyi +14 -24
  18. tnfr/cli/utils.py +20 -3
  19. tnfr/cli/utils.pyi +5 -5
  20. tnfr/config/__init__.py +2 -1
  21. tnfr/config/__init__.pyi +2 -0
  22. tnfr/config/feature_flags.py +83 -0
  23. tnfr/config/init.py +1 -1
  24. tnfr/config/operator_names.py +1 -14
  25. tnfr/config/presets.py +6 -26
  26. tnfr/constants/__init__.py +10 -13
  27. tnfr/constants/__init__.pyi +10 -22
  28. tnfr/constants/aliases.py +31 -0
  29. tnfr/constants/core.py +4 -3
  30. tnfr/constants/init.py +1 -1
  31. tnfr/constants/metric.py +3 -3
  32. tnfr/dynamics/__init__.py +64 -10
  33. tnfr/dynamics/__init__.pyi +3 -4
  34. tnfr/dynamics/adaptation.py +79 -13
  35. tnfr/dynamics/aliases.py +10 -9
  36. tnfr/dynamics/coordination.py +77 -35
  37. tnfr/dynamics/dnfr.py +575 -274
  38. tnfr/dynamics/dnfr.pyi +1 -10
  39. tnfr/dynamics/integrators.py +47 -33
  40. tnfr/dynamics/integrators.pyi +0 -1
  41. tnfr/dynamics/runtime.py +489 -129
  42. tnfr/dynamics/sampling.py +2 -0
  43. tnfr/dynamics/selectors.py +101 -62
  44. tnfr/execution.py +15 -8
  45. tnfr/execution.pyi +5 -25
  46. tnfr/flatten.py +7 -3
  47. tnfr/flatten.pyi +1 -8
  48. tnfr/gamma.py +22 -26
  49. tnfr/gamma.pyi +0 -6
  50. tnfr/glyph_history.py +37 -26
  51. tnfr/glyph_history.pyi +1 -19
  52. tnfr/glyph_runtime.py +16 -0
  53. tnfr/glyph_runtime.pyi +9 -0
  54. tnfr/immutable.py +20 -15
  55. tnfr/immutable.pyi +4 -7
  56. tnfr/initialization.py +5 -7
  57. tnfr/initialization.pyi +1 -9
  58. tnfr/io.py +6 -305
  59. tnfr/io.pyi +13 -8
  60. tnfr/mathematics/__init__.py +81 -0
  61. tnfr/mathematics/backend.py +426 -0
  62. tnfr/mathematics/dynamics.py +398 -0
  63. tnfr/mathematics/epi.py +254 -0
  64. tnfr/mathematics/generators.py +222 -0
  65. tnfr/mathematics/metrics.py +119 -0
  66. tnfr/mathematics/operators.py +233 -0
  67. tnfr/mathematics/operators_factory.py +71 -0
  68. tnfr/mathematics/projection.py +78 -0
  69. tnfr/mathematics/runtime.py +173 -0
  70. tnfr/mathematics/spaces.py +247 -0
  71. tnfr/mathematics/transforms.py +292 -0
  72. tnfr/metrics/__init__.py +10 -10
  73. tnfr/metrics/coherence.py +123 -94
  74. tnfr/metrics/common.py +22 -13
  75. tnfr/metrics/common.pyi +42 -11
  76. tnfr/metrics/core.py +72 -14
  77. tnfr/metrics/diagnosis.py +48 -57
  78. tnfr/metrics/diagnosis.pyi +3 -7
  79. tnfr/metrics/export.py +3 -5
  80. tnfr/metrics/glyph_timing.py +41 -31
  81. tnfr/metrics/reporting.py +13 -6
  82. tnfr/metrics/sense_index.py +884 -114
  83. tnfr/metrics/trig.py +167 -11
  84. tnfr/metrics/trig.pyi +1 -0
  85. tnfr/metrics/trig_cache.py +112 -15
  86. tnfr/node.py +400 -17
  87. tnfr/node.pyi +55 -38
  88. tnfr/observers.py +111 -8
  89. tnfr/observers.pyi +0 -15
  90. tnfr/ontosim.py +9 -6
  91. tnfr/ontosim.pyi +0 -5
  92. tnfr/operators/__init__.py +529 -42
  93. tnfr/operators/__init__.pyi +14 -0
  94. tnfr/operators/definitions.py +350 -18
  95. tnfr/operators/definitions.pyi +0 -14
  96. tnfr/operators/grammar.py +760 -0
  97. tnfr/operators/jitter.py +28 -22
  98. tnfr/operators/registry.py +7 -12
  99. tnfr/operators/registry.pyi +0 -2
  100. tnfr/operators/remesh.py +38 -61
  101. tnfr/rng.py +17 -300
  102. tnfr/schemas/__init__.py +8 -0
  103. tnfr/schemas/grammar.json +94 -0
  104. tnfr/selector.py +3 -4
  105. tnfr/selector.pyi +1 -1
  106. tnfr/sense.py +22 -24
  107. tnfr/sense.pyi +0 -7
  108. tnfr/structural.py +504 -21
  109. tnfr/structural.pyi +41 -18
  110. tnfr/telemetry/__init__.py +23 -1
  111. tnfr/telemetry/cache_metrics.py +226 -0
  112. tnfr/telemetry/nu_f.py +423 -0
  113. tnfr/telemetry/nu_f.pyi +123 -0
  114. tnfr/tokens.py +1 -4
  115. tnfr/tokens.pyi +1 -6
  116. tnfr/trace.py +20 -53
  117. tnfr/trace.pyi +9 -37
  118. tnfr/types.py +244 -15
  119. tnfr/types.pyi +200 -14
  120. tnfr/units.py +69 -0
  121. tnfr/units.pyi +16 -0
  122. tnfr/utils/__init__.py +107 -48
  123. tnfr/utils/__init__.pyi +80 -11
  124. tnfr/utils/cache.py +1705 -65
  125. tnfr/utils/cache.pyi +370 -58
  126. tnfr/utils/chunks.py +104 -0
  127. tnfr/utils/chunks.pyi +21 -0
  128. tnfr/utils/data.py +95 -5
  129. tnfr/utils/data.pyi +8 -17
  130. tnfr/utils/graph.py +2 -4
  131. tnfr/utils/init.py +31 -7
  132. tnfr/utils/init.pyi +4 -11
  133. tnfr/utils/io.py +313 -14
  134. tnfr/{helpers → utils}/numeric.py +50 -24
  135. tnfr/utils/numeric.pyi +21 -0
  136. tnfr/validation/__init__.py +92 -4
  137. tnfr/validation/__init__.pyi +77 -17
  138. tnfr/validation/compatibility.py +79 -43
  139. tnfr/validation/compatibility.pyi +4 -6
  140. tnfr/validation/grammar.py +55 -133
  141. tnfr/validation/grammar.pyi +37 -8
  142. tnfr/validation/graph.py +138 -0
  143. tnfr/validation/graph.pyi +17 -0
  144. tnfr/validation/rules.py +161 -74
  145. tnfr/validation/rules.pyi +55 -18
  146. tnfr/validation/runtime.py +263 -0
  147. tnfr/validation/runtime.pyi +31 -0
  148. tnfr/validation/soft_filters.py +170 -0
  149. tnfr/validation/soft_filters.pyi +37 -0
  150. tnfr/validation/spectral.py +159 -0
  151. tnfr/validation/spectral.pyi +46 -0
  152. tnfr/validation/syntax.py +28 -139
  153. tnfr/validation/syntax.pyi +7 -4
  154. tnfr/validation/window.py +39 -0
  155. tnfr/validation/window.pyi +1 -0
  156. tnfr/viz/__init__.py +9 -0
  157. tnfr/viz/matplotlib.py +246 -0
  158. {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/METADATA +63 -19
  159. tnfr-7.0.0.dist-info/RECORD +185 -0
  160. {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/licenses/LICENSE.md +1 -1
  161. tnfr/constants_glyphs.py +0 -16
  162. tnfr/constants_glyphs.pyi +0 -12
  163. tnfr/grammar.py +0 -25
  164. tnfr/grammar.pyi +0 -13
  165. tnfr/helpers/__init__.py +0 -151
  166. tnfr/helpers/__init__.pyi +0 -66
  167. tnfr/helpers/numeric.pyi +0 -12
  168. tnfr/presets.py +0 -15
  169. tnfr/presets.pyi +0 -7
  170. tnfr/utils/io.pyi +0 -10
  171. tnfr/utils/validators.py +0 -130
  172. tnfr/utils/validators.pyi +0 -19
  173. tnfr-6.0.0.dist-info/RECORD +0 -157
  174. {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/WHEEL +0 -0
  175. {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/entry_points.txt +0 -0
  176. {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/top_level.txt +0 -0
tnfr/structural.py CHANGED
@@ -1,36 +1,72 @@
1
- """Structural analysis."""
1
+ """Maintain TNFR structural coherence for nodes and operator sequences.
2
+
3
+ This module exposes the canonical entry points used by the engine to
4
+ instantiate coherent TNFR nodes and to orchestrate structural operator
5
+ pipelines while keeping the nodal equation
6
+ ``∂EPI/∂t = νf · ΔNFR(t)`` balanced.
7
+
8
+ Public API
9
+ ----------
10
+ create_nfr
11
+ Initialise a node with canonical EPI, νf and phase attributes plus a
12
+ ΔNFR hook that propagates reorganisations through the graph.
13
+ run_sequence
14
+ Validate and execute operator trajectories so that ΔNFR hooks can
15
+ update EPI, νf and phase coherently after each step.
16
+ OPERATORS
17
+ Registry of canonical structural operators ready to be composed into
18
+ validated sequences.
19
+ validate_sequence
20
+ Grammar guard that ensures operator trajectories stay within TNFR
21
+ closure rules before execution.
22
+ """
2
23
 
3
24
  from __future__ import annotations
4
25
 
5
- from typing import Iterable
26
+ from copy import deepcopy
27
+ from typing import Iterable, Mapping, Sequence
6
28
 
7
29
  import networkx as nx
8
30
 
9
- from .constants import EPI_PRIMARY, VF_PRIMARY, THETA_PRIMARY
31
+ from .constants import EPI_PRIMARY, THETA_PRIMARY, VF_PRIMARY
10
32
  from .dynamics import (
11
- set_delta_nfr_hook,
12
33
  dnfr_epi_vf_mixed,
34
+ set_delta_nfr_hook,
13
35
  )
14
- from .types import DeltaNFRHook, NodeId, TNFRGraph
36
+ from .mathematics import (
37
+ BasicStateProjector,
38
+ CoherenceOperator,
39
+ FrequencyOperator,
40
+ HilbertSpace,
41
+ MathematicalDynamicsEngine,
42
+ NFRValidator,
43
+ make_coherence_operator,
44
+ make_frequency_operator,
45
+ )
46
+ from .validation import validate_sequence
15
47
  from .operators.definitions import (
16
- Operator,
17
- Emission,
18
- Reception,
19
48
  Coherence,
20
- Dissonance,
49
+ Contraction,
21
50
  Coupling,
22
- Resonance,
23
- Silence,
51
+ Dissonance,
52
+ Emission,
24
53
  Expansion,
25
- Contraction,
26
- SelfOrganization,
27
54
  Mutation,
28
- Transition,
55
+ Operator,
56
+ Reception,
29
57
  Recursivity,
58
+ Resonance,
59
+ SelfOrganization,
60
+ Silence,
61
+ Transition,
30
62
  )
31
63
  from .operators.registry import OPERATORS
32
- from .validation import validate_sequence
64
+ from .types import DeltaNFRHook, NodeId, TNFRGraph
33
65
 
66
+ try: # pragma: no cover - optional dependency path exercised in CI extras
67
+ import numpy as np
68
+ except ImportError: # pragma: no cover - optional dependency path exercised in CI extras
69
+ np = None # type: ignore[assignment]
34
70
 
35
71
  # ---------------------------------------------------------------------------
36
72
  # 1) NFR factory
@@ -46,10 +82,82 @@ def create_nfr(
46
82
  graph: TNFRGraph | None = None,
47
83
  dnfr_hook: DeltaNFRHook = dnfr_epi_vf_mixed,
48
84
  ) -> tuple[TNFRGraph, str]:
49
- """Create a graph with an initialised NFR node.
85
+ """Anchor a TNFR node by seeding EPI, νf, phase and ΔNFR coupling.
86
+
87
+ The factory secures the structural state of a node: it stores canonical
88
+ values for the Primary Information Structure (EPI), structural frequency
89
+ (νf) and phase, then installs a ΔNFR hook so that later operator
90
+ sequences can reorganise the node without breaking the nodal equation.
91
+
92
+ Parameters
93
+ ----------
94
+ name : str
95
+ Identifier for the new node. The identifier is stored as the node key
96
+ and must remain hashable by :mod:`networkx`.
97
+ epi : float, optional
98
+ Initial Primary Information Structure (EPI) assigned to the node. The
99
+ value provides the baseline form that subsequent ΔNFR hooks reorganise
100
+ through the nodal equation.
101
+ vf : float, optional
102
+ Structural frequency (νf, expressed in Hz_str) used as the starting
103
+ reorganisation rate for the node.
104
+ theta : float, optional
105
+ Initial phase of the node in radians, used to keep phase alignment with
106
+ neighbouring coherence structures.
107
+ graph : TNFRGraph, optional
108
+ Existing graph where the node will be registered. When omitted a new
109
+ :class:`networkx.Graph` instance is created.
110
+ dnfr_hook : DeltaNFRHook, optional
111
+ Callable responsible for computing ΔNFR and updating EPI/νf after each
112
+ operator application. By default the canonical ``dnfr_epi_vf_mixed``
113
+ hook is installed, which keeps the nodal equation coherent with TNFR
114
+ invariants.
115
+
116
+ Returns
117
+ -------
118
+ tuple[TNFRGraph, str]
119
+ The graph that stores the node together with the node identifier. The
120
+ tuple form allows immediate reuse with :func:`run_sequence`.
50
121
 
51
- Returns the tuple ``(G, name)`` for convenience.
122
+ Notes
123
+ -----
124
+ The factory does not introduce additional TNFR-specific errors. Any
125
+ exceptions raised by :mod:`networkx` when adding nodes propagate unchanged.
126
+
127
+ Examples
128
+ --------
129
+ Create a node, connect a ΔNFR hook and launch a coherent operator
130
+ trajectory while tracking the evolving metrics.
131
+
132
+ >>> from tnfr.constants import DNFR_PRIMARY, EPI_PRIMARY, THETA_PRIMARY, VF_PRIMARY
133
+ >>> from tnfr.dynamics import set_delta_nfr_hook
134
+ >>> from tnfr.structural import (
135
+ ... Coherence,
136
+ ... Emission,
137
+ ... Reception,
138
+ ... Resonance,
139
+ ... Silence,
140
+ ... create_nfr,
141
+ ... run_sequence,
142
+ ... )
143
+ >>> G, node = create_nfr("seed", epi=1.0, vf=2.0, theta=0.1)
144
+ >>> def synchronise_delta(graph):
145
+ ... delta = graph.nodes[node][VF_PRIMARY] * 0.2
146
+ ... graph.nodes[node][DNFR_PRIMARY] = delta
147
+ ... graph.nodes[node][EPI_PRIMARY] += delta
148
+ ... graph.nodes[node][VF_PRIMARY] += delta * 0.05
149
+ ... graph.nodes[node][THETA_PRIMARY] += 0.01
150
+ >>> set_delta_nfr_hook(G, synchronise_delta)
151
+ >>> run_sequence(G, node, [Emission(), Reception(), Coherence(), Resonance(), Silence()]) # doctest: +SKIP
152
+ >>> (
153
+ ... G.nodes[node][EPI_PRIMARY],
154
+ ... G.nodes[node][VF_PRIMARY],
155
+ ... G.nodes[node][THETA_PRIMARY],
156
+ ... G.nodes[node][DNFR_PRIMARY],
157
+ ... ) # doctest: +SKIP
158
+ (..., ..., ..., ...)
52
159
  """
160
+
53
161
  G = graph if graph is not None else nx.Graph()
54
162
  G.add_node(
55
163
  name,
@@ -63,8 +171,318 @@ def create_nfr(
63
171
  return G, name
64
172
 
65
173
 
174
+ def _resolve_dimension(
175
+ G: TNFRGraph,
176
+ *,
177
+ dimension: int | None,
178
+ hilbert_space: HilbertSpace | None,
179
+ existing_cfg: Mapping[str, object] | None,
180
+ ) -> int:
181
+ if hilbert_space is not None:
182
+ resolved = int(getattr(hilbert_space, "dimension", 0) or 0)
183
+ if resolved <= 0:
184
+ raise ValueError("Hilbert space dimension must be positive.")
185
+ return resolved
186
+
187
+ if dimension is None and existing_cfg:
188
+ candidate = existing_cfg.get("dimension")
189
+ if isinstance(candidate, int) and candidate > 0:
190
+ dimension = candidate
191
+
192
+ if dimension is None:
193
+ if hasattr(G, "number_of_nodes"):
194
+ count = int(G.number_of_nodes())
195
+ else:
196
+ count = len(tuple(G.nodes))
197
+ dimension = max(1, count)
198
+
199
+ resolved = int(dimension)
200
+ if resolved <= 0:
201
+ raise ValueError("Hilbert space dimension must be positive.")
202
+ return resolved
203
+
204
+
205
+ def _ensure_coherence_operator(
206
+ *,
207
+ operator: CoherenceOperator | None,
208
+ dimension: int,
209
+ spectrum: Sequence[float] | None,
210
+ c_min: float | None,
211
+ ) -> CoherenceOperator:
212
+ if operator is not None:
213
+ return operator
214
+
215
+ kwargs: dict[str, object] = {}
216
+ if spectrum is not None:
217
+ spectrum_array = np.asarray(spectrum, dtype=np.complex128)
218
+ if spectrum_array.ndim != 1:
219
+ raise ValueError("Coherence spectrum must be one-dimensional.")
220
+ kwargs["spectrum"] = spectrum_array
221
+ if c_min is not None:
222
+ kwargs["c_min"] = float(c_min)
223
+ return make_coherence_operator(dimension, **kwargs)
224
+
225
+
226
+ def _ensure_frequency_operator(
227
+ *,
228
+ operator: FrequencyOperator | None,
229
+ dimension: int,
230
+ diagonal: Sequence[float] | None,
231
+ ) -> FrequencyOperator:
232
+ if operator is not None:
233
+ return operator
234
+
235
+ if diagonal is None:
236
+ matrix = np.eye(dimension, dtype=float)
237
+ else:
238
+ diag_array = np.asarray(diagonal, dtype=float)
239
+ if diag_array.ndim != 1:
240
+ raise ValueError("Frequency diagonal must be one-dimensional.")
241
+ if diag_array.shape[0] != int(dimension):
242
+ raise ValueError("Frequency diagonal size must match Hilbert dimension.")
243
+ matrix = np.diag(diag_array)
244
+ return make_frequency_operator(np.asarray(matrix, dtype=np.complex128))
245
+
246
+
247
+ def _ensure_generator_matrix(
248
+ *,
249
+ dimension: int,
250
+ diagonal: Sequence[float] | None,
251
+ ) -> "np.ndarray":
252
+ if diagonal is None:
253
+ return np.zeros((dimension, dimension), dtype=np.complex128)
254
+ diag_array = np.asarray(diagonal, dtype=np.complex128)
255
+ if diag_array.ndim != 1:
256
+ raise ValueError("Generator diagonal must be one-dimensional.")
257
+ if diag_array.shape[0] != int(dimension):
258
+ raise ValueError("Generator diagonal size must match Hilbert dimension.")
259
+ return np.diag(diag_array)
260
+
261
+
262
+ def create_math_nfr(
263
+ name: str,
264
+ *,
265
+ epi: float = 0.0,
266
+ vf: float = 1.0,
267
+ theta: float = 0.0,
268
+ graph: TNFRGraph | None = None,
269
+ dnfr_hook: DeltaNFRHook = dnfr_epi_vf_mixed,
270
+ dimension: int | None = None,
271
+ hilbert_space: HilbertSpace | None = None,
272
+ coherence_operator: CoherenceOperator | None = None,
273
+ coherence_spectrum: Sequence[float] | None = None,
274
+ coherence_c_min: float | None = None,
275
+ coherence_threshold: float | None = None,
276
+ frequency_operator: FrequencyOperator | None = None,
277
+ frequency_diagonal: Sequence[float] | None = None,
278
+ generator_diagonal: Sequence[float] | None = None,
279
+ state_projector: BasicStateProjector | None = None,
280
+ dynamics_engine: MathematicalDynamicsEngine | None = None,
281
+ validator: NFRValidator | None = None,
282
+ ) -> tuple[TNFRGraph, str]:
283
+ """Create a TNFR node with canonical mathematical validation attached.
284
+
285
+ The helper wraps :func:`create_nfr` while projecting the structural state
286
+ into a Hilbert space so coherence, νf and norm invariants can be tracked via
287
+ the mathematical runtime. It installs operators and validation metadata on
288
+ both the node and the hosting graph so that the
289
+ :class:`~tnfr.mathematics.MathematicalDynamicsEngine` can consume them
290
+ directly.
291
+
292
+ Parameters
293
+ ----------
294
+ name : str
295
+ Identifier for the new node.
296
+ epi, vf, theta : float, optional
297
+ Canonical TNFR scalars forwarded to :func:`create_nfr`.
298
+ dimension : int, optional
299
+ Hilbert space dimension. When omitted it is inferred from the graph size
300
+ (at least one).
301
+ hilbert_space : HilbertSpace, optional
302
+ Pre-built Hilbert space to reuse. Its dimension supersedes ``dimension``.
303
+ coherence_operator, frequency_operator : optional
304
+ Custom operators to install. When omitted they are derived from
305
+ ``coherence_spectrum``/``coherence_c_min`` and
306
+ ``frequency_diagonal`` respectively.
307
+ coherence_threshold : float, optional
308
+ Validation floor. Defaults to ``coherence_operator.c_min``.
309
+ generator_diagonal : sequence of float, optional
310
+ Diagonal entries for the unitary generator used by the mathematical
311
+ dynamics engine. Defaults to a null generator.
312
+ state_projector : BasicStateProjector, optional
313
+ Projector used to build the canonical spectral state for validation.
314
+
315
+ Returns
316
+ -------
317
+ tuple[TNFRGraph, str]
318
+ The graph and node identifier, mirroring :func:`create_nfr`.
319
+
320
+ Examples
321
+ --------
322
+ >>> G, node = create_math_nfr("math-seed", epi=0.4, vf=1.2, theta=0.05, dimension=3)
323
+ >>> metrics = G.nodes[node]["math_metrics"]
324
+ >>> round(metrics["norm"], 6)
325
+ 1.0
326
+ >>> metrics["coherence_passed"], metrics["frequency_passed"]
327
+ (True, True)
328
+ >>> metrics["coherence_value"] >= metrics["coherence_threshold"]
329
+ True
330
+
331
+ Notes
332
+ -----
333
+ The helper mutates/extends ``G.graph['MATH_ENGINE']`` so subsequent calls to
334
+ :mod:`tnfr.dynamics.runtime` can advance the mathematical engine without
335
+ further configuration.
336
+ """
337
+
338
+ if np is None:
339
+ raise ImportError("create_math_nfr requires NumPy; install the 'tnfr[math]' extras.")
340
+
341
+ G, node = create_nfr(
342
+ name,
343
+ epi=epi,
344
+ vf=vf,
345
+ theta=theta,
346
+ graph=graph,
347
+ dnfr_hook=dnfr_hook,
348
+ )
349
+
350
+ existing_cfg = G.graph.get("MATH_ENGINE")
351
+ mapping_cfg: Mapping[str, object] | None
352
+ if isinstance(existing_cfg, Mapping):
353
+ mapping_cfg = existing_cfg
354
+ else:
355
+ mapping_cfg = None
356
+
357
+ resolved_dimension = _resolve_dimension(
358
+ G,
359
+ dimension=dimension,
360
+ hilbert_space=hilbert_space,
361
+ existing_cfg=mapping_cfg,
362
+ )
363
+
364
+ hilbert = hilbert_space or HilbertSpace(resolved_dimension)
365
+ resolved_dimension = int(getattr(hilbert, "dimension", resolved_dimension))
366
+
367
+ coherence = _ensure_coherence_operator(
368
+ operator=coherence_operator,
369
+ dimension=resolved_dimension,
370
+ spectrum=coherence_spectrum,
371
+ c_min=coherence_c_min,
372
+ )
373
+ threshold = float(
374
+ coherence_threshold if coherence_threshold is not None else coherence.c_min
375
+ )
376
+
377
+ frequency = _ensure_frequency_operator(
378
+ operator=frequency_operator,
379
+ dimension=resolved_dimension,
380
+ diagonal=frequency_diagonal,
381
+ )
382
+
383
+ projector = state_projector or BasicStateProjector()
384
+
385
+ generator_matrix = _ensure_generator_matrix(
386
+ dimension=resolved_dimension,
387
+ diagonal=generator_diagonal,
388
+ )
389
+ engine = dynamics_engine or MathematicalDynamicsEngine(
390
+ generator_matrix,
391
+ hilbert_space=hilbert,
392
+ )
393
+
394
+ enforce_frequency = frequency is not None
395
+ spectral_validator = validator or NFRValidator(
396
+ hilbert,
397
+ coherence,
398
+ threshold,
399
+ frequency_operator=frequency if enforce_frequency else None,
400
+ )
401
+
402
+ state = projector(
403
+ epi=float(epi),
404
+ nu_f=float(vf),
405
+ theta=float(theta),
406
+ dim=resolved_dimension,
407
+ )
408
+ norm_value = float(hilbert.norm(state))
409
+ outcome = spectral_validator.validate(
410
+ state,
411
+ enforce_frequency_positivity=enforce_frequency,
412
+ )
413
+ summary_raw = outcome.summary
414
+ summary = {key: deepcopy(value) for key, value in summary_raw.items()}
415
+
416
+ coherence_summary = summary.get("coherence")
417
+ frequency_summary = summary.get("frequency")
418
+
419
+ math_metrics = {
420
+ "norm": norm_value,
421
+ "normalized": bool(summary.get("normalized", False)),
422
+ "coherence_value": float(coherence_summary.get("value", 0.0))
423
+ if isinstance(coherence_summary, Mapping)
424
+ else 0.0,
425
+ "coherence_threshold": float(
426
+ coherence_summary.get("threshold", threshold)
427
+ )
428
+ if isinstance(coherence_summary, Mapping)
429
+ else threshold,
430
+ "coherence_passed": bool(coherence_summary.get("passed", False))
431
+ if isinstance(coherence_summary, Mapping)
432
+ else False,
433
+ "frequency_value": float(frequency_summary.get("value", 0.0))
434
+ if isinstance(frequency_summary, Mapping)
435
+ else 0.0,
436
+ "frequency_passed": bool(frequency_summary.get("passed", False))
437
+ if isinstance(frequency_summary, Mapping)
438
+ else True,
439
+ "frequency_spectrum_min": float(
440
+ frequency_summary.get("spectrum_min", 0.0)
441
+ )
442
+ if isinstance(frequency_summary, Mapping)
443
+ and "spectrum_min" in frequency_summary
444
+ else None,
445
+ "unitary_passed": bool(
446
+ summary.get("unitary_stability", {}).get("passed", False)
447
+ ),
448
+ }
449
+
450
+ node_context = {
451
+ "hilbert_space": hilbert,
452
+ "coherence_operator": coherence,
453
+ "frequency_operator": frequency,
454
+ "coherence_threshold": threshold,
455
+ "dimension": resolved_dimension,
456
+ }
457
+
458
+ node_data = G.nodes[node]
459
+ node_data["math_metrics"] = math_metrics
460
+ node_data["math_summary"] = summary
461
+ node_data["math_context"] = node_context
462
+
463
+ cfg = dict(mapping_cfg) if mapping_cfg is not None else {}
464
+ cfg.update(
465
+ {
466
+ "enabled": True,
467
+ "dimension": resolved_dimension,
468
+ "hilbert_space": hilbert,
469
+ "coherence_operator": coherence,
470
+ "coherence_threshold": threshold,
471
+ "frequency_operator": frequency,
472
+ "state_projector": projector,
473
+ "validator": spectral_validator,
474
+ "generator_matrix": generator_matrix,
475
+ "dynamics_engine": engine,
476
+ }
477
+ )
478
+ G.graph["MATH_ENGINE"] = cfg
479
+
480
+ return G, node
481
+
482
+
66
483
  __all__ = (
67
484
  "create_nfr",
485
+ "create_math_nfr",
68
486
  "Operator",
69
487
  "Emission",
70
488
  "Reception",
@@ -86,15 +504,80 @@ __all__ = (
86
504
 
87
505
 
88
506
  def run_sequence(G: TNFRGraph, node: NodeId, ops: Iterable[Operator]) -> None:
89
- """Execute a sequence of operators on ``node`` after validation."""
507
+ """Drive structural sequences that rebalance EPI, νf, phase and ΔNFR.
508
+
509
+ The function enforces the canonical operator grammar, then executes each
510
+ operator so that the configured ΔNFR hook can update the nodal equation in
511
+ place. Each step is expected to express the structural effect of the
512
+ operator, while the hook keeps EPI, νf and phase consistent with the
513
+ resulting ΔNFR variations.
514
+
515
+ Parameters
516
+ ----------
517
+ G : TNFRGraph
518
+ Graph that stores the node and its ΔNFR orchestration hook. The hook is
519
+ read from ``G.graph['compute_delta_nfr']`` and is responsible for
520
+ keeping the nodal equation up to date after each operator.
521
+ node : NodeId
522
+ Identifier of the node that will receive the operators. The node must
523
+ already contain the canonical attributes ``EPI``, ``νf`` and ``θ``.
524
+ ops : Iterable[Operator]
525
+ Iterable of canonical structural operators to apply. Their
526
+ concatenation must respect the validated TNFR grammar.
527
+
528
+ Returns
529
+ -------
530
+ None
531
+ The function mutates ``G`` in-place by updating the node attributes.
532
+
533
+ Raises
534
+ ------
535
+ ValueError
536
+ Raised when the provided operator names do not satisfy the canonical
537
+ sequence validation rules.
538
+
539
+ Examples
540
+ --------
541
+ Run a validated trajectory that highlights the ΔNFR-driven evolution of the
542
+ node metrics.
543
+
544
+ >>> from tnfr.constants import DNFR_PRIMARY, EPI_PRIMARY, THETA_PRIMARY, VF_PRIMARY
545
+ >>> from tnfr.dynamics import set_delta_nfr_hook
546
+ >>> from tnfr.structural import (
547
+ ... Coherence,
548
+ ... Emission,
549
+ ... Reception,
550
+ ... Resonance,
551
+ ... Silence,
552
+ ... create_nfr,
553
+ ... run_sequence,
554
+ ... )
555
+ >>> G, node = create_nfr("seed", epi=0.8, vf=1.5, theta=0.0)
556
+ >>> def amplify_delta(graph):
557
+ ... delta = graph.nodes[node][VF_PRIMARY] * 0.15
558
+ ... graph.nodes[node][DNFR_PRIMARY] = delta
559
+ ... graph.nodes[node][EPI_PRIMARY] += delta * 0.8
560
+ ... graph.nodes[node][VF_PRIMARY] += delta * 0.1
561
+ ... graph.nodes[node][THETA_PRIMARY] += 0.02
562
+ >>> set_delta_nfr_hook(G, amplify_delta)
563
+ >>> run_sequence(G, node, [Emission(), Reception(), Coherence(), Resonance(), Silence()]) # doctest: +SKIP
564
+ >>> (
565
+ ... G.nodes[node][EPI_PRIMARY],
566
+ ... G.nodes[node][VF_PRIMARY],
567
+ ... G.nodes[node][THETA_PRIMARY],
568
+ ... G.nodes[node][DNFR_PRIMARY],
569
+ ... ) # doctest: +SKIP
570
+ (..., ..., ..., ...)
571
+ """
90
572
 
91
573
  compute = G.graph.get("compute_delta_nfr")
92
574
  ops_list = list(ops)
93
575
  names = [op.name for op in ops_list]
94
576
 
95
- ok, msg = validate_sequence(names)
96
- if not ok:
97
- raise ValueError(f"Invalid sequence: {msg}")
577
+ outcome = validate_sequence(names)
578
+ if not outcome.passed:
579
+ summary_message = outcome.summary.get("message", "validation failed")
580
+ raise ValueError(f"Invalid sequence: {summary_message}")
98
581
 
99
582
  for op in ops_list:
100
583
  op(G, node)
tnfr/structural.pyi CHANGED
@@ -1,31 +1,37 @@
1
- from typing import TYPE_CHECKING, Any, Callable, Hashable, Iterable
1
+ from typing import TYPE_CHECKING, Any, Callable, Hashable, Iterable, Sequence
2
2
 
3
+ from .validation import SequenceValidationResult
3
4
  from .operators.definitions import (
4
- Operator,
5
- Emission,
6
- Reception,
7
5
  Coherence,
8
- Dissonance,
6
+ Contraction,
9
7
  Coupling,
10
- Resonance,
11
- Silence,
8
+ Dissonance,
9
+ Emission,
12
10
  Expansion,
13
- Contraction,
14
- SelfOrganization,
15
11
  Mutation,
16
- Transition,
12
+ Operator,
13
+ Reception,
17
14
  Recursivity,
15
+ Resonance,
16
+ SelfOrganization,
17
+ Silence,
18
+ Transition,
18
19
  )
19
20
 
20
21
  if TYPE_CHECKING:
21
22
  import networkx as nx
23
+ from .mathematics import (
24
+ BasicStateProjector,
25
+ CoherenceOperator,
26
+ FrequencyOperator,
27
+ HilbertSpace,
28
+ MathematicalDynamicsEngine,
29
+ NFRValidator,
30
+ )
22
31
 
23
32
  __all__: tuple[str, ...]
24
33
 
25
-
26
34
  def __getattr__(name: str) -> Any: ...
27
-
28
-
29
35
  def create_nfr(
30
36
  name: str,
31
37
  *,
@@ -35,12 +41,29 @@ def create_nfr(
35
41
  graph: "nx.Graph" | None = ...,
36
42
  dnfr_hook: Callable[..., None] = ...,
37
43
  ) -> tuple["nx.Graph", str]: ...
38
-
44
+ def create_math_nfr(
45
+ name: str,
46
+ *,
47
+ epi: float = ...,
48
+ vf: float = ...,
49
+ theta: float = ...,
50
+ graph: "nx.Graph" | None = ...,
51
+ dnfr_hook: Callable[..., None] = ...,
52
+ dimension: int | None = ...,
53
+ hilbert_space: "HilbertSpace" | None = ...,
54
+ coherence_operator: "CoherenceOperator" | None = ...,
55
+ coherence_spectrum: Sequence[float] | None = ...,
56
+ coherence_c_min: float | None = ...,
57
+ coherence_threshold: float | None = ...,
58
+ frequency_operator: "FrequencyOperator" | None = ...,
59
+ frequency_diagonal: Sequence[float] | None = ...,
60
+ generator_diagonal: Sequence[float] | None = ...,
61
+ state_projector: "BasicStateProjector" | None = ...,
62
+ dynamics_engine: "MathematicalDynamicsEngine" | None = ...,
63
+ validator: "NFRValidator" | None = ...,
64
+ ) -> tuple["nx.Graph", str]: ...
39
65
 
40
66
  OPERATORS: dict[str, Operator]
41
67
 
42
-
43
- def validate_sequence(names: Iterable[str]) -> tuple[bool, str]: ...
44
-
45
-
68
+ def validate_sequence(names: Iterable[str]) -> SequenceValidationResult: ...
46
69
  def run_sequence(G: "nx.Graph", node: Hashable, ops: Iterable[Operator]) -> None: ...
@@ -1,12 +1,34 @@
1
1
  """Telemetry helpers for shared observability settings."""
2
2
 
3
+ from .cache_metrics import (
4
+ CacheMetricsSnapshot,
5
+ CacheTelemetryPublisher,
6
+ ensure_cache_metrics_publisher,
7
+ publish_graph_cache_metrics,
8
+ )
9
+ from .nu_f import (
10
+ NuFSnapshot,
11
+ NuFTelemetryAccumulator,
12
+ NuFWindow,
13
+ ensure_nu_f_telemetry,
14
+ record_nu_f_window,
15
+ )
3
16
  from .verbosity import (
4
- TelemetryVerbosity,
5
17
  TELEMETRY_VERBOSITY_DEFAULT,
6
18
  TELEMETRY_VERBOSITY_LEVELS,
19
+ TelemetryVerbosity,
7
20
  )
8
21
 
9
22
  __all__ = [
23
+ "CacheMetricsSnapshot",
24
+ "CacheTelemetryPublisher",
25
+ "ensure_cache_metrics_publisher",
26
+ "publish_graph_cache_metrics",
27
+ "NuFWindow",
28
+ "NuFSnapshot",
29
+ "NuFTelemetryAccumulator",
30
+ "ensure_nu_f_telemetry",
31
+ "record_nu_f_window",
10
32
  "TelemetryVerbosity",
11
33
  "TELEMETRY_VERBOSITY_DEFAULT",
12
34
  "TELEMETRY_VERBOSITY_LEVELS",