tnfr 4.5.2__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.
Files changed (161) hide show
  1. tnfr/__init__.py +228 -49
  2. tnfr/__init__.pyi +40 -0
  3. tnfr/_compat.py +11 -0
  4. tnfr/_version.py +7 -0
  5. tnfr/_version.pyi +7 -0
  6. tnfr/alias.py +106 -21
  7. tnfr/alias.pyi +140 -0
  8. tnfr/cache.py +666 -512
  9. tnfr/cache.pyi +232 -0
  10. tnfr/callback_utils.py +2 -9
  11. tnfr/callback_utils.pyi +105 -0
  12. tnfr/cli/__init__.py +21 -7
  13. tnfr/cli/__init__.pyi +47 -0
  14. tnfr/cli/arguments.py +42 -20
  15. tnfr/cli/arguments.pyi +33 -0
  16. tnfr/cli/execution.py +54 -20
  17. tnfr/cli/execution.pyi +80 -0
  18. tnfr/cli/utils.py +0 -2
  19. tnfr/cli/utils.pyi +8 -0
  20. tnfr/config/__init__.py +12 -0
  21. tnfr/config/__init__.pyi +8 -0
  22. tnfr/config/constants.py +104 -0
  23. tnfr/config/constants.pyi +12 -0
  24. tnfr/{config.py → config/init.py} +11 -7
  25. tnfr/config/init.pyi +8 -0
  26. tnfr/config/operator_names.py +106 -0
  27. tnfr/config/operator_names.pyi +28 -0
  28. tnfr/config/presets.py +104 -0
  29. tnfr/config/presets.pyi +7 -0
  30. tnfr/constants/__init__.py +78 -24
  31. tnfr/constants/__init__.pyi +104 -0
  32. tnfr/constants/core.py +1 -2
  33. tnfr/constants/core.pyi +17 -0
  34. tnfr/constants/init.pyi +12 -0
  35. tnfr/constants/metric.py +4 -12
  36. tnfr/constants/metric.pyi +19 -0
  37. tnfr/constants_glyphs.py +9 -91
  38. tnfr/constants_glyphs.pyi +12 -0
  39. tnfr/dynamics/__init__.py +112 -634
  40. tnfr/dynamics/__init__.pyi +83 -0
  41. tnfr/dynamics/adaptation.py +201 -0
  42. tnfr/dynamics/aliases.py +22 -0
  43. tnfr/dynamics/coordination.py +343 -0
  44. tnfr/dynamics/dnfr.py +1936 -354
  45. tnfr/dynamics/dnfr.pyi +33 -0
  46. tnfr/dynamics/integrators.py +369 -75
  47. tnfr/dynamics/integrators.pyi +35 -0
  48. tnfr/dynamics/runtime.py +521 -0
  49. tnfr/dynamics/sampling.py +8 -5
  50. tnfr/dynamics/sampling.pyi +7 -0
  51. tnfr/dynamics/selectors.py +680 -0
  52. tnfr/execution.py +56 -41
  53. tnfr/execution.pyi +65 -0
  54. tnfr/flatten.py +7 -7
  55. tnfr/flatten.pyi +28 -0
  56. tnfr/gamma.py +54 -37
  57. tnfr/gamma.pyi +40 -0
  58. tnfr/glyph_history.py +85 -38
  59. tnfr/glyph_history.pyi +53 -0
  60. tnfr/grammar.py +19 -338
  61. tnfr/grammar.pyi +13 -0
  62. tnfr/helpers/__init__.py +110 -30
  63. tnfr/helpers/__init__.pyi +66 -0
  64. tnfr/helpers/numeric.py +1 -0
  65. tnfr/helpers/numeric.pyi +12 -0
  66. tnfr/immutable.py +55 -19
  67. tnfr/immutable.pyi +37 -0
  68. tnfr/initialization.py +12 -10
  69. tnfr/initialization.pyi +73 -0
  70. tnfr/io.py +99 -34
  71. tnfr/io.pyi +11 -0
  72. tnfr/locking.pyi +7 -0
  73. tnfr/metrics/__init__.pyi +20 -0
  74. tnfr/metrics/coherence.py +934 -294
  75. tnfr/metrics/common.py +1 -3
  76. tnfr/metrics/common.pyi +15 -0
  77. tnfr/metrics/core.py +192 -34
  78. tnfr/metrics/core.pyi +13 -0
  79. tnfr/metrics/diagnosis.py +707 -101
  80. tnfr/metrics/diagnosis.pyi +89 -0
  81. tnfr/metrics/export.py +27 -13
  82. tnfr/metrics/glyph_timing.py +218 -38
  83. tnfr/metrics/reporting.py +22 -18
  84. tnfr/metrics/reporting.pyi +12 -0
  85. tnfr/metrics/sense_index.py +199 -25
  86. tnfr/metrics/sense_index.pyi +9 -0
  87. tnfr/metrics/trig.py +53 -18
  88. tnfr/metrics/trig.pyi +12 -0
  89. tnfr/metrics/trig_cache.py +3 -7
  90. tnfr/metrics/trig_cache.pyi +10 -0
  91. tnfr/node.py +148 -125
  92. tnfr/node.pyi +161 -0
  93. tnfr/observers.py +44 -30
  94. tnfr/observers.pyi +46 -0
  95. tnfr/ontosim.py +14 -13
  96. tnfr/ontosim.pyi +33 -0
  97. tnfr/operators/__init__.py +84 -52
  98. tnfr/operators/__init__.pyi +31 -0
  99. tnfr/operators/definitions.py +181 -0
  100. tnfr/operators/definitions.pyi +92 -0
  101. tnfr/operators/jitter.py +86 -23
  102. tnfr/operators/jitter.pyi +11 -0
  103. tnfr/operators/registry.py +80 -0
  104. tnfr/operators/registry.pyi +15 -0
  105. tnfr/operators/remesh.py +141 -57
  106. tnfr/presets.py +9 -54
  107. tnfr/presets.pyi +7 -0
  108. tnfr/py.typed +0 -0
  109. tnfr/rng.py +259 -73
  110. tnfr/rng.pyi +14 -0
  111. tnfr/selector.py +24 -17
  112. tnfr/selector.pyi +19 -0
  113. tnfr/sense.py +55 -43
  114. tnfr/sense.pyi +30 -0
  115. tnfr/structural.py +44 -267
  116. tnfr/structural.pyi +46 -0
  117. tnfr/telemetry/__init__.py +13 -0
  118. tnfr/telemetry/verbosity.py +37 -0
  119. tnfr/tokens.py +3 -2
  120. tnfr/tokens.pyi +41 -0
  121. tnfr/trace.py +272 -82
  122. tnfr/trace.pyi +68 -0
  123. tnfr/types.py +345 -6
  124. tnfr/types.pyi +145 -0
  125. tnfr/utils/__init__.py +158 -0
  126. tnfr/utils/__init__.pyi +133 -0
  127. tnfr/utils/cache.py +755 -0
  128. tnfr/utils/cache.pyi +156 -0
  129. tnfr/{collections_utils.py → utils/data.py} +57 -90
  130. tnfr/utils/data.pyi +73 -0
  131. tnfr/utils/graph.py +87 -0
  132. tnfr/utils/graph.pyi +10 -0
  133. tnfr/utils/init.py +746 -0
  134. tnfr/utils/init.pyi +85 -0
  135. tnfr/{json_utils.py → utils/io.py} +13 -18
  136. tnfr/utils/io.pyi +10 -0
  137. tnfr/utils/validators.py +130 -0
  138. tnfr/utils/validators.pyi +19 -0
  139. tnfr/validation/__init__.py +25 -0
  140. tnfr/validation/__init__.pyi +17 -0
  141. tnfr/validation/compatibility.py +59 -0
  142. tnfr/validation/compatibility.pyi +8 -0
  143. tnfr/validation/grammar.py +149 -0
  144. tnfr/validation/grammar.pyi +11 -0
  145. tnfr/validation/rules.py +194 -0
  146. tnfr/validation/rules.pyi +18 -0
  147. tnfr/validation/syntax.py +151 -0
  148. tnfr/validation/syntax.pyi +7 -0
  149. tnfr-6.0.0.dist-info/METADATA +135 -0
  150. tnfr-6.0.0.dist-info/RECORD +157 -0
  151. tnfr/graph_utils.py +0 -84
  152. tnfr/import_utils.py +0 -228
  153. tnfr/logging_utils.py +0 -116
  154. tnfr/validators.py +0 -84
  155. tnfr/value_utils.py +0 -59
  156. tnfr-4.5.2.dist-info/METADATA +0 -379
  157. tnfr-4.5.2.dist-info/RECORD +0 -67
  158. {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/WHEEL +0 -0
  159. {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/entry_points.txt +0 -0
  160. {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/licenses/LICENSE.md +0 -0
  161. {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/top_level.txt +0 -0
tnfr/sense.py CHANGED
@@ -1,18 +1,18 @@
1
1
  """Sense calculations."""
2
2
 
3
3
  from __future__ import annotations
4
- from typing import TypeVar
5
- from collections.abc import Iterable, Mapping
4
+ from typing import Any, Callable, TypeVar
5
+ from collections.abc import Iterable, Iterator, Mapping
6
6
  import math
7
7
  from collections import Counter
8
8
  from itertools import tee
9
9
 
10
- import networkx as nx # type: ignore[import-untyped]
10
+ import networkx as nx
11
11
 
12
12
  from .constants import get_aliases, get_graph_param
13
13
  from .alias import get_attr
14
14
  from .helpers.numeric import clamp01, kahan_sum_nd
15
- from .import_utils import get_numpy
15
+ from .utils import get_numpy
16
16
  from .callback_utils import CallbackEvent, callback_manager
17
17
  from .glyph_history import (
18
18
  ensure_history,
@@ -20,12 +20,13 @@ from .glyph_history import (
20
20
  count_glyphs,
21
21
  append_metric,
22
22
  )
23
- from .constants_glyphs import (
23
+ from .config.constants import (
24
24
  ANGLE_MAP,
25
25
  GLYPHS_CANONICAL,
26
26
  )
27
+ from .types import NodeId, SigmaVector, TNFRGraph
27
28
  # -------------------------
28
- # Canon: orden circular de glyphs y ángulos
29
+ # Canon: circular glyph order and angles
29
30
  # -------------------------
30
31
 
31
32
  GLYPH_UNITS: dict[str, complex] = {
@@ -45,7 +46,7 @@ __all__ = (
45
46
  )
46
47
 
47
48
  # -------------------------
48
- # Utilidades básicas
49
+ # Basic utilities
49
50
  # -------------------------
50
51
 
51
52
 
@@ -58,7 +59,7 @@ def _resolve_glyph(g: str, mapping: Mapping[str, T]) -> T:
58
59
  try:
59
60
  return mapping[g]
60
61
  except KeyError as e: # pragma: no cover - small helper
61
- raise KeyError(f"Glyph desconocido: {g}") from e
62
+ raise KeyError(f"Unknown glyph: {g}") from e
62
63
 
63
64
 
64
65
  def glyph_angle(g: str) -> float:
@@ -76,17 +77,19 @@ def glyph_unit(g: str) -> complex:
76
77
  ALIAS_SI = get_aliases("SI")
77
78
  ALIAS_EPI = get_aliases("EPI")
78
79
 
79
- MODE_FUNCS = {
80
+ MODE_FUNCS: dict[str, Callable[[Mapping[str, Any]], float]] = {
80
81
  "Si": lambda nd: clamp01(get_attr(nd, ALIAS_SI, 0.5)),
81
82
  "EPI": lambda nd: max(0.0, get_attr(nd, ALIAS_EPI, 0.0)),
82
83
  }
83
84
 
84
85
 
85
- def _weight(nd, mode: str) -> float:
86
+ def _weight(nd: Mapping[str, Any], mode: str) -> float:
86
87
  return MODE_FUNCS.get(mode, lambda _: 1.0)(nd)
87
88
 
88
89
 
89
- def _node_weight(nd, weight_mode: str) -> tuple[str, float, complex] | None:
90
+ def _node_weight(
91
+ nd: Mapping[str, Any], weight_mode: str
92
+ ) -> tuple[str, float, complex] | None:
90
93
  """Return ``(glyph, weight, weighted_unit)`` or ``None`` if no glyph."""
91
94
  g = last_glyph(nd)
92
95
  if not g:
@@ -96,7 +99,7 @@ def _node_weight(nd, weight_mode: str) -> tuple[str, float, complex] | None:
96
99
  return g, w, z
97
100
 
98
101
 
99
- def _sigma_cfg(G):
102
+ def _sigma_cfg(G: TNFRGraph) -> dict[str, Any]:
100
103
  return get_graph_param(G, "SIGMA", dict)
101
104
 
102
105
 
@@ -110,7 +113,7 @@ def _to_complex(val: complex | float | int) -> complex:
110
113
  raise TypeError("values must be an iterable of real or complex numbers")
111
114
 
112
115
 
113
- def _empty_sigma(fallback_angle: float) -> dict[str, float]:
116
+ def _empty_sigma(fallback_angle: float) -> SigmaVector:
114
117
  """Return an empty σ-vector with ``fallback_angle``.
115
118
 
116
119
  Helps centralise the default structure returned when no values are
@@ -127,14 +130,14 @@ def _empty_sigma(fallback_angle: float) -> dict[str, float]:
127
130
 
128
131
 
129
132
  # -------------------------
130
- # σ por nodo y σ global
133
+ # σ per node and global σ
131
134
  # -------------------------
132
135
 
133
136
 
134
137
  def _sigma_from_iterable(
135
138
  values: Iterable[complex | float | int] | complex | float | int,
136
139
  fallback_angle: float = 0.0,
137
- ) -> dict[str, float]:
140
+ ) -> SigmaVector:
138
141
  """Normalise vectors in the σ-plane.
139
142
 
140
143
  ``values`` may contain complex or real numbers; real inputs are promoted to
@@ -159,15 +162,15 @@ def _sigma_from_iterable(
159
162
  mag = float(np.hypot(x, y))
160
163
  ang = float(np.arctan2(y, x)) if mag > 0 else float(fallback_angle)
161
164
  return {
162
- "x": x,
163
- "y": y,
164
- "mag": mag,
165
- "angle": ang,
166
- "n": cnt,
165
+ "x": float(x),
166
+ "y": float(y),
167
+ "mag": float(mag),
168
+ "angle": float(ang),
169
+ "n": int(cnt),
167
170
  }
168
171
  cnt = 0
169
172
 
170
- def pair_iter():
173
+ def pair_iter() -> Iterator[tuple[float, float]]:
171
174
  nonlocal cnt
172
175
  for val in iterator:
173
176
  z = _to_complex(val)
@@ -188,24 +191,32 @@ def _sigma_from_iterable(
188
191
  "y": float(y),
189
192
  "mag": float(mag),
190
193
  "angle": float(ang),
191
- "n": cnt,
194
+ "n": int(cnt),
192
195
  }
193
196
 
194
197
 
195
198
  def _ema_update(
196
- prev: dict[str, float], current: dict[str, float], alpha: float
197
- ) -> dict[str, float]:
199
+ prev: SigmaVector, current: SigmaVector, alpha: float
200
+ ) -> SigmaVector:
198
201
  """Exponential moving average update for σ vectors."""
199
202
  x = (1 - alpha) * prev["x"] + alpha * current["x"]
200
203
  y = (1 - alpha) * prev["y"] + alpha * current["y"]
201
204
  mag = math.hypot(x, y)
202
205
  ang = math.atan2(y, x)
203
- return {"x": x, "y": y, "mag": mag, "angle": ang, "n": current.get("n", 0)}
206
+ return {
207
+ "x": float(x),
208
+ "y": float(y),
209
+ "mag": float(mag),
210
+ "angle": float(ang),
211
+ "n": int(current["n"]),
212
+ }
204
213
 
205
214
 
206
215
  def _sigma_from_nodes(
207
- nodes: Iterable[dict], weight_mode: str, fallback_angle: float = 0.0
208
- ) -> tuple[dict[str, float], list[tuple[str, float, complex]]]:
216
+ nodes: Iterable[Mapping[str, Any]],
217
+ weight_mode: str,
218
+ fallback_angle: float = 0.0,
219
+ ) -> tuple[SigmaVector, list[tuple[str, float, complex]]]:
209
220
  """Aggregate weighted glyph vectors for ``nodes``.
210
221
 
211
222
  Returns the aggregated σ vector and the list of ``(glyph, weight, vector)``
@@ -218,8 +229,8 @@ def _sigma_from_nodes(
218
229
 
219
230
 
220
231
  def sigma_vector_node(
221
- G, n, weight_mode: str | None = None
222
- ) -> dict[str, float] | None:
232
+ G: TNFRGraph, n: NodeId, weight_mode: str | None = None
233
+ ) -> SigmaVector | None:
223
234
  cfg = _sigma_cfg(G)
224
235
  nd = G.nodes[n]
225
236
  weight_mode = weight_mode or cfg.get("weight", "Si")
@@ -229,11 +240,12 @@ def sigma_vector_node(
229
240
  g, w, _ = nws[0]
230
241
  if sv["mag"] == 0:
231
242
  sv["angle"] = glyph_angle(g)
232
- sv.update({"glyph": g, "w": float(w)})
243
+ sv["glyph"] = g
244
+ sv["w"] = float(w)
233
245
  return sv
234
246
 
235
247
 
236
- def sigma_vector(dist: dict[str, float]) -> dict[str, float]:
248
+ def sigma_vector(dist: Mapping[str, float]) -> SigmaVector:
237
249
  """Compute Σ⃗ from a glyph distribution.
238
250
 
239
251
  ``dist`` may contain raw counts or proportions. All ``(glyph, weight)``
@@ -246,8 +258,8 @@ def sigma_vector(dist: dict[str, float]) -> dict[str, float]:
246
258
 
247
259
 
248
260
  def sigma_vector_from_graph(
249
- G: nx.Graph, weight_mode: str | None = None
250
- ) -> dict[str, float]:
261
+ G: TNFRGraph, weight_mode: str | None = None
262
+ ) -> SigmaVector:
251
263
  """Global vector in the σ sense plane for a graph.
252
264
 
253
265
  Parameters
@@ -264,7 +276,7 @@ def sigma_vector_from_graph(
264
276
  """
265
277
 
266
278
  if not isinstance(G, nx.Graph):
267
- raise TypeError("sigma_vector_from_graph requiere un networkx.Graph")
279
+ raise TypeError("sigma_vector_from_graph requires a networkx.Graph")
268
280
 
269
281
  cfg = _sigma_cfg(G)
270
282
  weight_mode = weight_mode or cfg.get("weight", "Si")
@@ -275,23 +287,23 @@ def sigma_vector_from_graph(
275
287
 
276
288
 
277
289
  # -------------------------
278
- # Historia / series
290
+ # History / series
279
291
  # -------------------------
280
292
 
281
293
 
282
- def push_sigma_snapshot(G, t: float | None = None) -> None:
294
+ def push_sigma_snapshot(G: TNFRGraph, t: float | None = None) -> None:
283
295
  cfg = _sigma_cfg(G)
284
296
  if not cfg.get("enabled", True):
285
297
  return
286
298
 
287
- # Cache local de la historia para evitar llamadas repetidas
299
+ # Local history cache to avoid repeated lookups
288
300
  hist = ensure_history(G)
289
301
  key = cfg.get("history_key", "sigma_global")
290
302
 
291
303
  weight_mode = cfg.get("weight", "Si")
292
304
  sv = sigma_vector_from_graph(G, weight_mode)
293
305
 
294
- # Suavizado exponencial (EMA) opcional
306
+ # Optional exponential smoothing (EMA)
295
307
  alpha = float(cfg.get("smooth", 0.0))
296
308
  if alpha > 0 and hist.get(key):
297
309
  sv = _ema_update(hist[key][-1], sv, alpha)
@@ -301,11 +313,11 @@ def push_sigma_snapshot(G, t: float | None = None) -> None:
301
313
 
302
314
  append_metric(hist, key, sv)
303
315
 
304
- # Conteo de glyphs por paso (útil para rosa glífica)
316
+ # Glyph count per step (useful for the glyph rose)
305
317
  counts = count_glyphs(G, last_only=True)
306
318
  append_metric(hist, "sigma_counts", {"t": current_t, **counts})
307
319
 
308
- # Trayectoria por nodo (opcional)
320
+ # Optional per-node trajectory
309
321
  if cfg.get("per_node", False):
310
322
  per = hist.setdefault("sigma_per_node", {})
311
323
  for n, nd in G.nodes(data=True):
@@ -317,11 +329,11 @@ def push_sigma_snapshot(G, t: float | None = None) -> None:
317
329
 
318
330
 
319
331
  # -------------------------
320
- # Registro como callback automático (after_step)
332
+ # Register as an automatic callback (after_step)
321
333
  # -------------------------
322
334
 
323
335
 
324
- def register_sigma_callback(G) -> None:
336
+ def register_sigma_callback(G: TNFRGraph) -> None:
325
337
  callback_manager.register_callback(
326
338
  G,
327
339
  event=CallbackEvent.AFTER_STEP.value,
@@ -330,7 +342,7 @@ def register_sigma_callback(G) -> None:
330
342
  )
331
343
 
332
344
 
333
- def sigma_rose(G, steps: int | None = None) -> dict[str, int]:
345
+ def sigma_rose(G: TNFRGraph, steps: int | None = None) -> dict[str, int]:
334
346
  """Histogram of glyphs in the last ``steps`` steps (or all)."""
335
347
  hist = ensure_history(G)
336
348
  counts = hist.get("sigma_counts", [])
tnfr/sense.pyi ADDED
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from typing import Optional
5
+
6
+ from .types import NodeId, SigmaVector, TNFRGraph
7
+
8
+ __all__: tuple[str, ...]
9
+
10
+ GLYPH_UNITS: dict[str, complex]
11
+
12
+ def glyph_angle(g: str) -> float: ...
13
+
14
+ def glyph_unit(g: str) -> complex: ...
15
+
16
+ def push_sigma_snapshot(G: TNFRGraph, t: Optional[float] = None) -> None: ...
17
+
18
+ def register_sigma_callback(G: TNFRGraph) -> None: ...
19
+
20
+ def sigma_rose(G: TNFRGraph, steps: Optional[int] = None) -> dict[str, int]: ...
21
+
22
+ def sigma_vector(dist: Mapping[str, float]) -> SigmaVector: ...
23
+
24
+ def sigma_vector_from_graph(
25
+ G: TNFRGraph, weight_mode: Optional[str] = None
26
+ ) -> SigmaVector: ...
27
+
28
+ def sigma_vector_node(
29
+ G: TNFRGraph, n: NodeId, weight_mode: Optional[str] = None
30
+ ) -> Optional[SigmaVector]: ...
tnfr/structural.py CHANGED
@@ -1,20 +1,39 @@
1
1
  """Structural analysis."""
2
2
 
3
3
  from __future__ import annotations
4
+
4
5
  from typing import Iterable
5
- import networkx as nx # type: ignore[import-untyped]
6
6
 
7
+ import networkx as nx
8
+
9
+ from .constants import EPI_PRIMARY, VF_PRIMARY, THETA_PRIMARY
7
10
  from .dynamics import (
8
11
  set_delta_nfr_hook,
9
12
  dnfr_epi_vf_mixed,
10
13
  )
11
- from .grammar import apply_glyph_with_grammar
12
- from .types import Glyph
13
- from .constants import EPI_PRIMARY, VF_PRIMARY, THETA_PRIMARY
14
+ from .types import DeltaNFRHook, NodeId, TNFRGraph
15
+ from .operators.definitions import (
16
+ Operator,
17
+ Emission,
18
+ Reception,
19
+ Coherence,
20
+ Dissonance,
21
+ Coupling,
22
+ Resonance,
23
+ Silence,
24
+ Expansion,
25
+ Contraction,
26
+ SelfOrganization,
27
+ Mutation,
28
+ Transition,
29
+ Recursivity,
30
+ )
31
+ from .operators.registry import OPERATORS
32
+ from .validation import validate_sequence
14
33
 
15
34
 
16
35
  # ---------------------------------------------------------------------------
17
- # 1) Factoría NFR
36
+ # 1) NFR factory
18
37
  # ---------------------------------------------------------------------------
19
38
 
20
39
 
@@ -24,9 +43,9 @@ def create_nfr(
24
43
  epi: float = 0.0,
25
44
  vf: float = 1.0,
26
45
  theta: float = 0.0,
27
- graph: nx.Graph | None = None,
28
- dnfr_hook=dnfr_epi_vf_mixed,
29
- ) -> tuple[nx.Graph, str]:
46
+ graph: TNFRGraph | None = None,
47
+ dnfr_hook: DeltaNFRHook = dnfr_epi_vf_mixed,
48
+ ) -> tuple[TNFRGraph, str]:
30
49
  """Create a graph with an initialised NFR node.
31
50
 
32
51
  Returns the tuple ``(G, name)`` for convenience.
@@ -44,278 +63,36 @@ def create_nfr(
44
63
  return G, name
45
64
 
46
65
 
47
- # ---------------------------------------------------------------------------
48
- # 2) Operadores estructurales como API de primer orden
49
- # ---------------------------------------------------------------------------
50
-
51
-
52
- class Operador:
53
- """Base class for TNFR operators.
54
-
55
- Each operator defines ``name`` (ASCII identifier) and ``glyph``
56
- (símbolo TNFR canónico). Calling an instance applies the corresponding
57
- symbol to the node.
58
- """
59
-
60
- name = "operador"
61
- glyph = None # tipo: str
62
-
63
- def __call__(self, G: nx.Graph, node, **kw) -> None:
64
- if self.glyph is None:
65
- raise NotImplementedError("Operador sin glyph asignado")
66
- apply_glyph_with_grammar(G, [node], self.glyph, kw.get("window"))
67
-
68
-
69
- class Emision(Operador):
70
- """Aplicación del operador de emisión (símbolo ``AL``)."""
71
-
72
- __slots__ = ()
73
- name = "emision"
74
- glyph = Glyph.AL.value
75
-
76
-
77
- class Recepcion(Operador):
78
- """Operador de recepción (símbolo ``EN``)."""
79
-
80
- __slots__ = ()
81
- name = "recepcion"
82
- glyph = Glyph.EN.value
83
-
84
-
85
- class Coherencia(Operador):
86
- """Operador de coherencia (símbolo ``IL``)."""
87
-
88
- __slots__ = ()
89
- name = "coherencia"
90
- glyph = Glyph.IL.value
91
-
92
-
93
- class Disonancia(Operador):
94
- """Operador de disonancia (símbolo ``OZ``)."""
95
-
96
- __slots__ = ()
97
- name = "disonancia"
98
- glyph = Glyph.OZ.value
99
-
100
-
101
- class Acoplamiento(Operador):
102
- """Operador de acoplamiento (símbolo ``UM``)."""
103
-
104
- __slots__ = ()
105
- name = "acoplamiento"
106
- glyph = Glyph.UM.value
107
-
108
-
109
- class Resonancia(Operador):
110
- """Operador de resonancia (símbolo ``RA``)."""
111
-
112
- __slots__ = ()
113
- name = "resonancia"
114
- glyph = Glyph.RA.value
115
-
116
-
117
- class Silencio(Operador):
118
- """Operador de silencio (símbolo ``SHA``)."""
119
-
120
- __slots__ = ()
121
- name = "silencio"
122
- glyph = Glyph.SHA.value
123
-
124
-
125
- class Expansion(Operador):
126
- """Operador de expansión (símbolo ``VAL``)."""
127
-
128
- __slots__ = ()
129
- name = "expansion"
130
- glyph = Glyph.VAL.value
131
-
132
-
133
- class Contraccion(Operador):
134
- """Operador de contracción (símbolo ``NUL``)."""
135
-
136
- __slots__ = ()
137
- name = "contraccion"
138
- glyph = Glyph.NUL.value
139
-
140
-
141
- class Autoorganizacion(Operador):
142
- """Operador de autoorganización (símbolo ``THOL``)."""
143
-
144
- __slots__ = ()
145
- name = "autoorganizacion"
146
- glyph = Glyph.THOL.value
147
-
148
-
149
- class Mutacion(Operador):
150
- """Operador de mutación (símbolo ``ZHIR``)."""
151
-
152
- __slots__ = ()
153
- name = "mutacion"
154
- glyph = Glyph.ZHIR.value
155
-
156
-
157
- class Transicion(Operador):
158
- """Operador de transición (símbolo ``NAV``)."""
159
-
160
- __slots__ = ()
161
- name = "transicion"
162
- glyph = Glyph.NAV.value
163
-
164
-
165
- class Recursividad(Operador):
166
- """Operador de recursividad (símbolo ``REMESH``)."""
167
-
168
- __slots__ = ()
169
- name = "recursividad"
170
- glyph = Glyph.REMESH.value
171
-
172
-
173
- OPERADORES: dict[str, type[Operador]] = {
174
- Emision.name: Emision,
175
- Recepcion.name: Recepcion,
176
- Coherencia.name: Coherencia,
177
- Disonancia.name: Disonancia,
178
- Acoplamiento.name: Acoplamiento,
179
- Resonancia.name: Resonancia,
180
- Silencio.name: Silencio,
181
- Expansion.name: Expansion,
182
- Contraccion.name: Contraccion,
183
- Autoorganizacion.name: Autoorganizacion,
184
- Mutacion.name: Mutacion,
185
- Transicion.name: Transicion,
186
- Recursividad.name: Recursividad,
187
- }
188
-
189
-
190
66
  __all__ = (
191
67
  "create_nfr",
192
- "Operador",
193
- "Emision",
194
- "Recepcion",
195
- "Coherencia",
196
- "Disonancia",
197
- "Acoplamiento",
198
- "Resonancia",
199
- "Silencio",
68
+ "Operator",
69
+ "Emission",
70
+ "Reception",
71
+ "Coherence",
72
+ "Dissonance",
73
+ "Coupling",
74
+ "Resonance",
75
+ "Silence",
200
76
  "Expansion",
201
- "Contraccion",
202
- "Autoorganizacion",
203
- "Mutacion",
204
- "Transicion",
205
- "Recursividad",
206
- "OPERADORES",
77
+ "Contraction",
78
+ "SelfOrganization",
79
+ "Mutation",
80
+ "Transition",
81
+ "Recursivity",
82
+ "OPERATORS",
207
83
  "validate_sequence",
208
84
  "run_sequence",
209
85
  )
210
- # ---------------------------------------------------------------------------
211
- # 3) Motor de secuencias + validador sintáctico
212
- # ---------------------------------------------------------------------------
213
-
214
-
215
- _INICIO_VALIDOS = {"emision", "recursividad"}
216
- _TRAMO_INTERMEDIO = {"disonancia", "acoplamiento", "resonancia"}
217
- _CIERRE_VALIDO = {"silencio", "transicion", "recursividad"}
218
-
219
-
220
- def _validate_start(token: str) -> tuple[bool, str]:
221
- """Ensure the sequence begins with a valid structural operator."""
222
-
223
- if not isinstance(token, str):
224
- return False, "tokens must be str"
225
- if token not in _INICIO_VALIDOS:
226
- return False, "must start with emission or recursion"
227
- return True, ""
228
-
229
-
230
- def _validate_intermediate(
231
- found_recepcion: bool, found_coherencia: bool, seen_intermedio: bool
232
- ) -> tuple[bool, str]:
233
- """Check that the central TNFR segment is present."""
234
-
235
- if not (found_recepcion and found_coherencia):
236
- return False, "missing input→coherence segment"
237
- if not seen_intermedio:
238
- return False, "missing tension/coupling/resonance segment"
239
- return True, ""
240
-
241
-
242
- def _validate_end(last_token: str, open_thol: bool) -> tuple[bool, str]:
243
- """Validate closing operator and any pending THOL blocks."""
244
-
245
- if last_token not in _CIERRE_VALIDO:
246
- return False, "sequence must end with silence/transition/recursion"
247
- if open_thol:
248
- return False, "THOL block without closure"
249
- return True, ""
250
-
251
-
252
- def _validate_known_tokens(nombres_set: set[str]) -> tuple[bool, str]:
253
- """Ensure all tokens map to canonical operators."""
254
-
255
- desconocidos = nombres_set - OPERADORES.keys()
256
- if desconocidos:
257
- return False, f"unknown tokens: {', '.join(desconocidos)}"
258
- return True, ""
259
-
260
-
261
- def _validate_token_sequence(nombres: list[str]) -> tuple[bool, str]:
262
- """Validate token format and logical coherence in one pass."""
263
-
264
- if not nombres:
265
- return False, "empty sequence"
266
-
267
- ok, msg = _validate_start(nombres[0])
268
- if not ok:
269
- return False, msg
270
-
271
- nombres_set: set[str] = set()
272
- found_recepcion = False
273
- found_coherencia = False
274
- seen_intermedio = False
275
- open_thol = False
276
-
277
- for n in nombres:
278
- if not isinstance(n, str):
279
- return False, "tokens must be str"
280
- nombres_set.add(n)
281
-
282
- if n == "recepcion" and not found_recepcion:
283
- found_recepcion = True
284
- elif found_recepcion and n == "coherencia" and not found_coherencia:
285
- found_coherencia = True
286
- elif found_coherencia and not seen_intermedio and n in _TRAMO_INTERMEDIO:
287
- seen_intermedio = True
288
-
289
- if n == "autoorganizacion":
290
- open_thol = True
291
- elif open_thol and n in {"silencio", "contraccion"}:
292
- open_thol = False
293
-
294
- ok, msg = _validate_known_tokens(nombres_set)
295
- if not ok:
296
- return False, msg
297
- ok, msg = _validate_intermediate(found_recepcion, found_coherencia, seen_intermedio)
298
- if not ok:
299
- return False, msg
300
- ok, msg = _validate_end(nombres[-1], open_thol)
301
- if not ok:
302
- return False, msg
303
- return True, "ok"
304
-
305
-
306
- def validate_sequence(nombres: list[str]) -> tuple[bool, str]:
307
- """Validate minimal TNFR syntax rules."""
308
- return _validate_token_sequence(nombres)
309
86
 
310
87
 
311
- def run_sequence(G: nx.Graph, node, ops: Iterable[Operador]) -> None:
88
+ def run_sequence(G: TNFRGraph, node: NodeId, ops: Iterable[Operator]) -> None:
312
89
  """Execute a sequence of operators on ``node`` after validation."""
313
90
 
314
91
  compute = G.graph.get("compute_delta_nfr")
315
92
  ops_list = list(ops)
316
- nombres = [op.name for op in ops_list]
93
+ names = [op.name for op in ops_list]
317
94
 
318
- ok, msg = validate_sequence(nombres)
95
+ ok, msg = validate_sequence(names)
319
96
  if not ok:
320
97
  raise ValueError(f"Invalid sequence: {msg}")
321
98