tnfr 4.5.1__py3-none-any.whl → 4.5.2__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 (78) hide show
  1. tnfr/__init__.py +91 -90
  2. tnfr/alias.py +546 -0
  3. tnfr/cache.py +578 -0
  4. tnfr/callback_utils.py +388 -0
  5. tnfr/cli/__init__.py +75 -0
  6. tnfr/cli/arguments.py +177 -0
  7. tnfr/cli/execution.py +288 -0
  8. tnfr/cli/utils.py +36 -0
  9. tnfr/collections_utils.py +300 -0
  10. tnfr/config.py +19 -28
  11. tnfr/constants/__init__.py +174 -0
  12. tnfr/constants/core.py +159 -0
  13. tnfr/constants/init.py +31 -0
  14. tnfr/constants/metric.py +110 -0
  15. tnfr/constants_glyphs.py +98 -0
  16. tnfr/dynamics/__init__.py +658 -0
  17. tnfr/dynamics/dnfr.py +733 -0
  18. tnfr/dynamics/integrators.py +267 -0
  19. tnfr/dynamics/sampling.py +31 -0
  20. tnfr/execution.py +201 -0
  21. tnfr/flatten.py +283 -0
  22. tnfr/gamma.py +302 -88
  23. tnfr/glyph_history.py +290 -0
  24. tnfr/grammar.py +285 -96
  25. tnfr/graph_utils.py +84 -0
  26. tnfr/helpers/__init__.py +71 -0
  27. tnfr/helpers/numeric.py +87 -0
  28. tnfr/immutable.py +178 -0
  29. tnfr/import_utils.py +228 -0
  30. tnfr/initialization.py +197 -0
  31. tnfr/io.py +246 -0
  32. tnfr/json_utils.py +162 -0
  33. tnfr/locking.py +37 -0
  34. tnfr/logging_utils.py +116 -0
  35. tnfr/metrics/__init__.py +41 -0
  36. tnfr/metrics/coherence.py +829 -0
  37. tnfr/metrics/common.py +151 -0
  38. tnfr/metrics/core.py +101 -0
  39. tnfr/metrics/diagnosis.py +234 -0
  40. tnfr/metrics/export.py +137 -0
  41. tnfr/metrics/glyph_timing.py +189 -0
  42. tnfr/metrics/reporting.py +148 -0
  43. tnfr/metrics/sense_index.py +120 -0
  44. tnfr/metrics/trig.py +181 -0
  45. tnfr/metrics/trig_cache.py +109 -0
  46. tnfr/node.py +214 -159
  47. tnfr/observers.py +126 -136
  48. tnfr/ontosim.py +134 -134
  49. tnfr/operators/__init__.py +420 -0
  50. tnfr/operators/jitter.py +203 -0
  51. tnfr/operators/remesh.py +485 -0
  52. tnfr/presets.py +46 -14
  53. tnfr/rng.py +254 -0
  54. tnfr/selector.py +210 -0
  55. tnfr/sense.py +284 -131
  56. tnfr/structural.py +207 -79
  57. tnfr/tokens.py +60 -0
  58. tnfr/trace.py +329 -94
  59. tnfr/types.py +43 -17
  60. tnfr/validators.py +70 -24
  61. tnfr/value_utils.py +59 -0
  62. tnfr-4.5.2.dist-info/METADATA +379 -0
  63. tnfr-4.5.2.dist-info/RECORD +67 -0
  64. tnfr/cli.py +0 -322
  65. tnfr/constants.py +0 -277
  66. tnfr/dynamics.py +0 -814
  67. tnfr/helpers.py +0 -264
  68. tnfr/main.py +0 -47
  69. tnfr/metrics.py +0 -597
  70. tnfr/operators.py +0 -525
  71. tnfr/program.py +0 -176
  72. tnfr/scenarios.py +0 -34
  73. tnfr-4.5.1.dist-info/METADATA +0 -221
  74. tnfr-4.5.1.dist-info/RECORD +0 -28
  75. {tnfr-4.5.1.dist-info → tnfr-4.5.2.dist-info}/WHEEL +0 -0
  76. {tnfr-4.5.1.dist-info → tnfr-4.5.2.dist-info}/entry_points.txt +0 -0
  77. {tnfr-4.5.1.dist-info → tnfr-4.5.2.dist-info}/licenses/LICENSE.md +0 -0
  78. {tnfr-4.5.1.dist-info → tnfr-4.5.2.dist-info}/top_level.txt +0 -0
