tnfr 4.0.0__py3-none-any.whl → 4.1.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,57 +1,59 @@
1
-
2
- from __future__ import annotations
3
- """
4
- TNFR — Teoría de la Naturaleza Fractal Resonante
5
- API pública del paquete.
6
-
7
- Ecuación nodal:
8
- ∂EPI/∂t = νf · ΔNFR(t)
9
- """
10
-
11
- __version__ = "4.0.0"
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
17
- from .gamma import GAMMA_REGISTRY, eval_gamma, kuramoto_R_psi
18
- from .grammar import enforce_canonical_grammar, on_applied_glifo
19
- from .sense import (
20
- GLYPHS_CANONICAL, glyph_angle, glyph_unit,
21
- sigma_vector_node, sigma_vector_global,
22
- push_sigma_snapshot, sigma_series, sigma_rose,
23
- register_sigma_callback,
24
- )
25
- from .metrics import (
26
- register_metrics_callbacks,
27
- Tg_global, Tg_by_node,
28
- latency_series, glifogram_series,
29
- glyph_top, glyph_dwell_stats,
30
- )
31
- from .trace import register_trace
32
- from .program import play, seq, block, target, wait, THOL, TARGET, WAIT
33
- from .cli import main as cli_main
34
- from .scenarios import build_graph
35
- from .presets import get_preset
36
- from .types import NodeState
37
-
38
- __all__ = [
39
- "preparar_red",
40
- "step", "run", "set_delta_nfr_hook",
41
- "attach_standard_observer", "coherencia_global", "orden_kuramoto",
42
- "GAMMA_REGISTRY", "eval_gamma", "kuramoto_R_psi",
43
- "enforce_canonical_grammar", "on_applied_glifo",
44
- "GLYPHS_CANONICAL", "glyph_angle", "glyph_unit",
45
- "sigma_vector_node", "sigma_vector_global",
46
- "push_sigma_snapshot", "sigma_series", "sigma_rose",
47
- "register_sigma_callback",
48
- "register_metrics_callbacks",
49
- "register_trace",
50
- "Tg_global", "Tg_by_node",
51
- "latency_series", "glifogram_series",
52
- "glyph_top", "glyph_dwell_stats",
53
- "play", "seq", "block", "target", "wait", "THOL", "TARGET", "WAIT",
54
- "__version__",
55
- ]
56
-
57
- __all__ += ["cli_main", "build_graph", "get_preset", "NodeState"]
1
+
2
+ from __future__ import annotations
3
+ """
4
+ TNFR — Teoría de la Naturaleza Fractal Resonante
5
+ API pública del paquete.
6
+
7
+ Ecuación nodal:
8
+ ∂EPI/∂t = νf · ΔNFR(t)
9
+ """
10
+
11
+ __version__ = "4.1.0"
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
17
+ from .gamma import GAMMA_REGISTRY, eval_gamma, kuramoto_R_psi
18
+ from .grammar import enforce_canonical_grammar, on_applied_glifo
19
+ from .sense import (
20
+ GLYPHS_CANONICAL, glyph_angle, glyph_unit,
21
+ sigma_vector_node, sigma_vector_global,
22
+ push_sigma_snapshot, sigma_series, sigma_rose,
23
+ register_sigma_callback,
24
+ )
25
+ from .metrics import (
26
+ register_metrics_callbacks,
27
+ Tg_global, Tg_by_node,
28
+ latency_series, glifogram_series,
29
+ glyph_top, glyph_dwell_stats,
30
+ )
31
+ from .trace import register_trace
32
+ from .program import play, seq, block, target, wait, THOL, TARGET, WAIT, ejemplo_canonico_basico
33
+ from .cli import main as cli_main
34
+ from .scenarios import build_graph
35
+ from .presets import get_preset
36
+ from .types import NodeState
37
+
38
+
39
+ __all__ = [
40
+ "preparar_red",
41
+ "step", "run", "set_delta_nfr_hook",
42
+
43
+ "attach_standard_observer", "coherencia_global", "orden_kuramoto",
44
+ "GAMMA_REGISTRY", "eval_gamma", "kuramoto_R_psi",
45
+ "enforce_canonical_grammar", "on_applied_glifo",
46
+ "GLYPHS_CANONICAL", "glyph_angle", "glyph_unit",
47
+ "sigma_vector_node", "sigma_vector_global",
48
+ "push_sigma_snapshot", "sigma_series", "sigma_rose",
49
+ "register_sigma_callback",
50
+ "register_metrics_callbacks",
51
+ "register_trace",
52
+ "Tg_global", "Tg_by_node",
53
+ "latency_series", "glifogram_series",
54
+ "glyph_top", "glyph_dwell_stats",
55
+ "play", "seq", "block", "target", "wait", "THOL", "TARGET", "WAIT",
56
+ "cli_main", "build_graph", "get_preset", "NodeState",
57
+ "ejemplo_canonico_basico",
58
+ "__version__",
59
+ ]
tnfr/cli.py CHANGED
@@ -21,7 +21,8 @@ from .metrics import (
21
21
  )
22
22
  from .trace import register_trace
23
23
  from .program import play, seq, block, wait, target
