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

tnfr/__init__.py CHANGED
@@ -1,3 +1,4 @@
1
+
1
2
  from __future__ import annotations
2
3
  """
3
4
  TNFR — Teoría de la Naturaleza Fractal Resonante
@@ -7,46 +8,12 @@ Ecuación nodal:
7
8
  ∂EPI/∂t = νf · ΔNFR(t)
8
9
  """
9
10
 
10
- __version__ = "3.0.3"
11
-
12
- import sys as _sys
13
-
14
- # ------------------------------------------------------------
15
- # 1) Crear alias para módulos con imports absolutos entre sí
16
- # (los submódulos usan cosas tipo `from constants import ...`)
17
- # Por eso registramos primero: constants → helpers → operators → observers
18
- # ------------------------------------------------------------
19
- from . import constants as _constants
20
- _sys.modules.setdefault("constants", _constants)
21
-
22
- from . import helpers as _helpers
23
- _sys.modules.setdefault("helpers", _helpers)
24
-
25
- from . import operators as _operators
26
- _sys.modules.setdefault("operators", _operators)
27
-
28
- from . import observers as _observers
29
- _sys.modules.setdefault("observers", _observers)
30
-
31
- # ------------------------------------------------------------
32
- # 2) IMPORTAR dynamics y ALIAS antes de ontosim
33
- # (porque ontosim hace `from dynamics import ...`)
34
- # ------------------------------------------------------------
35
- from . import dynamics as _dynamics
36
- _sys.modules.setdefault("dynamics", _dynamics)
37
-
38
- # ------------------------------------------------------------
39
- # 3) Ahora sí, importar ontosim y alias
40
- # ------------------------------------------------------------
41
- from . import ontosim as _ontosim
42
- _sys.modules.setdefault("ontosim", _ontosim)
43
-
44
- # ------------------------------------------------------------
45
- # 4) Re-exports de la API pública
46
- # ------------------------------------------------------------
47
- from .dynamics import step, run, set_delta_nfr_hook
48
- from .ontosim import preparar_red
49
- from .observers import attach_standard_observer, coherencia_global, orden_kuramoto
11
+ __version__ = "3.0.3"
12
+
13
+ # Re-exports de la API pública
14
+ from .dynamics import step, run, set_delta_nfr_hook
15
+ from .ontosim import preparar_red
16
+ from .observers import attach_standard_observer, coherencia_global, orden_kuramoto
50
17
 