tnfr/structural.py CHANGED
@@ -1,29 +1,23 @@
1
+ """Structural analysis."""
2
+
1
3
  from __future__ import annotations
2
- """API de operadores estructurales y secuencias TNFR.
3
-
4
- Este módulo ofrece:
5
- - Factoría `create_nfr` para inicializar redes/nodos TNFR.
6
- - Clases de operador (`Operador` y derivados) con interfaz común.
7
- - Registro de operadores `OPERADORES`.
8
- - Utilidades `validate_sequence` y `run_sequence` para ejecutar
9
- secuencias canónicas de operadores.
10
- """
11
- from typing import Iterable, Tuple, List
12
- import networkx as nx
4
+ from typing import Iterable
5
+ import networkx as nx # type: ignore[import-untyped]
13
6
 
14
7
  from .dynamics import (
15
8
  set_delta_nfr_hook,
16
- update_epi_via_nodal_equation,
17
9
  dnfr_epi_vf_mixed,
18
10
  )
19
- from .operators import aplicar_glifo
20
- from .constants import ALIAS_EPI, ALIAS_VF, ALIAS_THETA
11
+ from .grammar import apply_glyph_with_grammar
12
+ from .types import Glyph
13
+ from .constants import EPI_PRIMARY, VF_PRIMARY, THETA_PRIMARY
21
14
 
22
15
 
23
16
  # ---------------------------------------------------------------------------
24
17
  # 1) Factoría NFR
25
18
  # ---------------------------------------------------------------------------
26
19
 