24
- from .dynamics import step, _update_history
24
+ from .dynamics import step, _update_history, default_glyph_selector, parametric_glyph_selector
25
+ from .gamma import GAMMA_REGISTRY
25
26
  from .scenarios import build_graph
26
27
  from .presets import get_preset
27
28
 
@@ -72,6 +73,9 @@ def _attach_callbacks(G: nx.Graph) -> None:
72
73
  def cmd_run(args: argparse.Namespace) -> int:
73
74
  G = build_graph(n=args.nodes, topology=args.topology, seed=args.seed)
74
75
  _attach_callbacks(G)
76
+ G.graph.setdefault("GRAMMAR_CANON", DEFAULTS["GRAMMAR_CANON"]).update({"enabled": bool(args.grammar_canon)})
77
+ G.graph["glyph_selector"] = default_glyph_selector if args.selector == "basic" else parametric_glyph_selector
78
+ G.graph["GAMMA"] = {"type": args.gamma}
75
79
 
76
80
  if args.preset:
77
81
  program = get_preset(args.preset)
@@ -96,6 +100,9 @@ def cmd_run(args: argparse.Namespace) -> int:
96
100
  def cmd_sequence(args: argparse.Namespace) -> int:
97
101
  G = build_graph(n=args.nodes, topology=args.topology, seed=args.seed)
98
102
  _attach_callbacks(G)
103
+ G.graph.setdefault("GRAMMAR_CANON", DEFAULTS["GRAMMAR_CANON"]).update({"enabled": bool(args.grammar_canon)})
104
+ G.graph["glyph_selector"] = default_glyph_selector if args.selector == "basic" else parametric_glyph_selector
105
+ G.graph["GAMMA"] = {"type": args.gamma}
99
106
 
100
107
  if args.preset:
101
108
  program = get_preset(args.preset)
@@ -114,6 +121,9 @@ def cmd_sequence(args: argparse.Namespace) -> int:
114
121
  def cmd_metrics(args: argparse.Namespace) -> int:
115
122
  G = build_graph(n=args.nodes, topology=args.topology, seed=args.seed)
116
123
  _attach_callbacks(G)
124
+ G.graph.setdefault("GRAMMAR_CANON", DEFAULTS["GRAMMAR_CANON"]).update({"enabled": bool(args.grammar_canon)})
125
+ G.graph["glyph_selector"] = default_glyph_selector if args.selector == "basic" else parametric_glyph_selector
126
+ G.graph["GAMMA"] = {"type": args.gamma}
117
127
  for _ in range(int(args.steps or 200)):
118
128
  step(G)
119
129
 
@@ -147,6 +157,9 @@ def main(argv: Optional[List[str]] = None) -> int:
147
157
  p_run.add_argument("--preset", type=str, default=None)
148
158
  p_run.add_argument("--save-history", dest="save_history", type=str, default=None)
149
159
  p_run.add_argument("--summary", action="store_true")
160
+ p_run.add_argument("--no-canon", dest="grammar_canon", action="store_false", default=True, help="Desactiva gramática canónica")
161
+ p_run.add_argument("--selector", choices=["basic", "param"], default="basic")
162
+ p_run.add_argument("--gamma", choices=list(GAMMA_REGISTRY.keys()), default="none")
150
163
  p_run.set_defaults(func=cmd_run)
151
164
 
152
165
  p_seq = sub.add_parser("sequence", help="Ejecutar una secuencia (preset o YAML/JSON)")
@@ -163,6 +176,9 @@ def main(argv: Optional[List[str]] = None) -> int:
163
176
  p_met.add_argument("--topology", choices=["ring", "complete", "erdos"], default="ring")
164
177
  p_met.add_argument("--steps", type=int, default=300)
165
178
  p_met.add_argument("--seed", type=int, default=1)
179
+ p_met.add_argument("--no-canon", dest="grammar_canon", action="store_false", default=True, help="Desactiva gramática canónica")
180
+ p_met.add_argument("--selector", choices=["basic", "param"], default="basic")
181
+ p_met.add_argument("--gamma", choices=list(GAMMA_REGISTRY.keys()), default="none")
166
182
  p_met.add_argument("--save", type=str, default=None)
167
183
  p_met.set_defaults(func=cmd_metrics)
168
184
 