51
18
  __all__ = [
52
19
  "preparar_red",
tnfr/dynamics.py CHANGED
@@ -13,11 +13,13 @@ Incluye:
13
13
  from __future__ import annotations
14
14
  from typing import Dict, Any, Iterable
15
15
  import math
16
+ from collections import deque
17
+ import networkx as nx
16
18
 
17
- from observers import sincronía_fase, carga_glifica, orden_kuramoto, sigma_vector
18
- from operators import aplicar_remesh_si_estabilizacion_global
19
- from constants import DEFAULTS, ALIAS_VF, ALIAS_THETA, ALIAS_DNFR, ALIAS_EPI, ALIAS_SI, ALIAS_dEPI, ALIAS_D2EPI
20
- from helpers import (
19
+ from .observers import sincronía_fase, carga_glifica, orden_kuramoto, sigma_vector
20
+ from .operators import aplicar_remesh_si_estabilizacion_global
21
+ from .constants import DEFAULTS, ALIAS_VF, ALIAS_THETA, ALIAS_DNFR, ALIAS_EPI, ALIAS_SI, ALIAS_dEPI, ALIAS_D2EPI
22
+ from .helpers import (
21
23
  clamp, clamp01, list_mean, phase_distance,
22
24
  _get_attr, _set_attr, media_vecinal, fase_media,
23
25
  invoke_callbacks, reciente_glifo
@@ -120,8 +122,16 @@ def dnfr_epi_vf_mixed(G) -> None:
120
122
  # -------------------------
121
123
 
122
124
  def update_epi_via_nodal_equation(G, *, dt: float = None) -> None:
125
+ if not isinstance(G, (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)):
126
+ raise TypeError("G must be a networkx graph instance")
123
127
  if dt is None:
124
128
  dt = float(G.graph.get("DT", DEFAULTS["DT"]))
129
+ else:
130
+ if not isinstance(dt, (int, float)):
131
+ raise TypeError("dt must be a number")
132
+ if dt < 0:
133
+ raise ValueError("dt must be non-negative")
134
+ dt = float(dt)
125
135
  for n in G.nodes():
126
136
  nd = G.nodes[n]
127
137
  vf = _get_attr(nd, ALIAS_VF, 0.0)
@@ -175,20 +185,30 @@ def coordinar_fase_global_vecinal(G, fuerza_global: float | None = None, fuerza_
175
185
  Si no se pasan fuerzas explícitas, adapta kG/kL según estado (disonante / transición / estable).
176
186
  Estado se decide por R (Kuramoto) y carga glífica disruptiva reciente.
177
187
  """
188
+ g = G.graph
189
+ defaults = DEFAULTS
178
190
  # 0) Si hay fuerzas explícitas, usar y salir del modo adaptativo
179
191
  if (fuerza_global is not None) or (fuerza_vecinal is not None):
180
- kG = float(fuerza_global if fuerza_global is not None else G.graph.get("PHASE_K_GLOBAL", DEFAULTS["PHASE_K_GLOBAL"]))
181
- kL = float(fuerza_vecinal if fuerza_vecinal is not None else G.graph.get("PHASE_K_LOCAL", DEFAULTS["PHASE_K_LOCAL"]))
192
+ kG = float(
193
+ fuerza_global
194
+ if fuerza_global is not None
195
+ else g.get("PHASE_K_GLOBAL", defaults["PHASE_K_GLOBAL"])
196
+ )
197
+ kL = float(
198
+ fuerza_vecinal
199
+ if fuerza_vecinal is not None
200
+ else g.get("PHASE_K_LOCAL", defaults["PHASE_K_LOCAL"])
201
+ )
182
202
  else:
183
203
  # 1) Lectura de configuración
184
- cfg = G.graph.get("PHASE_ADAPT", DEFAULTS.get("PHASE_ADAPT", {}))
185
- kG = float(G.graph.get("PHASE_K_GLOBAL", DEFAULTS["PHASE_K_GLOBAL"]))
186
- kL = float(G.graph.get("PHASE_K_LOCAL", DEFAULTS["PHASE_K_LOCAL"]))
204
+ cfg = g.get("PHASE_ADAPT", defaults.get("PHASE_ADAPT", {}))
205
+ kG = float(g.get("PHASE_K_GLOBAL", defaults["PHASE_K_GLOBAL"]))
206
+ kL = float(g.get("PHASE_K_LOCAL", defaults["PHASE_K_LOCAL"]))
187
207
 
188
208
  if bool(cfg.get("enabled", False)):
189
209
  # 2) Métricas actuales (no dependemos de history)
190
210
  R = orden_kuramoto(G)
191
- win = int(G.graph.get("GLYPH_LOAD_WINDOW", DEFAULTS["GLYPH_LOAD_WINDOW"]))
211
+ win = int(g.get("GLYPH_LOAD_WINDOW", defaults["GLYPH_LOAD_WINDOW"]))
192
212
  dist = carga_glifica(G, window=win)
193
213
  disr = float(dist.get("_disruptivos", 0.0)) if dist else 0.0
194
214
 
@@ -216,7 +236,9 @@ def coordinar_fase_global_vecinal(G, fuerza_global: float | None = None, fuerza_
216
236
  kG_t = 0.5 * (kG_min + kG_max)
217
237
  kL_t = 0.5 * (kL_min + kL_max)
218
238
 
219
- up = float(cfg.get("up", 0.10)); down = float(cfg.get("down", 0.07))
239
+ up = float(cfg.get("up", 0.10))
240
+ down = float(cfg.get("down", 0.07))
241
+
220
242
  def _step(curr, target, mn, mx):
221
243
  gain = up if target > curr else down
222
244
  nxt = curr + gain * (target - curr)
@@ -226,14 +248,19 @@ def coordinar_fase_global_vecinal(G, fuerza_global: float | None = None, fuerza_
226
248
  kL = _step(kL, kL_t, kL_min, kL_max)
227
249
 
228
250
  # 5) Persistir en G.graph y log de serie
229
- G.graph["PHASE_K_GLOBAL"] = kG
230
- G.graph["PHASE_K_LOCAL"] = kL
231
- hist = G.graph.setdefault("history", {})
232
- hist.setdefault("phase_kG", []).append(float(kG))
233
- hist.setdefault("phase_kL", []).append(float(kL))
234
- hist.setdefault("phase_state", []).append(state)
235
- hist.setdefault("phase_R", []).append(float(R))
236
- hist.setdefault("phase_disr", []).append(float(disr))
251
+ g["PHASE_K_GLOBAL"] = kG
252
+ g["PHASE_K_LOCAL"] = kL
253
+ hist = g.setdefault("history", {})
254
+ hist_kG = hist.setdefault("phase_kG", [])
255
+ hist_kL = hist.setdefault("phase_kL", [])
256
+ hist_state = hist.setdefault("phase_state", [])
257
+ hist_R = hist.setdefault("phase_R", [])
258
+ hist_disr = hist.setdefault("phase_disr", [])
259
+ hist_kG.append(float(kG))
260
+ hist_kL.append(float(kL))
261
+ hist_state.append(state)
262
+ hist_R.append(float(R))
263
+ hist_disr.append(float(disr))
237
264
 
238
265
  # 6) Fase GLOBAL (centroide) para empuje
239
266
  X = list(math.cos(_get_attr(G.nodes[n], ALIAS_THETA, 0.0)) for n in G.nodes())
@@ -394,7 +421,7 @@ def step(G, *, dt: float | None = None, use_Si: bool = True, apply_glyphs: bool
394
421
 
395
422
  # 2) (opcional) Si
396
423
  if use_Si:
397
- from helpers import compute_Si
424
+ from .helpers import compute_Si
398
425
  compute_Si(G, inplace=True)
399
426
 
400
427
  # 2b) Normalizadores para selector paramétrico (por paso)
@@ -403,7 +430,7 @@ def step(G, *, dt: float | None = None, use_Si: bool = True, apply_glyphs: bool
403
430
  # 3) Selección glífica + aplicación
404
431
  if apply_glyphs:
405
432
  selector = G.graph.get("glyph_selector", default_glyph_selector)
406
- from operators import aplicar_glifo
433
+ from .operators import aplicar_glifo
407
434
  window = int(G.graph.get("GLYPH_HYSTERESIS_WINDOW", DEFAULTS["GLYPH_HYSTERESIS_WINDOW"]))
408
435
  for n in G.nodes():
409
436
  g = selector(G, n)
@@ -422,13 +449,13 @@ def step(G, *, dt: float | None = None, use_Si: bool = True, apply_glyphs: bool
422
449
  # 7) Observadores ligeros
423
450
  _update_history(G)
424
451
  # dynamics.py — dentro de step(), justo antes del punto 8)
425
- epi_hist = G.graph.setdefault("_epi_hist", [])
426
- epi_hist.append({n: _get_attr(G.nodes[n], ALIAS_EPI, 0.0) for n in G.nodes()})
427
- # recorta el buffer para que no crezca sin límite
428
452
  tau = int(G.graph.get("REMESH_TAU", DEFAULTS["REMESH_TAU"]))
429
- maxlen = max(2*tau + 5, 64)
430
- if len(epi_hist) > maxlen:
431
- del epi_hist[:-maxlen]
453
+ maxlen = max(2 * tau + 5, 64)
454
+ epi_hist = G.graph.get("_epi_hist")
455
+ if not isinstance(epi_hist, deque) or epi_hist.maxlen != maxlen:
456
+ epi_hist = deque(list(epi_hist or [])[-maxlen:], maxlen=maxlen)
457
+ G.graph["_epi_hist"] = epi_hist
458
+ epi_hist.append({n: _get_attr(G.nodes[n], ALIAS_EPI, 0.0) for n in G.nodes()})
432
459
 
433
460
  # 8) RE’MESH condicionado
434
461
  aplicar_remesh_si_estabilizacion_global(G)
tnfr/helpers.py CHANGED
@@ -7,13 +7,14 @@ from __future__ import annotations
7
7
  from typing import Iterable, Dict, Any, Tuple, List
8
8
  import math
9
9
  from collections import deque
10
+ from statistics import fmean, StatisticsError
10
11
 
11
12
  try:
12
13
  import networkx as nx # solo para tipos
13
14
  except Exception: # pragma: no cover
14
15
  nx = None # type: ignore
15
16
 
16
- from constants import DEFAULTS, ALIAS_VF, ALIAS_THETA, ALIAS_DNFR, ALIAS_EPI, ALIAS_SI
17
+ from .constants import DEFAULTS, ALIAS_VF, ALIAS_THETA, ALIAS_DNFR, ALIAS_EPI, ALIAS_SI
17
18
 
18
19
  # -------------------------
19
20
  # Utilidades numéricas
@@ -33,8 +34,10 @@ def clamp01(x: float) -> float:
33
34
 
34
35
 
35
36
  def list_mean(xs: Iterable[float], default: float = 0.0) -> float:
36
- xs = list(xs)
37
- return sum(xs) / len(xs) if xs else default
37
+ try:
38
+ return fmean(xs)
39
+ except StatisticsError:
40
+ return default
38
41
 
39
42
 
40
43
  def _wrap_angle(a: float) -> float:
@@ -82,7 +85,6 @@ def media_vecinal(G, n, aliases: Iterable[str], default: float = 0.0) -> float:
82
85
 
83
86
  def fase_media(G, n) -> float:
84
87
  """Promedio circular de las fases de los vecinos."""
85
- import math
86
88
  x = 0.0
87
89
  y = 0.0
88
90
  count = 0
tnfr/observers.py CHANGED
@@ -8,8 +8,8 @@ from collections import Counter
8
8
  from typing import Dict, Any
9
9
  import math
10
10
 
11
- from constants import ALIAS_DNFR, ALIAS_EPI, ALIAS_THETA, ALIAS_dEPI
12
- from helpers import _get_attr, list_mean, register_callback
11
+ from .constants import ALIAS_DNFR, ALIAS_EPI, ALIAS_THETA, ALIAS_dEPI
12
+ from .helpers import _get_attr, list_mean, register_callback
13
13
 
14
14
  # -------------------------
15
15
  # Observador estándar Γ(R)
@@ -44,15 +44,27 @@ def coherencia_global(G) -> float:
44
44
 
45
45
 
46
46
  def sincronía_fase(G) -> float:
47
- X = list(math.cos(_get_attr(G.nodes[n], ALIAS_THETA, 0.0)) for n in G.nodes())
48
- Y = list(math.sin(_get_attr(G.nodes[n], ALIAS_THETA, 0.0)) for n in G.nodes())
47
+ X = [math.cos(_get_attr(G.nodes[n], ALIAS_THETA, 0.0)) for n in G.nodes()]
48
+ Y = [math.sin(_get_attr(G.nodes[n], ALIAS_THETA, 0.0)) for n in G.nodes()]
49
49
  if not X:
50
50
  return 1.0
51
- import math
52
- th = math.atan2(sum(Y)/len(Y), sum(X)/len(X))
51
+ th = math.atan2(sum(Y) / len(Y), sum(X) / len(X))
53
52
  # varianza angular aproximada (0 = muy sincronizado)
54
53
  import statistics as st
55
- var = st.pvariance([((_get_attr(G.nodes[n], ALIAS_THETA, 0.0) - th + math.pi) % (2*math.pi) - math.pi) for n in G.nodes()]) if len(X) > 1 else 0.0
54
+ var = (
55
+ st.pvariance(
56
+ [
57
+ (
58
+ (_get_attr(G.nodes[n], ALIAS_THETA, 0.0) - th + math.pi)
59
+ % (2 * math.pi)
60
+ - math.pi
61
+ )
62
+ for n in G.nodes()
63
+ ]
64
+ )
65
+ if len(X) > 1
66
+ else 0.0
67
+ )
56
68
  return 1.0 / (1.0 + var)
57
69
 
58
70
  def orden_kuramoto(G) -> float:
tnfr/ontosim.py CHANGED
@@ -5,20 +5,21 @@ Módulo de orquestación mínima que encadena:
5
5
  ΔNFR (campo) → Si → glifos → ecuación nodal → clamps → U’M → observadores → RE’MESH
6
6
  """
7
7
  from __future__ import annotations
8
- import networkx as nx
9
- import math
10
- import random
8
+ import networkx as nx
9
+ import math
10
+ import random
11
+ from collections import deque
11
12
 
12
- from constants import DEFAULTS, attach_defaults
13
- from dynamics import step as _step, run as _run
14
- from dynamics import default_compute_delta_nfr
13
+ from .constants import DEFAULTS, attach_defaults
14
+ from .dynamics import step as _step, run as _run
15
+ from .dynamics import default_compute_delta_nfr
15
16
 
16
17
  # API de alto nivel
17
18
 
18
19
  def preparar_red(G: nx.Graph, *, override_defaults: bool = False, **overrides) -> nx.Graph:
19
20
  attach_defaults(G, override=override_defaults)
20
21
  if overrides:
21
- from constants import merge_overrides
22
+ from .constants import merge_overrides
22
23
  merge_overrides(G, **overrides)
23
24
  # Inicializaciones blandas
24
25
  G.graph.setdefault("history", {
@@ -43,11 +44,13 @@ def preparar_red(G: nx.Graph, *, override_defaults: bool = False, **overrides) -
43
44
  "phase_R": [],
44
45
  "phase_disr": [],
45
46
  })
46
- G.graph.setdefault("_epi_hist", [])
47
+ tau = int(G.graph.get("REMESH_TAU", DEFAULTS["REMESH_TAU"]))
48
+ maxlen = max(2 * tau + 5, 64)
49
+ G.graph.setdefault("_epi_hist", deque(maxlen=maxlen))
47
50
  # Auto-attach del observador estándar si se pide
48
51
  if G.graph.get("ATTACH_STD_OBSERVER", False):
49
52
  try:
50
- from observers import attach_standard_observer
53
+ from .observers import attach_standard_observer
51
54
  attach_standard_observer(G)
52
55
  except Exception as e:
53
56
  G.graph.setdefault("_callback_errors", []).append(
tnfr/operators.py CHANGED
@@ -5,14 +5,15 @@ import math
5
5
  import random
6
6
  import hashlib
7
7
 
8
- from constants import DEFAULTS, ALIAS_VF, ALIAS_THETA, ALIAS_DNFR, ALIAS_EPI, ALIAS_D2EPI
9
- from helpers import _get_attr, _set_attr, clamp, clamp01, list_mean, fase_media, push_glifo, invoke_callbacks
8
+ from .constants import DEFAULTS, ALIAS_VF, ALIAS_THETA, ALIAS_DNFR, ALIAS_EPI, ALIAS_D2EPI
9
+ from .helpers import _get_attr, _set_attr, clamp, clamp01, list_mean, fase_media, push_glifo, invoke_callbacks
10
+ from collections import deque
10
11
 
11
12
  """
12
13
  Este módulo implementa:
13
14
  - Los 13 glifos como operadores locales suaves.
14
15
  - Un dispatcher `aplicar_glifo` que mapea el nombre del glifo (con apóstrofo tipográfico) a su función.
15
- - RE’MESH de red: `aplicar_remesh_red` y `aplicar_remesh_si_estabilizacion_global`.
16
+ - RE’MESH de red: `aplicar_remesh_red` y `aplicar_remesh_si_estabilización_global`.
16
17
 
17
18
  Nota sobre α (alpha) de RE’MESH: se toma por prioridad de
18
19
  1) G.graph["GLYPH_FACTORS"]["REMESH_alpha"]
@@ -186,7 +187,7 @@ def aplicar_remesh_red(G) -> None:
186
187
  """
187
188
  tau = int(G.graph.get("REMESH_TAU", DEFAULTS["REMESH_TAU"]))
188
189
  alpha, alpha_src = _remesh_alpha_info(G)
189
- hist = G.graph.get("_epi_hist", [])
190
+ hist = G.graph.get("_epi_hist", deque())
190
191
  if len(hist) < tau + 1:
191
192
  return
192
193
 
@@ -256,7 +257,11 @@ def aplicar_remesh_red(G) -> None:
256
257
 
257
258
  def aplicar_remesh_si_estabilizacion_global(G, pasos_estables_consecutivos: Optional[int] = None) -> None:
258
259
  # Ventanas y umbrales
259
- w_estab = int(G.graph.get("REMESH_STABILITY_WINDOW", DEFAULTS["REMESH_STABILITY_WINDOW"]))
260
+ w_estab = (
261
+ pasos_estables_consecutivos
262
+ if pasos_estables_consecutivos is not None
263
+ else int(G.graph.get("REMESH_STABILITY_WINDOW", DEFAULTS["REMESH_STABILITY_WINDOW"]))
264
+ )
260
265
  frac_req = float(G.graph.get("FRACTION_STABLE_REMESH", DEFAULTS["FRACTION_STABLE_REMESH"]))
261
266
  req_extra = bool(G.graph.get("REMESH_REQUIRE_STABILITY", DEFAULTS["REMESH_REQUIRE_STABILITY"]))
262
267
  min_sync = float(G.graph.get("REMESH_MIN_PHASE_SYNC", DEFAULTS["REMESH_MIN_PHASE_SYNC"]))
@@ -293,4 +298,4 @@ def aplicar_remesh_si_estabilizacion_global(G, pasos_estables_consecutivos: Opti
293
298
  return
294
299
  # 4) Aplicar y registrar
295
300
  aplicar_remesh_red(G)
296
- G.graph["_last_remesh_step"] = step_idx
301
+ G.graph["_last_remesh_step"] = step_idx
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.4
2
+ Name: tnfr
3
+ Version: 3.5.0
4
+ Summary: TNFR canónica: dinámica glífica modular sobre redes.
5
+ Author: fmg
6
+ License: MIT
7
+ Project-URL: Homepage, https://pypi.org/project/tnfr/
8
+ Project-URL: Repository, https://github.com/fermga/Teoria-de-la-naturaleza-fractal-resonante-TNFR-
9
+ Keywords: TNFR,fractal resonante,resonancia,glifos,networkx,dinámica,coherencia,EPI,Kuramoto
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Intended Audience :: Science/Research
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE.md
25
+ Requires-Dist: networkx>=2.6
26
+ Dynamic: license-file
27
+
28
+ # General Project Structure
29
+
30
+ * **Package entry point.** `__init__.py` registers modules under short names to avoid circular imports and exposes the public API: `preparar_red`, `step`, `run`, and observation utilities.
31
+
32
+ * **Configuration & constants.** `constants.py` centralizes default parameters (discretization, EPI and νf ranges, mixing weights, re-mesh limits, etc.) and provides utilities to inject them into the network (`attach_defaults`, `merge_overrides`), along with standardized aliases for node attributes.
33
+
34
+ * **Cross-cutting utilities.** `helpers.py` offers core numeric helpers, alias-based attribute accessors, neighborhood statistics, glyph history, a callback system, and computation of the sense index `Si` for each node.
35
+
36
+ * **Dynamics engine.** `dynamics.py` implements the simulation loop: ΔNFR field computation, nodal equation integration, glyph selection/application, clamps, phase coordination, history updates, and conditional re-mesh (`step` and `run`).
37
+
38
+ * **Glyph operators.** `operators.py` defines the 13 glyphs as local transformations, a dispatcher `aplicar_glifo`, and both direct and stability-conditioned re-mesh utilities.
39
+
40
+ * **Observers & metrics.** `observers.py` registers standard callbacks and computes global coherence, phase synchrony, Kuramoto order, glyph distribution, and the sense vector `Σ⃗`, among others.
41
+
42
+ * **Simulation orchestration.** `ontosim.py` prepares a NetworkX graph, attaches configuration, and initializes attributes (EPI, phases, frequencies) before delegating dynamics to `dynamics.step`/`run`.
43
+
44
+ * **Demo CLI.** `main.py` generates an Erdős–Rényi network, lets you tweak basic parameters, and runs the simulation while displaying final metrics.
45
+
46
+ ---
47
+
48
+ ## Key Concepts to Grasp
49
+
50
+ * **Aliased dependency tree.** Modules import each other via global aliases to simplify access and prevent cycles—essential for navigating the code unambiguously.
51
+
52
+ * **Normalized node attributes.** All data (EPI, phase `θ`, frequency `νf`, `ΔNFR`, etc.) live in `G.nodes[n]` under compatible alias names, making extensions and custom hooks straightforward.
53
+
54
+ * **Sense Index (`Si`).** Combines normalized frequency, phase dispersion, and field magnitude to evaluate each node’s “sense,” influencing glyph selection.
55
+
56
+ * **Step-wise engine.** `dynamics.step` orchestrates eight phases: field computation, `Si`, glyph selection & application, integration, clamps, phase coordination, history update, and conditioned re-mesh.
57
+
58
+ * **Glyphs as operators.** Each glyph applies a smooth transformation to node attributes (emission, diffusion, coupling, dissonance, etc.), dispatched by a configurable, typographic name.
59
+
60
+ * **Network re-mesh.** Mixes the current state with a past one (memory `τ`) to stabilize the network, with clear precedence for `α` and conditions based on recent stability and synchrony history.
61
+
62
+ * **Callbacks & observers.** The `Γ(R)` system lets you hook functions before/after each step and after re-mesh, enabling monitoring or external intervention.
63
+
64
+ ---
65
+
66
+ ## Recommendations for Going Deeper
67
+
68
+ * **NetworkX & the Graph API.** Get comfortable with how NetworkX handles attributes and topology; all dynamics operate on `Graph` objects and their properties.
69
+
70
+ * **Extending the ΔNFR field.** Explore `set_delta_nfr_hook` to implement alternative nodal fields and learn how metadata and mixing weights are recorded.
71
+
72
+ * **Designing new glyphs.** Review `operators.py` to add operators or adjust factors in `DEFAULTS['GLYPH_FACTORS']`.
73
+
74
+ * **Custom observers.** Implement your own metrics via `register_callback` or by extending `observers.py` to measure phenomena specific to your study.
75
+
76
+ * **Theoretical reading.** For conceptual background, see the included PDFs (`TNFR.pdf`, *El Pulso que nos Atraviesa*), which deepen the fractal-resonant framework.
77
+
78
+ * **Advanced parameters.** Experiment with adaptive phase coordination, stability criteria, and the glyph grammar to observe their impact on network self-organization.
79
+
80
+ ---
81
+
82
+ **Mastering these pieces will let you extend the simulation, build analysis pipelines and connect the theory with computational applications.**
83
+
84
+ ## Testing
85
+
86
+ Install the dependencies and project in editable mode before running the test suite with `pytest`:
87
+
88
+ ```
89
+ pip install networkx
90
+ pip install -e .
91
+ pytest
92
+ ```
@@ -0,0 +1,14 @@
1
+ tnfr/__init__.py,sha256=LYIpDLjPnC4MxrviM2-Lg8KudbzXM_56aepRg_BEJG4,581
2
+ tnfr/constants.py,sha256=_775sPHussR9vgkWRCLC6dzwgk_1_lLnSlWT8sBWR3U,7677
3
+ tnfr/dynamics.py,sha256=btDyC3jIOmND1c1qqA2RDo0f3NraJKZwggO5BqBAmCA,23967
4
+ tnfr/helpers.py,sha256=3DT7nb2FBrJxSEFcZ_Q9qiR9JGbjCNJOzA49bzN6hbM,6067
5
+ tnfr/main.py,sha256=TEngteuC9MD7Ec9bNGuCC9ym-2ohbh202-HGArCR4tk,1506
6
+ tnfr/observers.py,sha256=PTw3hxk7KD-Yx_CvCIU09icuhyYD6uNU6SvF80UvP-Y,5354
7
+ tnfr/ontosim.py,sha256=9GfEtiLIdJOPJUTufcq_MssAA9J8AfChHU6HKb3DIJY,5628
8
+ tnfr/operators.py,sha256=ONUC0tzB7y0Ad4MppLjFc8bmlTjPol-l_xdEB_84uaw,12293
9
+ tnfr-3.5.0.dist-info/licenses/LICENSE.md,sha256=SRvvhXLrKtseuK6DARbuJffuXOXqAyk3wvF2n0t1SWA,1109
10
+ tnfr-3.5.0.dist-info/METADATA,sha256=N420pHD6T9d8kkDwi202Klyh7mu7oIx3-CmwYZo_CwA,5576
11
+ tnfr-3.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ tnfr-3.5.0.dist-info/entry_points.txt,sha256=6ZYrYA3Z-kKvqiYddIAj9c8ZcoVlK7pj-qCBvlNtuLE,40
13
+ tnfr-3.5.0.dist-info/top_level.txt,sha256=Q2HJnvc5Rt2VHwVvyBTnNPT4SfmJWnCj7XUxxEvQa7c,5
14
+ tnfr-3.5.0.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tnfr = tnfr.main:main
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
 
3
- Copyright (c) [year] [fullname]
3
+ Copyright (c) 2025 TNFR - Teoría de la naturaleza fractral resonante
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,35 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: tnfr
3
- Version: 3.0.3
4
- Summary: TNFR canónica: dinámica glífica modular sobre redes.
5
- Author: Fer
6
- License: MIT
7
- Project-URL: Homepage, https://pypi.org/project/tnfr/
8
- Project-URL: Repository, https://github.com/fermga/Teoria-de-la-naturaleza-fractal-resonante-TNFR-
9
- Keywords: TNFR,fractal resonante,resonancia,glifos,networkx,dinámica,coherencia,EPI,Kuramoto
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3 :: Only
12
- Classifier: Programming Language :: Python :: 3.9
13
- Classifier: Programming Language :: Python :: 3.10
14
- Classifier: Programming Language :: Python :: 3.11
15
- Classifier: Programming Language :: Python :: 3.12
16
- Classifier: Programming Language :: Python :: 3.13
17
- Classifier: License :: OSI Approved :: MIT License
18
- Classifier: Operating System :: OS Independent
19
- Classifier: Intended Audience :: Science/Research
20
- Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
- Classifier: Topic :: Scientific/Engineering :: Information Analysis
22
- Requires-Python: >=3.9
23
- Description-Content-Type: text/markdown
24
- License-File: LICENSE.txt
25
- Requires-Dist: networkx>=2.6
26
- Dynamic: license-file
27
-
28
- # tnfr
29
-
30
-
31
- Paquete **tnfr** (canónica). Orquesta dinámica glífica sobre grafos `networkx`.
32
-
33
-
34
- ```bash
35
- pip install tnfr
@@ -1,13 +0,0 @@
1
- tnfr/__init__.py,sha256=TRSH_QpQJthBeGIUFVmxTBNyth5KqRrRfRBnTeGzA40,2012
2
- tnfr/constants.py,sha256=_775sPHussR9vgkWRCLC6dzwgk_1_lLnSlWT8sBWR3U,7677
3
- tnfr/dynamics.py,sha256=7B38c9SVKLVFBrKHeJ1nXbghoRHfDs8Nl9CqUmCcAyI,23260
4
- tnfr/helpers.py,sha256=tZJsDXc8k9HIfg8BA9cVUEFKBoX1Rfnuhurl2Fvxsy0,6017
5
- tnfr/main.py,sha256=TEngteuC9MD7Ec9bNGuCC9ym-2ohbh202-HGArCR4tk,1506
6
- tnfr/observers.py,sha256=MoC-xLJuMP-UYj8cpIVlgSbXDsE1Uj70Zy51PSH3AJY,5192
7
- tnfr/ontosim.py,sha256=U0IeNVF8rFNhnmWWux91xDc0djTDZQkqRRosP6Z7FmE,5485
8
- tnfr/operators.py,sha256=M6ahJL8IuB2y4qiEalge5EufCz0eEbhw-O4xfh3NpwE,12146
9
- tnfr-3.0.3.dist-info/licenses/LICENSE.txt,sha256=xTjBNhy3N8pomFljrCkD1d34SmAEWv8hyJMMOjNMH0M,1071
10
- tnfr-3.0.3.dist-info/METADATA,sha256=aZbWmWTeChR9K_uZSOizMzwggsi5Kv2pQ6Apu58ut8I,1325
11
- tnfr-3.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
- tnfr-3.0.3.dist-info/top_level.txt,sha256=Q2HJnvc5Rt2VHwVvyBTnNPT4SfmJWnCj7XUxxEvQa7c,5
13
- tnfr-3.0.3.dist-info/RECORD,,
File without changes