20
+
27
21
  def create_nfr(
28
22
  name: str,
29
23
  *,
@@ -32,19 +26,19 @@ def create_nfr(
32
26
  theta: float = 0.0,
33
27
  graph: nx.Graph | None = None,
34
28
  dnfr_hook=dnfr_epi_vf_mixed,
35
- ) -> Tuple[nx.Graph, str]:
36
- """Crea una red (graph) con un nodo NFR inicializado.
29
+ ) -> tuple[nx.Graph, str]:
30
+ """Create a graph with an initialised NFR node.
37
31
 
38
- Devuelve la tupla ``(G, name)`` para conveniencia.
32
+ Returns the tuple ``(G, name)`` for convenience.
39
33
  """
40
- G = graph or nx.Graph()
34
+ G = graph if graph is not None else nx.Graph()
41
35
  G.add_node(
42
36
  name,
43
37
  **{
44
- ALIAS_EPI[0]: float(epi),
45
- ALIAS_VF[0]: float(vf),
46
- ALIAS_THETA[0]: float(theta),
47
- }
38
+ EPI_PRIMARY: float(epi),
39
+ VF_PRIMARY: float(vf),
40
+ THETA_PRIMARY: float(theta),
41
+ },
48
42
  )
49
43
  set_delta_nfr_hook(G, dnfr_hook)
50
44
  return G, name
@@ -56,10 +50,11 @@ def create_nfr(
56
50
 
57
51
 
58
52
  class Operador:
59
- """Base para operadores TNFR.
53
+ """Base class for TNFR operators.
60
54
 
61
- Cada operador define ``name`` (identificador ASCII) y ``glyph`` (glifo
62
- canónico). La llamada ejecuta el glifo correspondiente sobre el nodo.
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.
63
58
  """
64
59
 
65
60
  name = "operador"
@@ -67,96 +62,151 @@ class Operador:
67
62
 
68
63
  def __call__(self, G: nx.Graph, node, **kw) -> None:
69
64
  if self.glyph is None:
70
- raise NotImplementedError("Operador sin glifo asignado")
71
- aplicar_glifo(G, node, self.glyph, **kw)
65
+ raise NotImplementedError("Operador sin glyph asignado")
66
+ apply_glyph_with_grammar(G, [node], self.glyph, kw.get("window"))
72
67
 
73
68
 
74
- # Derivados concretos -------------------------------------------------------
75
69
  class Emision(Operador):
70
+ """Aplicación del operador de emisión (símbolo ``AL``)."""
71
+
72
+ __slots__ = ()
76
73
  name = "emision"
77
- glyph = "A’L"
74
+ glyph = Glyph.AL.value
78
75
 
79
76
 
80
77
  class Recepcion(Operador):
78
+ """Operador de recepción (símbolo ``EN``)."""
79
+
80
+ __slots__ = ()
81
81
  name = "recepcion"
82
- glyph = "E’N"
82
+ glyph = Glyph.EN.value
83
83
 
84
84
 
85
85
  class Coherencia(Operador):
86
+ """Operador de coherencia (símbolo ``IL``)."""
87
+
88
+ __slots__ = ()
86
89
  name = "coherencia"
87
- glyph = "I’L"
90
+ glyph = Glyph.IL.value
88
91
 
89
92
 
90
93
  class Disonancia(Operador):
94
+ """Operador de disonancia (símbolo ``OZ``)."""
95
+
96
+ __slots__ = ()
91
97
  name = "disonancia"
92
- glyph = "O’Z"
98
+ glyph = Glyph.OZ.value
93
99
 
94
100
 
95
101
  class Acoplamiento(Operador):
102
+ """Operador de acoplamiento (símbolo ``UM``)."""
103
+
104
+ __slots__ = ()
96
105
  name = "acoplamiento"
97
- glyph = "U’M"
106
+ glyph = Glyph.UM.value
98
107
 
99
108
 
100
109
  class Resonancia(Operador):
110
+ """Operador de resonancia (símbolo ``RA``)."""
111
+
112
+ __slots__ = ()
101
113
  name = "resonancia"
102
- glyph = "R’A"
114
+ glyph = Glyph.RA.value
103
115
 
104
116
 
105
117
  class Silencio(Operador):
118
+ """Operador de silencio (símbolo ``SHA``)."""
119
+
120
+ __slots__ = ()
106
121
  name = "silencio"
107
- glyph = "SH’A"
122
+ glyph = Glyph.SHA.value
108
123
 
109
124
 
110
125
  class Expansion(Operador):
126
+ """Operador de expansión (símbolo ``VAL``)."""
127
+
128
+ __slots__ = ()
111
129
  name = "expansion"
112
- glyph = "VA’L"
130
+ glyph = Glyph.VAL.value
113
131
 
114
132
 
115
133
  class Contraccion(Operador):
134
+ """Operador de contracción (símbolo ``NUL``)."""
135
+
136
+ __slots__ = ()
116
137
  name = "contraccion"
117
- glyph = "NU’L"
138
+ glyph = Glyph.NUL.value
118
139
 
119
140
 
120
141
  class Autoorganizacion(Operador):
142
+ """Operador de autoorganización (símbolo ``THOL``)."""
143
+
144
+ __slots__ = ()
121
145
  name = "autoorganizacion"
122
- glyph = "T’HOL"
146
+ glyph = Glyph.THOL.value
123
147
 
124
148
 
125
149
  class Mutacion(Operador):
150
+ """Operador de mutación (símbolo ``ZHIR``)."""
151
+
152
+ __slots__ = ()
126
153
  name = "mutacion"
127
- glyph = "Z’HIR"
154
+ glyph = Glyph.ZHIR.value
128
155
 
129
156
 
130
157
  class Transicion(Operador):
158
+ """Operador de transición (símbolo ``NAV``)."""
159
+
160
+ __slots__ = ()
131
161
  name = "transicion"
132
- glyph = "NA’V"
162
+ glyph = Glyph.NAV.value
133
163
 
134
164
 
135
165
  class Recursividad(Operador):
166
+ """Operador de recursividad (símbolo ``REMESH``)."""
167
+
168
+ __slots__ = ()
136
169
  name = "recursividad"
137
- glyph = "RE’MESH"
138
-
139
-
140
- OPERADORES = {
141
- op().name: op
142
- for op in [
143
- Emision,
144
- Recepcion,
145
- Coherencia,
146
- Disonancia,
147
- Acoplamiento,
148
- Resonancia,
149
- Silencio,
150
- Expansion,
151
- Contraccion,
152
- Autoorganizacion,
153
- Mutacion,
154
- Transicion,
155
- Recursividad,
156
- ]
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,
157
187
  }
158
188
 
159
189
 
190
+ __all__ = (
191
+ "create_nfr",
192
+ "Operador",
193
+ "Emision",
194
+ "Recepcion",
195
+ "Coherencia",
196
+ "Disonancia",
197
+ "Acoplamiento",
198
+ "Resonancia",
199
+ "Silencio",
200
+ "Expansion",
201
+ "Contraccion",
202
+ "Autoorganizacion",
203
+ "Mutacion",
204
+ "Transicion",
205
+ "Recursividad",
206
+ "OPERADORES",
207
+ "validate_sequence",
208
+ "run_sequence",
209
+ )
160
210
  # ---------------------------------------------------------------------------
161
211
  # 3) Motor de secuencias + validador sintáctico
162
212
  # ---------------------------------------------------------------------------
@@ -167,35 +217,113 @@ _TRAMO_INTERMEDIO = {"disonancia", "acoplamiento", "resonancia"}
167
217
  _CIERRE_VALIDO = {"silencio", "transicion", "recursividad"}
168
218
 
169
219
 
170
- def validate_sequence(nombres: List[str]) -> Tuple[bool, str]:
171
- """Valida reglas mínimas de la sintaxis TNFR."""
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
+
172
264
  if not nombres:
173
- return False, "secuencia vacía"
174
- if nombres[0] not in _INICIO_VALIDOS:
175
- return False, "debe iniciar en emisión o recursividad"
176
- try:
177
- i_rec = nombres.index("recepcion")
178
- i_coh = nombres.index("coherencia", i_rec + 1)
179
- except ValueError:
180
- return False, "falta tramo entrada→coherencia"
181
- if not any(n in _TRAMO_INTERMEDIO for n in nombres[i_coh + 1 :]):
182
- return False, "falta tramo de tensión/acoplamiento/resonancia"
183
- if not any(n in _CIERRE_VALIDO for n in nombres[-2:]):
184
- return False, "falta cierre (silencio/transición/recursividad)"
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
185
303
  return True, "ok"
186
304
 
187
305
 
306
+ def validate_sequence(nombres: list[str]) -> tuple[bool, str]:
307
+ """Validate minimal TNFR syntax rules."""
308
+ return _validate_token_sequence(nombres)
309
+
310
+
188
311
  def run_sequence(G: nx.Graph, node, ops: Iterable[Operador]) -> None:
189
- """Ejecuta una secuencia validada de operadores sobre el nodo dado."""
312
+ """Execute a sequence of operators on ``node`` after validation."""
313
+
314
+ compute = G.graph.get("compute_delta_nfr")
190
315
  ops_list = list(ops)
191
316
  nombres = [op.name for op in ops_list]
317
+
192
318
  ok, msg = validate_sequence(nombres)
193
319
  if not ok:
194
- raise ValueError(f"Secuencia no válida: {msg}")
320
+ raise ValueError(f"Invalid sequence: {msg}")
321
+
195
322
  for op in ops_list:
196
323
  op(G, node)
197
- compute = G.graph.get("compute_delta_nfr")
198
324
  if callable(compute):
199
325
  compute(G)
200
- update_epi_via_nodal_equation(G)
201
-
326
+ # ``update_epi_via_nodal_equation`` was previously invoked here to
327
+ # recalculate the EPI value after each operator. The responsibility for
328
+ # updating EPI now lies with the dynamics hook configured in
329
+ # ``compute_delta_nfr`` or with external callers.
tnfr/tokens.py ADDED
@@ -0,0 +1,60 @@
1
+ """Token primitives for the TNFR DSL."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import Enum, auto
7
+ from typing import Any, Iterable, Optional, Sequence, Union
8
+
9
+ from .types import Glyph
10
+
11
+ Node = Any
12
+
13
+
14
+ @dataclass(slots=True)
15
+ class WAIT:
16
+ """Wait a number of steps without applying glyphs."""
17
+
18
+ steps: int = 1
19
+
20
+
21
+ @dataclass(slots=True)
22
+ class TARGET:
23
+ """Select the subset of nodes for subsequent glyphs."""
24
+
25
+ nodes: Optional[Iterable[Node]] = None # ``None`` targets all nodes
26
+
27
+
28
+ @dataclass(slots=True)
29
+ class THOL:
30
+ """THOL block that opens self-organisation."""
31
+
32
+ body: Sequence[Any]
33
+ repeat: int = 1
34
+ force_close: Optional[Glyph] = None
35
+
36
+
37
+ Token = Union[Glyph, WAIT, TARGET, THOL, str]
38
+
39
+ # Sentinel used internally to mark the boundaries of a THOL block during flattening
40
+ THOL_SENTINEL = object()
41
+
42
+
43
+ class OpTag(Enum):
44
+ """Operation tags emitted by the flattening step."""
45
+
46
+ TARGET = auto()
47
+ WAIT = auto()
48
+ GLYPH = auto()
49
+ THOL = auto()
50
+
51
+
52
+ __all__ = [
53
+ "Node",
54
+ "WAIT",
55
+ "TARGET",
56
+ "THOL",
57
+ "Token",
58
+ "THOL_SENTINEL",
59
+ "OpTag",
60
+ ]