tnfr/constants.py CHANGED
@@ -72,8 +72,9 @@ DEFAULTS: Dict[str, Any] = {
72
72
  # Criterios de estabilidad (para activar RE’MESH de red)
73
73
  "EPS_DNFR_STABLE": 1e-3,
74
74
  "EPS_DEPI_STABLE": 1e-3,
75
- "FRACTION_STABLE_REMESH": 0.80, # fracción de nodos estables requerida
76
- "REMESH_COOLDOWN_VENTANA": 20, # pasos mínimos entre RE’MESH
75
+ "FRACTION_STABLE_REMESH": 0.80, # fracción de nodos estables requerida
76
+ "REMESH_COOLDOWN_VENTANA": 20, # pasos mínimos entre RE’MESH
77
+ "REMESH_COOLDOWN_TS": 0.0, # cooldown adicional por tiempo simulado
77
78
  # Gating adicional basado en observadores (conmutador + ventana)
78
79
  "REMESH_REQUIRE_STABILITY": False, # si True, exige ventana de estabilidad multi-métrica
79
80
  "REMESH_STABILITY_WINDOW": 25, # tamaño de ventana para evaluar estabilidad
@@ -82,8 +83,9 @@ DEFAULTS: Dict[str, Any] = {
82
83
  "REMESH_LOG_EVENTS": True, # guarda eventos y metadatos del RE’MESH
83
84
 
84
85
  # RE’MESH: memoria τ y mezcla α
85
- "REMESH_TAU": 8, # pasos hacia atrás
86
- "REMESH_ALPHA": 0.5, # mezcla con pasado
86
+ "REMESH_TAU": 8, # pasos hacia atrás
87
+ "REMESH_ALPHA": 0.5, # mezcla con pasado
88
+ "REMESH_ALPHA_HARD": False, # si True ignora GLYPH_FACTORS['REMESH_alpha']
87
89
 
88
90
  # Histéresis glífica
89
91
  "GLYPH_HYSTERESIS_WINDOW": 7,
@@ -92,13 +94,13 @@ DEFAULTS: Dict[str, Any] = {
92
94
  "GLYPH_SELECTOR_MARGIN": 0.05,
93
95
 
94
96
  # Ventana para estimar la carga glífica en history/plots
95
- "GLYPH_LOAD_WINDOW": 50,
97
+ "GLYPH_LOAD_WINDOW": 50,
96
98
 
97
99
  # Tamaño de ventana para coherencia promedio W̄
98
100
  "WBAR_WINDOW": 25,
99
101
 
100
102
  # Factores suaves por glifo (operadores)
101
- "GLYPH_FACTORS": {
103
+ "GLYPH_FACTORS": {
102
104
  "AL_boost": 0.05, # A’L — pequeña emisión
103
105
  "EN_mix": 0.25, # E’N — mezcla con vecindad
104
106
  "IL_dnfr_factor": 0.7, # I’L — reduce ΔNFR
@@ -107,7 +109,7 @@ DEFAULTS: Dict[str, Any] = {
107
109
  "RA_epi_diff": 0.15, # R’A — difusión EPI
108
110
  "SHA_vf_factor": 0.85, # SH’A — baja νf
109
111
  "VAL_scale": 1.15, # VA’L — expande EPI
110
- "NUL_scale": 0.85, # NU’L — contrae EPI
112
+ "NUL_scale": 0.85, # NU’L — contrae EPI
111
113
  "THOL_accel": 0.10, # T’HOL — acelera (seg. deriv.) si hay umbral
112
114
  "ZHIR_theta_shift": 1.57079632679, # Z’HIR — desplazamiento ~π/2
113
115
  "NAV_jitter": 0.05, # NA’V — pequeña inestabilidad creativa
@@ -154,6 +156,7 @@ DEFAULTS: Dict[str, Any] = {
154
156
  "R0": 0.0,
155
157
  },
156
158
  "CALLBACKS_STRICT": False, # si True, un error en callback detiene; si False, se loguea y continúa
159
+ "VALIDATORS_STRICT": False, # si True, alerta si se clampa fuera de rango
157
160
  }
158
161
 
159
162
  # Gramática glífica canónica
tnfr/dynamics.py CHANGED
@@ -187,7 +187,7 @@ def integrar_epi_euler(G, dt: float | None = None) -> None:
187
187
  update_epi_via_nodal_equation(G, dt=dt)
188
188
 
189
189
 
190
- def aplicar_clamps_canonicos(nd: Dict[str, Any], G=None) -> None:
190
+ def aplicar_clamps_canonicos(nd: Dict[str, Any], G=None, node=None) -> None:
191
191
  eps_min = float((G.graph.get("EPI_MIN") if G is not None else DEFAULTS["EPI_MIN"]))
192
192
  eps_max = float((G.graph.get("EPI_MAX") if G is not None else DEFAULTS["EPI_MAX"]))
193
193
  vf_min = float((G.graph.get("VF_MIN") if G is not None else DEFAULTS["VF_MIN"]))
@@ -197,6 +197,14 @@ def aplicar_clamps_canonicos(nd: Dict[str, Any], G=None) -> None:
197
197
  vf = _get_attr(nd, ALIAS_VF, 0.0)
198
198
  th = _get_attr(nd, ALIAS_THETA, 0.0)
199
199
 
200
+ strict = bool((G.graph.get("VALIDATORS_STRICT") if G is not None else DEFAULTS.get("VALIDATORS_STRICT", False)))
201
+ if strict and G is not None:
202
+ hist = G.graph.setdefault("history", {}).setdefault("clamp_alerts", [])
203
+ if epi < eps_min or epi > eps_max:
204
+ hist.append({"node": node, "attr": "EPI", "value": float(epi)})
205
+ if vf < vf_min or vf > vf_max:
206
+ hist.append({"node": node, "attr": "VF", "value": float(vf)})
207
+
200
208
  _set_attr(nd, ALIAS_EPI, clamp(epi, eps_min, eps_max))
201
209
  _set_attr(nd, ALIAS_VF, clamp(vf, vf_min, vf_max))
202
210
  if (G.graph.get("THETA_WRAP") if G is not None else DEFAULTS["THETA_WRAP"]):
@@ -212,6 +220,10 @@ def coordinar_fase_global_vecinal(G, fuerza_global: float | None = None, fuerza_
212
220
  """
213
221
  g = G.graph
214
222
  defaults = DEFAULTS
223
+ hist = g.setdefault("history", {})
224
+ hist_state = hist.setdefault("phase_state", [])
225
+ hist_R = hist.setdefault("phase_R", [])
226
+ hist_disr = hist.setdefault("phase_disr", [])
215
227
  # 0) Si hay fuerzas explícitas, usar y salir del modo adaptativo
216
228
  if (fuerza_global is not None) or (fuerza_vecinal is not None):
217
229
  kG = float(
@@ -273,20 +285,15 @@ def coordinar_fase_global_vecinal(G, fuerza_global: float | None = None, fuerza_
273
285
  kL = _step(kL, kL_t, kL_min, kL_max)
274
286
 
275
287
  # 5) Persistir en G.graph y log de serie
276
- g["PHASE_K_GLOBAL"] = kG
277
- g["PHASE_K_LOCAL"] = kL
278
- hist = g.setdefault("history", {})
279
- hist_kG = hist.setdefault("phase_kG", [])
280
- hist_kL = hist.setdefault("phase_kL", [])
281
- hist_state = hist.setdefault("phase_state", [])
282
- hist_R = hist.setdefault("phase_R", [])
283
- hist_disr = hist.setdefault("phase_disr", [])
284
- hist_kG.append(float(kG))
285
- hist_kL.append(float(kL))
286
288
  hist_state.append(state)
287
289
  hist_R.append(float(R))
288
290
  hist_disr.append(float(disr))
289
291
 
292
+ g["PHASE_K_GLOBAL"] = kG
293
+ g["PHASE_K_LOCAL"] = kL
294
+ hist.setdefault("phase_kG", []).append(float(kG))
295
+ hist.setdefault("phase_kL", []).append(float(kL))
296
+
290
297
  # 6) Fase GLOBAL (centroide) para empuje
291
298
  X = list(math.cos(_get_attr(G.nodes[n], ALIAS_THETA, 0.0)) for n in G.nodes())
292
299
  Y = list(math.sin(_get_attr(G.nodes[n], ALIAS_THETA, 0.0)) for n in G.nodes())
@@ -345,6 +352,21 @@ def _norms_para_selector(G) -> dict:
345
352
  G.graph["_sel_norms"] = norms
346
353
  return norms
347
354
 
355
+
356
+ def _soft_grammar_prefilter(G, n, cand, dnfr, accel):
357
+ """Gramática suave: evita repeticiones antes de la canónica."""
358
+ gram = G.graph.get("GRAMMAR", DEFAULTS.get("GRAMMAR", {}))
359
+ gwin = int(gram.get("window", 3))
360
+ avoid = set(gram.get("avoid_repeats", []))
361
+ force_dn = float(gram.get("force_dnfr", 0.60))
362
+ force_ac = float(gram.get("force_accel", 0.60))
363
+ fallbacks = gram.get("fallbacks", {})
364
+ nd = G.nodes[n]
365
+ if cand in avoid and reciente_glifo(nd, cand, gwin):
366
+ if not (dnfr >= force_dn or accel >= force_ac):
367
+ cand = fallbacks.get(cand, cand)
368
+ return cand
369
+
348
370
  def parametric_glyph_selector(G, n) -> str:
349
371
  """Multiobjetivo: combina Si, |ΔNFR|_norm y |accel|_norm + histéresis.
350
372
  Reglas base:
@@ -412,22 +434,9 @@ def parametric_glyph_selector(G, n) -> str:
412
434
  elif score <= 0.33 and cand in ("NA’V","R’A","I’L"):
413
435
  cand = "O’Z" if dnfr >= dnfr_lo else "Z’HIR"
414
436
  except NameError:
415
- # por si 'score' no se definió (robustez), no forzamos nada
416
437
  pass
417
438
 
418
- # --- Gramática glífica suave: evita repeticiones cercanas salvo que el campo lo pida ---
419
- gram = G.graph.get("GRAMMAR", DEFAULTS.get("GRAMMAR", {}))
420
- gwin = int(gram.get("window", 3))
421
- avoid = set(gram.get("avoid_repeats", []))
422
- force_dn = float(gram.get("force_dnfr", 0.60))
423
- force_ac = float(gram.get("force_accel", 0.60))
424
- fallbacks = gram.get("fallbacks", {})
425
-
426
- if cand in avoid and reciente_glifo(nd, cand, gwin):
427
- # Solo permitimos repetir si el campo "insiste": dnfr o accel altos (ya normalizados)
428
- if not (dnfr >= force_dn or accel >= force_ac):
429
- cand = fallbacks.get(cand, "R’A")
430
-
439
+ cand = _soft_grammar_prefilter(G, n, cand, dnfr, accel)
431
440
  return cand
432
441
 
433
442
  # -------------------------
@@ -470,7 +479,7 @@ def step(G, *, dt: float | None = None, use_Si: bool = True, apply_glyphs: bool
470
479
 
471
480
  # 5) Clamps
472
481
  for n in G.nodes():
473
- aplicar_clamps_canonicos(G.nodes[n], G)
482
+ aplicar_clamps_canonicos(G.nodes[n], G, n)
474
483
 
475
484
  # 6) Coordinación de fase
476
485
  coordinar_fase_global_vecinal(G, None, None)
tnfr/gamma.py CHANGED
@@ -3,10 +3,19 @@
3
3
  Γi(R): acoplamientos de red para la ecuación nodal extendida
4
4
  ∂EPI/∂t = νf · ΔNFR(t) + Γi(R)
5
5
 
6
+ `Γ` suma un término de acoplamiento dependiente del orden global de fase
7
+ `R`. La especificación se toma de ``G.graph['GAMMA']`` (ver
8
+ ``DEFAULTS['GAMMA']``) con parámetros como:
9
+
10
+ * ``type`` – modo de acoplamiento (``none``, ``kuramoto_linear``,
11
+ ``kuramoto_bandpass``)
12
+ * ``beta`` – ganancia del acoplamiento
13
+ * ``R0`` – umbral de activación (solo lineal)
14
+
6
15
  Provee:
7
16
  - kuramoto_R_psi(G): (R, ψ) orden de Kuramoto en la red
8
17
  - GAMMA_REGISTRY: registro de acoplamientos canónicos
9
- - eval_gamma(G, node, t): evalúa Γ para cada nodo según G.graph['GAMMA']
18
+ - eval_gamma(G, node, t): evalúa Γ para cada nodo según la config
10
19
  """
11
20
  from __future__ import annotations
12
21
  from typing import Dict, Any, Tuple
tnfr/grammar.py CHANGED
@@ -142,6 +142,12 @@ def on_applied_glifo(G, n, applied: str) -> None:
142
142
  # -------------------------
143
143
 
144
144
  def select_and_apply_with_grammar(G, n, selector, window: int) -> None:
145
+ """Aplica gramática canónica sobre la propuesta del selector.
146
+
147
+ El selector puede incluir una gramática **suave** (pre–filtro) como
148
+ `parametric_glyph_selector`; la presente función garantiza que la
149
+ gramática canónica tenga precedencia final.
150
+ """
145
151
  from .operators import aplicar_glifo
146
152
  cand = selector(G, n)
147
153
  cand = enforce_canonical_grammar(G, n, cand)
tnfr/helpers.py CHANGED
@@ -186,7 +186,10 @@ def invoke_callbacks(G, event: str, ctx: dict | None = None):
186
186
  def compute_Si(G, *, inplace: bool = True) -> Dict[Any, float]:
187
187
  """Calcula Si por nodo y lo escribe en G.nodes[n]["Si"].
188
188
 
189
- Si = α·νf_norm + β·(1 - disp_fase_local) + γ·(1 - |ΔNFR|/max|ΔNFR|)
189
+ Fórmula:
190
+ Si = α·νf_norm + β·(1 - disp_fase_local) + γ·(1 - |ΔNFR|/max|ΔNFR|)
191
+ También guarda en ``G.graph`` los pesos normalizados y la
192
+ sensibilidad parcial (∂Si/∂componente).
190
193
  """
191
194
  alpha = float(G.graph.get("SI_WEIGHTS", DEFAULTS["SI_WEIGHTS"]).get("alpha", 0.34))
192
195
  beta = float(G.graph.get("SI_WEIGHTS", DEFAULTS["SI_WEIGHTS"]).get("beta", 0.33))
@@ -196,6 +199,8 @@ def compute_Si(G, *, inplace: bool = True) -> Dict[Any, float]:
196
199
  alpha = beta = gamma = 1/3
197
200
  else:
198
201
  alpha, beta, gamma = alpha/s, beta/s, gamma/s
202
+ G.graph["_Si_weights"] = {"alpha": alpha, "beta": beta, "gamma": gamma}
203
+ G.graph["_Si_sensitivity"] = {"dSi_dvf_norm": alpha, "dSi_ddisp_fase": -beta, "dSi_ddnfr_norm": -gamma}
199
204
 
200
205
  # Normalización de νf en red
201
206
  vfs = [abs(_get_attr(G.nodes[n], ALIAS_VF, 0.0)) for n in G.nodes()]
tnfr/operators.py CHANGED
@@ -21,6 +21,15 @@ Nota sobre α (alpha) de RE’MESH: se toma por prioridad de
21
21
  3) DEFAULTS["REMESH_ALPHA"]
22
22
  """
23
23
 
24
+
25
+ def _node_offset(G, n) -> int:
26
+ """Deterministic node index used for jitter seeds."""
27
+ mapping = G.graph.get("_node_offset_map")
28
+ if mapping is None or len(mapping) != G.number_of_nodes():
29
+ mapping = {node: idx for idx, node in enumerate(sorted(G.nodes(), key=lambda x: str(x)))}
30
+ G.graph["_node_offset_map"] = mapping
31
+ return int(mapping.get(n, 0))
32
+
24
33
  # -------------------------
25
34
  # Glifos (operadores locales)
26
35
  # -------------------------
@@ -55,7 +64,7 @@ def op_OZ(G, n): # O’Z — Disonancia (aumenta ΔNFR o añade ruido)
55
64
  if bool(G.graph.get("OZ_NOISE_MODE", False)):
56
65
  base_seed = int(G.graph.get("RANDOM_SEED", 0))
57
66
  step_idx = len(G.graph.get("history", {}).get("C_steps", []))
58
- rnd = random.Random(base_seed + step_idx*1000003 + hash(("OZ", n)) % 1009)
67
+ rnd = random.Random(base_seed + step_idx*1000003 + _node_offset(G, n) % 1009)
59
68
  sigma = float(G.graph.get("OZ_SIGMA", 0.1))
60
69
  noise = sigma * (2.0 * rnd.random() - 1.0)
61
70
  _set_attr(nd, ALIAS_DNFR, dnfr + noise)
@@ -124,7 +133,7 @@ def op_NAV(G, n): # NA’V — Transición (jitter suave de ΔNFR)
124
133
  base_seed = int(G.graph.get("RANDOM_SEED", 0))
125
134
  # opcional: pequeño offset para evitar misma secuencia en todos los nodos/pasos
126
135
  step_idx = len(G.graph.get("history", {}).get("C_steps", []))
127
- rnd = random.Random(base_seed + step_idx*1000003 + hash(n) % 1009)
136
+ rnd = random.Random(base_seed + step_idx*1000003 + _node_offset(G, n) % 1009)
128
137
  jitter = j * (2.0 * rnd.random() - 1.0)
129
138
  else:
130
139
  # comportamiento determinista (compatibilidad previa)
@@ -181,13 +190,15 @@ def aplicar_glifo(G, n, glifo: str, *, window: Optional[int] = None) -> None:
181
190
  # -------------------------
182
191
 
183
192
  def _remesh_alpha_info(G):
184
- """Devuelve (alpha, source) con precedencia explícita:
185
- 1) GLYPH_FACTORS["REMESH_alpha"] 2) G.graph["REMESH_ALPHA"] 3) DEFAULTS["REMESH_ALPHA"]"""
193
+ """Devuelve (alpha, source) con precedencia explícita."""
194
+ hard = bool(G.graph.get("REMESH_ALPHA_HARD", DEFAULTS.get("REMESH_ALPHA_HARD", False)))
186
195
  gf = G.graph.get("GLYPH_FACTORS", DEFAULTS["GLYPH_FACTORS"])
187
- if "REMESH_alpha" in gf:
196
+ if not hard and "REMESH_alpha" in gf:
188
197
  return float(gf["REMESH_alpha"]), "GLYPH_FACTORS"
189
198
  if "REMESH_ALPHA" in G.graph:
190
199
  return float(G.graph["REMESH_ALPHA"]), "G.graph"
200
+ if "REMESH_alpha" in gf:
201
+ return float(gf["REMESH_alpha"]), "GLYPH_FACTORS"
191
202
  return float(DEFAULTS["REMESH_ALPHA"]), "DEFAULTS"
192
203
 
193
204
 
@@ -308,6 +319,12 @@ def aplicar_remesh_si_estabilizacion_global(G, pasos_estables_consecutivos: Opti
308
319
  cooldown = int(G.graph.get("REMESH_COOLDOWN_VENTANA", DEFAULTS["REMESH_COOLDOWN_VENTANA"]))
309
320
  if step_idx - last < cooldown:
310
321
  return
322
+ t_now = float(G.graph.get("_t", 0.0))
323
+ last_ts = float(G.graph.get("_last_remesh_ts", -1e12))
324
+ cooldown_ts = float(G.graph.get("REMESH_COOLDOWN_TS", DEFAULTS.get("REMESH_COOLDOWN_TS", 0.0)))
325
+ if cooldown_ts > 0 and (t_now - last_ts) < cooldown_ts:
326
+ return
311
327
  # 4) Aplicar y registrar
312
328
  aplicar_remesh_red(G)
313
329
  G.graph["_last_remesh_step"] = step_idx
330
+ G.graph["_last_remesh_ts"] = t_now
tnfr/presets.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from __future__ import annotations
2
- from .program import seq, block, wait
2
+ from .program import seq, block, wait, ejemplo_canonico_basico
3
3
 
4
4
 
5
5
  _PRESETS = {
@@ -15,6 +15,7 @@ _PRESETS = {
15
15
  "R’A",
16
16
  "SH’A",
17
17
  ),
18
+ "ejemplo_canonico": ejemplo_canonico_basico(),
18
19
  }
19
20
 
20
21
 
tnfr/program.py CHANGED
@@ -166,3 +166,11 @@ def target(nodes: Optional[Iterable[Node]] = None) -> TARGET:
166
166
 
167
167
  def wait(steps: int = 1) -> WAIT:
168
168
  return WAIT(steps=max(1, int(steps)))
169
+
170
+
171
+ def ejemplo_canonico_basico() -> List[Token]:
172
+ """Secuencia canónica de referencia.
173
+
174
+ SH’A → A’L → R’A → Z’HIR → NU’L → T’HOL
175
+ """
176
+ return seq("SH’A", "A’L", "R’A", "Z’HIR", "NU’L", "T’HOL")
tnfr/trace.py CHANGED
@@ -22,7 +22,7 @@ except Exception: # pragma: no cover
22
22
  # -------------------------
23
23
  DEFAULTS.setdefault("TRACE", {
24
24
  "enabled": True,
25
- "capture": ["gamma", "grammar", "selector", "dnfr_mix", "callbacks", "thol_state", "sigma", "kuramoto", "glifo_counts"],
25
+ "capture": ["gamma", "grammar", "selector", "dnfr_weights", "si_weights", "callbacks", "thol_state", "sigma", "kuramoto", "glifo_counts"],
26
26
  "history_key": "trace_meta",
27
27
  })
28
28
 
@@ -70,10 +70,14 @@ def _trace_before(G, *args, **kwargs):
70
70
  sel = G.graph.get("glyph_selector")
71
71
  meta["selector"] = getattr(sel, "__name__", str(sel)) if sel else None
72
72
 
73
- if "dnfr_mix" in capture:
74
- # tratar de capturar varias convenciones posibles
75
- mix = G.graph.get("DNFR_MIX") or G.graph.get("DELTA_NFR_MIX") or G.graph.get("NFR_MIX")
76
- meta["dnfr_mix"] = mix if isinstance(mix, dict) else {"value": mix}
73
+ if "dnfr_weights" in capture:
74
+ mix = G.graph.get("DNFR_WEIGHTS")
75
+ if isinstance(mix, dict):
76
+ meta["dnfr_weights"] = dict(mix)
77
+
78
+ if "si_weights" in capture:
79
+ meta["si_weights"] = dict(G.graph.get("_Si_weights", {}))
80
+ meta["si_sensitivity"] = dict(G.graph.get("_Si_sensitivity", {}))
77
81
 
78
82
  if "callbacks" in capture:
79
83
  # si el motor guarda los callbacks, exponer nombres por fase
@@ -134,7 +138,8 @@ def register_trace(G) -> None:
134
138
  - gamma: especificación activa de Γi(R)
135
139
  - grammar: configuración de gramática canónica
136
140
  - selector: nombre del selector glífico
137
- - dnfr_mix: mezcla (si el motor la expone en G.graph)
141
+ - dnfr_weights: mezcla ΔNFR declarada en el motor
142
+ - si_weights: pesos α/β/γ y sensibilidad de Si
138
143
  - callbacks: callbacks registrados por fase (si están en G.graph['_callbacks'])
139
144
  - thol_open_nodes: cuántos nodos tienen bloque T’HOL abierto
140
145
  - kuramoto: (R, ψ) de la red
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tnfr
3
- Version: 4.0.0
3
+ Version: 4.1.0
4
4
  Summary: TNFR canónica: dinámica glífica modular sobre redes.
5
5
  Author: fmg
6
6
  License: MIT
@@ -59,6 +59,8 @@ Dynamic: license-file
59
59
 
60
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
61
 
62
+ * **Γ(R) coupling.** Optional network term added to the nodal equation, parameterized by global phase order `R` with gain `β` and threshold `R0` (see `DEFAULTS["GAMMA"]`).
63
+
62
64
  * **Callbacks & observers.** The `Γ(R)` system lets you hook functions before/after each step and after re-mesh, enabling monitoring or external intervention.
63
65
 
64
66
  ---
@@ -0,0 +1,24 @@
1
+ tnfr/__init__.py,sha256=93fkcY9lteZyKkZuWJAB-hvDEE2q0ZYgk16qCBXe50A,1988
2
+ tnfr/cli.py,sha256=PVU95Iik_3bn-4UrLr8H7HHGme1X0qYnTuVv_AOp9FA,7484
3
+ tnfr/constants.py,sha256=EPFW7howB2IsyeGRc8hyzO3s6sgRkM78lWB7S3BJXq4,9147
4
+ tnfr/dynamics.py,sha256=SIf6cTj6_Wd6DRKJRYjE-iBuaxfDRpHu-j14wzlB-J0,25598
5
+ tnfr/gamma.py,sha256=Ccsr2yKA7f8VlVqKgckQpGAEISlBw8i3FBeNPxfS-AQ,3048
6
+ tnfr/grammar.py,sha256=vz5F0P3IfvA6HassRcoD327hBP5vCUw-xPSTsPmqwhQ,5363
7
+ tnfr/helpers.py,sha256=CPEBkmFY6S4DKBf91EqW5vW_jTokOzg3HojCx28KxTU,7327
8
+ tnfr/main.py,sha256=XqjI1YEdF-OqRzTMa5dYIxCig4qyAR-l1FPcyxpC8WY,1926
9
+ tnfr/metrics.py,sha256=XusywHo1-GgLqYy1Jz43Xc4zRsggrn_DwoaFWfAy82M,6640
10
+ tnfr/observers.py,sha256=PTw3hxk7KD-Yx_CvCIU09icuhyYD6uNU6SvF80UvP-Y,5354
11
+ tnfr/ontosim.py,sha256=9GfEtiLIdJOPJUTufcq_MssAA9J8AfChHU6HKb3DIJY,5628
12
+ tnfr/operators.py,sha256=QYsGf9R3lr8WdR0kanLqGYqZ-Lw-RWzjqKKTczgtyEw,13741
13
+ tnfr/presets.py,sha256=rN6kDnI59DfYCL0YuYQr0yEu8jZaCFezGBnIWn6HFn8,737
14
+ tnfr/program.py,sha256=eim7D8zsbbkGDWbODag-0VKG44jEYioX4Sl6KRwgVtw,6038
15
+ tnfr/scenarios.py,sha256=FgsXzA6ENRkalENnG5ZuBNFguEGaKZTM6y0quW5fJI4,808
16
+ tnfr/sense.py,sha256=1yVlphcs4o_uOQ51MDqW9dphydMiDNKv1Z1EdgbJgpU,6778
17
+ tnfr/trace.py,sha256=bY0GU5I5yvHKSDOUQ0m5ICaXScYHzCM5xOWqTO68VO0,5026
18
+ tnfr/types.py,sha256=xyFHp0PptEqPNUekAFH6DcAnyMx4bCQutMyFUXMd2sA,457
19
+ tnfr-4.1.0.dist-info/licenses/LICENSE.md,sha256=SRvvhXLrKtseuK6DARbuJffuXOXqAyk3wvF2n0t1SWA,1109
20
+ tnfr-4.1.0.dist-info/METADATA,sha256=cuR46do-ZJA5_OiZy4MNCwwbsEAi6DYD0HcRVTTYIz8,6075
21
+ tnfr-4.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
22
+ tnfr-4.1.0.dist-info/entry_points.txt,sha256=j4-QRHqeT2WnchHe_mvK7npGTLjlyfLpvRONFe9Z4MU,39
23
+ tnfr-4.1.0.dist-info/top_level.txt,sha256=Q2HJnvc5Rt2VHwVvyBTnNPT4SfmJWnCj7XUxxEvQa7c,5
24
+ tnfr-4.1.0.dist-info/RECORD,,
@@ -1,24 +0,0 @@
1
- tnfr/__init__.py,sha256=nN8P2Xm26kvJvqDhmRITnimew3SgBgw4fSUHPOAiYts,1880
2
- tnfr/cli.py,sha256=fdzmIOVerg6J1j6cBuu7m2-nKFG_WqiNu-e2Wg8eECQ,5980
3
- tnfr/constants.py,sha256=PUhps2gBXeh_rJIrCF9HpWRdi02sfRXvVp066AOgkyQ,8912
4
- tnfr/dynamics.py,sha256=FeFv7h4m9e8qhbU9tIRss3K6P2uz4Hfmtbldf2AhfyQ,25286
5
- tnfr/gamma.py,sha256=1hGqYTBYIYirgmSSSbvSuFWPVy_KGzPNH3eD7wmRUu0,2689
6
- tnfr/grammar.py,sha256=X50-BzTzaYkFIi-ljcgB_XXC6c2srvqzDqOKa91nsuA,5090
7
- tnfr/helpers.py,sha256=foJgwyC__6lQeOpPY02slFRggY3tCy1CxeFlEQcI-gY,7014
8
- tnfr/main.py,sha256=XqjI1YEdF-OqRzTMa5dYIxCig4qyAR-l1FPcyxpC8WY,1926
9
- tnfr/metrics.py,sha256=XusywHo1-GgLqYy1Jz43Xc4zRsggrn_DwoaFWfAy82M,6640
10
- tnfr/observers.py,sha256=PTw3hxk7KD-Yx_CvCIU09icuhyYD6uNU6SvF80UvP-Y,5354
11
- tnfr/ontosim.py,sha256=9GfEtiLIdJOPJUTufcq_MssAA9J8AfChHU6HKb3DIJY,5628
12
- tnfr/operators.py,sha256=Q2QvtK2oQUuMsq_CPdfaK9eCrhniI1NaWEfwQH08zSk,12936
13
- tnfr/presets.py,sha256=vaqgqz1pVU93hEOITQn-2CUGfMbN2n47A9GBFmjWRpo,661
14
- tnfr/program.py,sha256=qGqj74qBqXILm-Yrw73__0uIlx2gJiSF9dDHxOGTbCs,5798
15
- tnfr/scenarios.py,sha256=FgsXzA6ENRkalENnG5ZuBNFguEGaKZTM6y0quW5fJI4,808
16
- tnfr/sense.py,sha256=1yVlphcs4o_uOQ51MDqW9dphydMiDNKv1Z1EdgbJgpU,6778
17
- tnfr/trace.py,sha256=YtqSKJM2tCH5eLa22nObPLnYgsn4Bm6inipY0n_mwXY,4887
18
- tnfr/types.py,sha256=xyFHp0PptEqPNUekAFH6DcAnyMx4bCQutMyFUXMd2sA,457
19
- tnfr-4.0.0.dist-info/licenses/LICENSE.md,sha256=SRvvhXLrKtseuK6DARbuJffuXOXqAyk3wvF2n0t1SWA,1109
20
- tnfr-4.0.0.dist-info/METADATA,sha256=xzcC2N34E82NmkgYly3jpfgzLVgI9fQLNnMiXLWk2RQ,5898
21
- tnfr-4.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
22
- tnfr-4.0.0.dist-info/entry_points.txt,sha256=j4-QRHqeT2WnchHe_mvK7npGTLjlyfLpvRONFe9Z4MU,39
23
- tnfr-4.0.0.dist-info/top_level.txt,sha256=Q2HJnvc5Rt2VHwVvyBTnNPT4SfmJWnCj7XUxxEvQa7c,5
24
- tnfr-4.0.0.dist-info/RECORD,,
File without changes