tnfr 4.1.0__py3-none-any.whl → 4.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
@@ -8,10 +8,10 @@ Ecuación nodal:
8
8
  ∂EPI/∂t = νf · ΔNFR(t)
9
9
  """
10
10
 
11
- __version__ = "4.1.0"
11
+ __version__ = "4.5.0"
12
12
 
13
13
  # Re-exports de la API pública
14
- from .dynamics import step, run, set_delta_nfr_hook
14
+ from .dynamics import step, run, set_delta_nfr_hook, validate_canon
15
15
  from .ontosim import preparar_red
16
16
  from .observers import attach_standard_observer, coherencia_global, orden_kuramoto
17
17
  from .gamma import GAMMA_REGISTRY, eval_gamma, kuramoto_R_psi
@@ -26,19 +26,40 @@ from .metrics import (
26
26
  register_metrics_callbacks,
27
27
  Tg_global, Tg_by_node,
28
28
  latency_series, glifogram_series,
29
- glyph_top, glyph_dwell_stats,
29
+ glyph_top, glyph_dwell_stats, export_history,
30
30
  )
31
+ from .operators import aplicar_remesh_red_topologico
31
32
  from .trace import register_trace
32
33
  from .program import play, seq, block, target, wait, THOL, TARGET, WAIT, ejemplo_canonico_basico
33
34
  from .cli import main as cli_main
34
35
  from .scenarios import build_graph
35
36
  from .presets import get_preset
36
37
  from .types import NodeState
38
+ from .structural import (
39
+ create_nfr,
40
+ Operador,
41
+ Emision,
42
+ Recepcion,
43
+ Coherencia,
44
+ Disonancia,
45
+ Acoplamiento,
46
+ Resonancia,
47
+ Silencio,
48
+ Expansion,
49
+ Contraccion,
50
+ Autoorganizacion,
51
+ Mutacion,
52
+ Transicion,
53
+ Recursividad,
54
+ OPERADORES,
55
+ validate_sequence,
56
+ run_sequence,
57
+ )
37
58
 
38
59
 
39
60
  __all__ = [
40
61
  "preparar_red",
41
- "step", "run", "set_delta_nfr_hook",
62
+ "step", "run", "set_delta_nfr_hook", "validate_canon",
42
63
 
43
64
  "attach_standard_observer", "coherencia_global", "orden_kuramoto",
44
65
  "GAMMA_REGISTRY", "eval_gamma", "kuramoto_R_psi",
@@ -52,8 +73,17 @@ __all__ = [
52
73
  "Tg_global", "Tg_by_node",
53
74
  "latency_series", "glifogram_series",
54
75
  "glyph_top", "glyph_dwell_stats",
76
+ "export_history",
77
+ "aplicar_remesh_red_topologico",
55
78
  "play", "seq", "block", "target", "wait", "THOL", "TARGET", "WAIT",
56
79
  "cli_main", "build_graph", "get_preset", "NodeState",
57
80
  "ejemplo_canonico_basico",
81
+ "create_nfr",
82
+ "Operador", "Emision", "Recepcion", "Coherencia", "Disonancia",
83
+ "Acoplamiento", "Resonancia", "Silencio", "Expansion", "Contraccion",
84
+ "Autoorganizacion", "Mutacion", "Transicion", "Recursividad",
85
+ "OPERADORES", "validate_sequence", "run_sequence",
58
86
  "__version__",
59
87
  ]
88
+
89
+
tnfr/cli.py CHANGED
@@ -18,13 +18,15 @@ from .metrics import (
18
18
  latency_series,
19
19
  glifogram_series,
20
20
  glyph_top,
21
+ export_history,
21
22
  )
22
23
  from .trace import register_trace
23
24
  from .program import play, seq, block, wait, target
24
- from .dynamics import step, _update_history, default_glyph_selector, parametric_glyph_selector
25
+ from .dynamics import step, _update_history, default_glyph_selector, parametric_glyph_selector, validate_canon
25
26
  from .gamma import GAMMA_REGISTRY
26
27
  from .scenarios import build_graph
27
28
  from .presets import get_preset
29
+ from .config import apply_config
28
30
 
29
31
 
30
32
  def _save_json(path: str, data: Any) -> None:
@@ -32,6 +34,24 @@ def _save_json(path: str, data: Any) -> None:
32
34
  json.dump(data, f, ensure_ascii=False, indent=2)
33
35
 
34
36
 
37
+ def _str2bool(s: str) -> bool:
38
+ s = s.lower()
39
+ if s in {"true", "1", "yes", "y"}:
40
+ return True
41
+ if s in {"false", "0", "no", "n"}:
42
+ return False
43
+ raise argparse.ArgumentTypeError("expected true/false")
44
+
45
+
46
+ def _args_to_dict(args: argparse.Namespace, prefix: str) -> Dict[str, Any]:
47
+ out: Dict[str, Any] = {}
48
+ pref = prefix.replace(".", "_")
49
+ for k, v in vars(args).items():
50
+ if k.startswith(pref) and v is not None:
51
+ out[k[len(pref):]] = v
52
+ return out
53
+
54
+
35
55
  def _load_sequence(path: str) -> List[Any]:
36
56
  with open(path, "r", encoding="utf-8") as f:
37
57
  text = f.read()
@@ -72,10 +92,29 @@ def _attach_callbacks(G: nx.Graph) -> None:
72
92
 
73
93
  def cmd_run(args: argparse.Namespace) -> int:
74
94
  G = build_graph(n=args.nodes, topology=args.topology, seed=args.seed)
95
+ if getattr(args, "config", None):
96
+ apply_config(G, args.config)
75
97
  _attach_callbacks(G)
76
- G.graph.setdefault("GRAMMAR_CANON", DEFAULTS["GRAMMAR_CANON"]).update({"enabled": bool(args.grammar_canon)})
98
+ validate_canon(G)
99
+ if args.dt is not None:
100
+ G.graph["DT"] = float(args.dt)
101
+ if args.integrator is not None:
102
+ G.graph["INTEGRATOR_METHOD"] = str(args.integrator)
103
+ if getattr(args, "remesh_mode", None):
104
+ G.graph["REMESH_MODE"] = str(args.remesh_mode)
105
+ gcanon = dict(DEFAULTS["GRAMMAR_CANON"])
106
+ gcanon.update(_args_to_dict(args, prefix="grammar."))
107
+ if hasattr(args, "grammar_canon") and args.grammar_canon is not None:
108
+ gcanon["enabled"] = bool(args.grammar_canon)
109
+ G.graph.setdefault("GRAMMAR_CANON", {}).update(gcanon)
110
+ if args.glyph_hysteresis_window is not None:
111
+ G.graph["GLYPH_HYSTERESIS_WINDOW"] = int(args.glyph_hysteresis_window)
77
112
  G.graph["glyph_selector"] = default_glyph_selector if args.selector == "basic" else parametric_glyph_selector
78
- G.graph["GAMMA"] = {"type": args.gamma}
113
+ G.graph["GAMMA"] = {
114
+ "type": args.gamma_type,
115
+ "beta": args.gamma_beta,
116
+ "R0": args.gamma_R0,
117
+ }
79
118
 
80
119
  if args.preset:
81
120
  program = get_preset(args.preset)
@@ -87,11 +126,28 @@ def cmd_run(args: argparse.Namespace) -> int:
87
126
 
88
127
  if args.save_history:
89
128
  _save_json(args.save_history, G.graph.get("history", {}))
129
+ if args.export_history_base:
130
+ export_history(G, args.export_history_base, fmt=args.export_format)
131
+
132
+ # Resúmenes rápidos (si están activados)
133
+ if G.graph.get("COHERENCE", DEFAULTS["COHERENCE"]).get("enabled", True):
134
+ Wstats = G.graph.get("history", {}).get(
135
+ G.graph.get("COHERENCE", DEFAULTS["COHERENCE"]).get("stats_history_key", "W_stats"), []
136
+ )
137
+ if Wstats:
138
+ print("[COHERENCE] último paso:", Wstats[-1])
139
+ if G.graph.get("DIAGNOSIS", DEFAULTS["DIAGNOSIS"]).get("enabled", True):
140
+ last_diag = G.graph.get("history", {}).get(
141
+ G.graph.get("DIAGNOSIS", DEFAULTS["DIAGNOSIS"]).get("history_key", "nodal_diag"), []
142
+ )
143
+ if last_diag:
144
+ sample = list(last_diag[-1].values())[:3]
145
+ print("[DIAGNOSIS] ejemplo:", sample)
90
146
 
91
147
  if args.summary:
92
148
  tg = Tg_global(G, normalize=True)
93
149
  lat = latency_series(G)
94
- print("Top glifos por Tg:", glyph_top(G, k=5))
150
+ print("Top operadores por Tg:", glyph_top(G, k=5))
95
151
  if lat["value"]:
96
152
  print("Latencia media:", sum(lat["value"]) / max(1, len(lat["value"])) )
97
153
  return 0
@@ -99,10 +155,29 @@ def cmd_run(args: argparse.Namespace) -> int:
99
155
 
100
156
  def cmd_sequence(args: argparse.Namespace) -> int:
101
157
  G = build_graph(n=args.nodes, topology=args.topology, seed=args.seed)
158
+ if getattr(args, "config", None):
159
+ apply_config(G, args.config)
102
160
  _attach_callbacks(G)
103
- G.graph.setdefault("GRAMMAR_CANON", DEFAULTS["GRAMMAR_CANON"]).update({"enabled": bool(args.grammar_canon)})
161
+ validate_canon(G)
162
+ if args.dt is not None:
163
+ G.graph["DT"] = float(args.dt)
164
+ if args.integrator is not None:
165
+ G.graph["INTEGRATOR_METHOD"] = str(args.integrator)
166
+ if getattr(args, "remesh_mode", None):
167
+ G.graph["REMESH_MODE"] = str(args.remesh_mode)
168
+ gcanon = dict(DEFAULTS["GRAMMAR_CANON"])
169
+ gcanon.update(_args_to_dict(args, prefix="grammar."))
170
+ if hasattr(args, "grammar_canon") and args.grammar_canon is not None:
171
+ gcanon["enabled"] = bool(args.grammar_canon)
172
+ G.graph.setdefault("GRAMMAR_CANON", {}).update(gcanon)
173
+ if args.glyph_hysteresis_window is not None:
174
+ G.graph["GLYPH_HYSTERESIS_WINDOW"] = int(args.glyph_hysteresis_window)
104
175
  G.graph["glyph_selector"] = default_glyph_selector if args.selector == "basic" else parametric_glyph_selector
105
- G.graph["GAMMA"] = {"type": args.gamma}
176
+ G.graph["GAMMA"] = {
177
+ "type": args.gamma_type,
178
+ "beta": args.gamma_beta,
179
+ "R0": args.gamma_R0,
180
+ }
106
181
 
107
182
  if args.preset:
108
183
  program = get_preset(args.preset)
@@ -115,15 +190,30 @@ def cmd_sequence(args: argparse.Namespace) -> int:
115
190
 
116
191
  if args.save_history:
117
192
  _save_json(args.save_history, G.graph.get("history", {}))
193
+ if args.export_history_base:
194
+ export_history(G, args.export_history_base, fmt=args.export_format)
118
195
  return 0
119
196
 
120
197
 
121
198
  def cmd_metrics(args: argparse.Namespace) -> int:
122
199
  G = build_graph(n=args.nodes, topology=args.topology, seed=args.seed)
200
+ if getattr(args, "config", None):
201
+ apply_config(G, args.config)
123
202
  _attach_callbacks(G)
203
+ validate_canon(G)
204
+ if args.dt is not None:
205
+ G.graph["DT"] = float(args.dt)
206
+ if args.integrator is not None:
207
+ G.graph["INTEGRATOR_METHOD"] = str(args.integrator)
208
+ if getattr(args, "remesh_mode", None):
209
+ G.graph["REMESH_MODE"] = str(args.remesh_mode)
124
210
  G.graph.setdefault("GRAMMAR_CANON", DEFAULTS["GRAMMAR_CANON"]).update({"enabled": bool(args.grammar_canon)})
125
211
  G.graph["glyph_selector"] = default_glyph_selector if args.selector == "basic" else parametric_glyph_selector
126
- G.graph["GAMMA"] = {"type": args.gamma}
212
+ G.graph["GAMMA"] = {
213
+ "type": args.gamma_type,
214
+ "beta": args.gamma_beta,
215
+ "R0": args.gamma_R0,
216
+ }
127
217
  for _ in range(int(args.steps or 200)):
128
218
  step(G)
129
219
 
@@ -155,11 +245,27 @@ def main(argv: Optional[List[str]] = None) -> int:
155
245
  p_run.add_argument("--steps", type=int, default=200)
156
246
  p_run.add_argument("--seed", type=int, default=1)
157
247
  p_run.add_argument("--preset", type=str, default=None)
248
+ p_run.add_argument("--config", type=str, default=None)
249
+ p_run.add_argument("--dt", type=float, default=None)
250
+ p_run.add_argument("--integrator", choices=["euler", "rk4"], default=None)
158
251
  p_run.add_argument("--save-history", dest="save_history", type=str, default=None)
252
+ p_run.add_argument("--export-history-base", dest="export_history_base", type=str, default=None)
253
+ p_run.add_argument("--export-format", dest="export_format", choices=["csv", "json"], default="json")
159
254
  p_run.add_argument("--summary", action="store_true")
255
+ p_run.add_argument("--remesh-mode", choices=["knn", "mst", "community"], default=None)
160
256
  p_run.add_argument("--no-canon", dest="grammar_canon", action="store_false", default=True, help="Desactiva gramática canónica")
257
+ p_run.add_argument("--grammar.enabled", dest="grammar_enabled", type=_str2bool, default=None)
258
+ p_run.add_argument("--grammar.zhir_requires_oz_window", dest="grammar_zhir_requires_oz_window", type=int, default=None)
259
+ p_run.add_argument("--grammar.zhir_dnfr_min", dest="grammar_zhir_dnfr_min", type=float, default=None)
260
+ p_run.add_argument("--grammar.thol_min_len", dest="grammar_thol_min_len", type=int, default=None)
261
+ p_run.add_argument("--grammar.thol_max_len", dest="grammar_thol_max_len", type=int, default=None)
262
+ p_run.add_argument("--grammar.thol_close_dnfr", dest="grammar_thol_close_dnfr", type=float, default=None)
263
+ p_run.add_argument("--grammar.si_high", dest="grammar_si_high", type=float, default=None)
264
+ p_run.add_argument("--glyph.hysteresis_window", dest="glyph_hysteresis_window", type=int, default=None)
161
265
  p_run.add_argument("--selector", choices=["basic", "param"], default="basic")
162
- p_run.add_argument("--gamma", choices=list(GAMMA_REGISTRY.keys()), default="none")
266
+ p_run.add_argument("--gamma-type", choices=list(GAMMA_REGISTRY.keys()), default="none")
267
+ p_run.add_argument("--gamma-beta", type=float, default=0.0)
268
+ p_run.add_argument("--gamma-R0", type=float, default=0.0)
163
269
  p_run.set_defaults(func=cmd_run)
164
270
 
165
271
  p_seq = sub.add_parser("sequence", help="Ejecutar una secuencia (preset o YAML/JSON)")
@@ -168,7 +274,24 @@ def main(argv: Optional[List[str]] = None) -> int:
168
274
  p_seq.add_argument("--seed", type=int, default=1)
169
275
  p_seq.add_argument("--preset", type=str, default=None)
170
276
  p_seq.add_argument("--sequence-file", type=str, default=None)
277
+ p_seq.add_argument("--config", type=str, default=None)
278
+ p_seq.add_argument("--dt", type=float, default=None)
279
+ p_seq.add_argument("--integrator", choices=["euler", "rk4"], default=None)
171
280
  p_seq.add_argument("--save-history", dest="save_history", type=str, default=None)
281
+ p_seq.add_argument("--export-history-base", dest="export_history_base", type=str, default=None)
282
+ p_seq.add_argument("--export-format", dest="export_format", choices=["csv", "json"], default="json")
283
+ p_seq.add_argument("--remesh-mode", choices=["knn", "mst", "community"], default=None)
284
+ p_seq.add_argument("--gamma-type", choices=list(GAMMA_REGISTRY.keys()), default="none")
285
+ p_seq.add_argument("--gamma-beta", type=float, default=0.0)
286
+ p_seq.add_argument("--gamma-R0", type=float, default=0.0)
287
+ p_seq.add_argument("--grammar.enabled", dest="grammar_enabled", type=_str2bool, default=None)
288
+ p_seq.add_argument("--grammar.zhir_requires_oz_window", dest="grammar_zhir_requires_oz_window", type=int, default=None)
289
+ p_seq.add_argument("--grammar.zhir_dnfr_min", dest="grammar_zhir_dnfr_min", type=float, default=None)
290
+ p_seq.add_argument("--grammar.thol_min_len", dest="grammar_thol_min_len", type=int, default=None)
291
+ p_seq.add_argument("--grammar.thol_max_len", dest="grammar_thol_max_len", type=int, default=None)
292
+ p_seq.add_argument("--grammar.thol_close_dnfr", dest="grammar_thol_close_dnfr", type=float, default=None)
293
+ p_seq.add_argument("--grammar.si_high", dest="grammar_si_high", type=float, default=None)
294
+ p_seq.add_argument("--glyph.hysteresis_window", dest="glyph_hysteresis_window", type=int, default=None)
172
295
  p_seq.set_defaults(func=cmd_sequence)
173
296
 
174
297
  p_met = sub.add_parser("metrics", help="Correr breve y volcar métricas clave")
@@ -176,10 +299,16 @@ def main(argv: Optional[List[str]] = None) -> int:
176
299
  p_met.add_argument("--topology", choices=["ring", "complete", "erdos"], default="ring")
177
300
  p_met.add_argument("--steps", type=int, default=300)
178
301
  p_met.add_argument("--seed", type=int, default=1)
302
+ p_met.add_argument("--dt", type=float, default=None)
303
+ p_met.add_argument("--integrator", choices=["euler", "rk4"], default=None)
179
304
  p_met.add_argument("--no-canon", dest="grammar_canon", action="store_false", default=True, help="Desactiva gramática canónica")
180
305
  p_met.add_argument("--selector", choices=["basic", "param"], default="basic")
181
- p_met.add_argument("--gamma", choices=list(GAMMA_REGISTRY.keys()), default="none")
306
+ p_met.add_argument("--gamma-type", choices=list(GAMMA_REGISTRY.keys()), default="none")
307
+ p_met.add_argument("--gamma-beta", type=float, default=0.0)
308
+ p_met.add_argument("--gamma-R0", type=float, default=0.0)
309
+ p_met.add_argument("--remesh-mode", choices=["knn", "mst", "community"], default=None)
182
310
  p_met.add_argument("--save", type=str, default=None)
311
+ p_met.add_argument("--config", type=str, default=None)
183
312
  p_met.set_defaults(func=cmd_metrics)
184
313
 
185
314
  args = p.parse_args(argv)
tnfr/config.py ADDED
@@ -0,0 +1,41 @@
1
+ """Carga e inyección de configuraciones externas.
2
+
3
+ Permite definir parámetros en JSON o YAML y aplicarlos sobre ``G.graph``
4
+ reutilizando :func:`tnfr.constants.inject_defaults`.
5
+ """
6
+
7
+ from __future__ import annotations
8
+ from typing import Any, Dict
9
+ import json
10
+
11
+ try: # pragma: no cover - dependencia opcional
12
+ import yaml # type: ignore
13
+ except Exception: # pragma: no cover
14
+ yaml = None
15
+
16
+ from .constants import inject_defaults
17
+
18
+
19
+ def load_config(path: str) -> Dict[str, Any]:
20
+ """Lee un archivo JSON/YAML y devuelve un ``dict`` con los parámetros."""
21
+ with open(path, "r", encoding="utf-8") as f:
22
+ text = f.read()
23
+ if path.endswith((".yaml", ".yml")):
24
+ if not yaml: # pragma: no cover - fallo en entorno sin pyyaml
25
+ raise RuntimeError("pyyaml no está instalado")
26
+ data = yaml.safe_load(text)
27
+ else:
28
+ data = json.loads(text)
29
+ if not isinstance(data, dict):
30
+ raise ValueError("El archivo de configuración debe contener un objeto")
31
+ return data
32
+
33
+
34
+ def apply_config(G, path: str) -> None:
35
+ """Inyecta parámetros desde ``path`` sobre ``G.graph``.
36
+
37
+ Se reutiliza :func:`inject_defaults` para mantener la semántica de los
38
+ *defaults* canónicos.
39
+ """
40
+ cfg = load_config(path)
41
+ inject_defaults(G, cfg, override=True)
tnfr/constants.py CHANGED
@@ -11,8 +11,10 @@ from typing import Dict, Any
11
11
  # Parámetros canónicos
12
12
  # -------------------------
13
13
  DEFAULTS: Dict[str, Any] = {
14
- # Discretización
15
- "DT": 1.0,
14
+ # Discretización
15
+ "DT": 1.0,
16
+ "INTEGRATOR_METHOD": "euler",
17
+ "DT_MIN": 0.1,
16
18
 
17
19
  # Rango de EPI (estructura primaria)
18
20
  "EPI_MIN": -1.0,
@@ -43,9 +45,10 @@ DEFAULTS: Dict[str, Any] = {
43
45
  "INIT_VF_CLAMP_TO_LIMITS": True,
44
46
 
45
47
 
46
- # Mezcla para ΔNFR (campo nodal)
47
- # phase: dispersión de fase local; epi: gradiente de EPI; vf: desajuste de νf
48
- "DNFR_WEIGHTS": {"phase": 0.34, "epi": 0.33, "vf": 0.33},
48
+ # Mezcla para ΔNFR (campo nodal)
49
+ # phase: dispersión de fase local; epi: gradiente de EPI; vf: desajuste de νf;
50
+ # topo: término topológico (p. ej., centralidad). Pesos se normalizan.
51
+ "DNFR_WEIGHTS": {"phase": 0.34, "epi": 0.33, "vf": 0.33, "topo": 0.0},
49
52
 
50
53
  # Índice de sentido Si = α·νf_norm + β·(1 - disp_fase) + γ·(1 - |ΔNFR|/max)
51
54
  "SI_WEIGHTS": {"alpha": 0.34, "beta": 0.33, "gamma": 0.33},
@@ -76,33 +79,54 @@ DEFAULTS: Dict[str, Any] = {
76
79
  "REMESH_COOLDOWN_VENTANA": 20, # pasos mínimos entre RE’MESH
77
80
  "REMESH_COOLDOWN_TS": 0.0, # cooldown adicional por tiempo simulado
78
81
  # Gating adicional basado en observadores (conmutador + ventana)
79
- "REMESH_REQUIRE_STABILITY": False, # si True, exige ventana de estabilidad multi-métrica
80
- "REMESH_STABILITY_WINDOW": 25, # tamaño de ventana para evaluar estabilidad
81
- "REMESH_MIN_PHASE_SYNC": 0.85, # media mínima de sincronía de fase en ventana
82
- "REMESH_MAX_GLYPH_DISR": 0.35, # media máxima de carga glífica disruptiva en ventana
83
- "REMESH_LOG_EVENTS": True, # guarda eventos y metadatos del RE’MESH
84
-
85
- # RE’MESH: memoria τ y mezcla α
86
- "REMESH_TAU": 8, # pasos hacia atrás
82
+ "REMESH_REQUIRE_STABILITY": True, # si True, exige ventana de estabilidad multi-métrica
83
+ "REMESH_STABILITY_WINDOW": 25, # tamaño de ventana para evaluar estabilidad
84
+ "REMESH_MIN_PHASE_SYNC": 0.85, # media mínima de sincronía de fase en ventana
85
+ "REMESH_MAX_GLYPH_DISR": 0.35, # media máxima de carga glífica disruptiva en ventana
86
+ "REMESH_MIN_SIGMA_MAG": 0.50, # magnitud mínima de σ en ventana
87
+ "REMESH_MIN_KURAMOTO_R": 0.80, # R de Kuramoto mínimo en ventana
88
+ "REMESH_MIN_SI_HI_FRAC": 0.50, # fracción mínima de nodos con Si alto
89
+ "REMESH_LOG_EVENTS": True, # guarda eventos y metadatos del RE’MESH
90
+ "REMESH_MODE": "knn", # modo de remallado topológico
91
+ "REMESH_COMMUNITY_K": 2, # conexiones por comunidad
92
+
93
+ # RE’MESH: memoria τ y mezcla α (global/local)
94
+ "REMESH_TAU": 8, # compatibilidad: tau global por defecto
95
+ "REMESH_TAU_GLOBAL": 8, # pasos hacia atrás (escala global)
96
+ "REMESH_TAU_LOCAL": 4, # pasos hacia atrás (escala local)
87
97
  "REMESH_ALPHA": 0.5, # mezcla con pasado
88
98
  "REMESH_ALPHA_HARD": False, # si True ignora GLYPH_FACTORS['REMESH_alpha']
99
+
100
+ # Soporte y norma de la EPI
101
+ "EPI_SUPPORT_THR": 0.05, # umbral para Supp(EPI)
102
+
103
+ # U'M — compatibilidad mínima para crear/reforzar enlaces funcionales
104
+ "UM_COMPAT_THRESHOLD": 0.75,
89
105
 
90
- # Histéresis glífica
91
- "GLYPH_HYSTERESIS_WINDOW": 7,
92
-
93
- # Margen de histéresis del selector (cuánto "aguanta" sin cambiar glifo si está cerca de un umbral)
94
- "GLYPH_SELECTOR_MARGIN": 0.05,
106
+ # Histéresis glífica
107
+ "GLYPH_HYSTERESIS_WINDOW": 7,
108
+
109
+ # Lags máximos sin emisión (A’L) y recepción (E’N)
110
+ "AL_MAX_LAG": 5,
111
+ "EN_MAX_LAG": 3,
112
+
113
+ # Margen de histéresis del selector (cuánto "aguanta" sin cambiar glifo si está cerca de un umbral)
114
+ "GLYPH_SELECTOR_MARGIN": 0.05,
95
115
 
96
- # Ventana para estimar la carga glífica en history/plots
116
+ # Ventana para estimar la carga glífica en history/plots
97
117
  "GLYPH_LOAD_WINDOW": 50,
98
-
99
- # Tamaño de ventana para coherencia promedio W̄
100
- "WBAR_WINDOW": 25,
101
-
102
- # Factores suaves por glifo (operadores)
118
+
119
+ # Tamaño de ventana para coherencia promedio W̄
120
+ "WBAR_WINDOW": 25,
121
+
122
+ # Adaptación de frecuencia estructural por coherencia
123
+ "VF_ADAPT_TAU": 5, # pasos estables antes de ajustar νf
124
+ "VF_ADAPT_MU": 0.1, # velocidad de ajuste hacia la media vecinal
125
+
126
+ # Factores suaves por glifo (operadores)
103
127
  "GLYPH_FACTORS": {
104
- "AL_boost": 0.05, # A’L — pequeña emisión
105
- "EN_mix": 0.25, # E’N — mezcla con vecindad
128
+ "AL_boost": 0.05, # A’L — pequeña emisión
129
+ "EN_mix": 0.25, # E’N — mezcla con vecindad
106
130
  "IL_dnfr_factor": 0.7, # I’L — reduce ΔNFR
107
131
  "OZ_dnfr_factor": 1.3, # O’Z — aumenta ΔNFR
108
132
  "UM_theta_push": 0.25, # U’M — empuje adicional de fase local
@@ -111,17 +135,19 @@ DEFAULTS: Dict[str, Any] = {
111
135
  "VAL_scale": 1.15, # VA’L — expande EPI
112
136
  "NUL_scale": 0.85, # NU’L — contrae EPI
113
137
  "THOL_accel": 0.10, # T’HOL — acelera (seg. deriv.) si hay umbral
114
- "ZHIR_theta_shift": 1.57079632679, # Z’HIR — desplazamiento ~π/2
115
- "NAV_jitter": 0.05, # NA’V — pequeña inestabilidad creativa
116
- "REMESH_alpha": 0.5, # REMESHmezcla si no se usa REMESH_ALPHA
117
- },
138
+ "ZHIR_theta_shift": 1.57079632679, # Z’HIR — desplazamiento ~π/2
139
+ "NAV_jitter": 0.05, # NA’V — pequeña inestabilidad creativa
140
+ "NAV_eta": 0.5, # NAVpeso de convergencia hacia νf
141
+ "REMESH_alpha": 0.5, # RE’MESH — mezcla si no se usa REMESH_ALPHA
142
+ },
118
143
 
119
144
  # Umbrales para el selector glífico por defecto
120
145
  "GLYPH_THRESHOLDS": {"hi": 0.66, "lo": 0.33, "dnfr": 1e-3},
121
146
 
122
147
  # Comportamiento NA’V
123
- "NAV_RANDOM": True, # si True, usa jitter aleatorio en [-j, j]; si False, jitter determinista por signo
124
- "RANDOM_SEED": 0, # semilla base para reproducibilidad del jitter
148
+ "NAV_RANDOM": True, # si True, usa jitter aleatorio en [-j, j]; si False, jitter determinista por signo
149
+ "NAV_STRICT": False, # si True, fuerza ΔNFR νf (sin mezcla)
150
+ "RANDOM_SEED": 0, # semilla base para reproducibilidad del jitter
125
151
 
126
152
  # Modo ruido para O’Z
127
153
  "OZ_NOISE_MODE": False, # si True, añade ruido aditivo en ΔNFR
@@ -169,6 +195,41 @@ DEFAULTS.setdefault("GRAMMAR_CANON", {
169
195
  "thol_close_dnfr": 0.15, # si el campo calma, cerramos con SH’A/NU’L
170
196
  "si_high": 0.66, # umbral para elegir NU’L vs SH’A al cerrar
171
197
  })
198
+
199
+ # --- Coherencia (W) ---
200
+ DEFAULTS.setdefault("COHERENCE", {
201
+ "enabled": True,
202
+ "scope": "neighbors", # "neighbors" | "all"
203
+ "weights": {
204
+ "phase": 0.34,
205
+ "epi": 0.33,
206
+ "vf": 0.20,
207
+ "si": 0.13,
208
+ },
209
+ "self_on_diag": True, # W_ii = 1.0
210
+ "store_mode": "sparse", # "sparse" | "dense"
211
+ "threshold": 0.0,
212
+ "history_key": "W_sparse",
213
+ "Wi_history_key": "W_i",
214
+ "stats_history_key": "W_stats",
215
+ })
216
+
217
+ # --- Diagnóstico nodal ---
218
+ DEFAULTS.setdefault("DIAGNOSIS", {
219
+ "enabled": True,
220
+ "window": 16,
221
+ "history_key": "nodal_diag",
222
+ "stable": {"Rloc_hi": 0.80, "dnfr_lo": 0.20, "persist": 3},
223
+ "dissonance": {"Rloc_lo": 0.40, "dnfr_hi": 0.50, "persist": 3},
224
+ "transition": {"persist": 2},
225
+ "compute_symmetry": True,
226
+ "include_typology": False,
227
+ "advice": {
228
+ "stable": ["Coherencia", "Acoplamiento", "Resonancia"],
229
+ "transition": ["Transición", "Resonancia", "Autoorganización"],
230
+ "dissonant": ["Silencio", "Contracción", "Mutación"],
231
+ },
232
+ })
172
233
 
173
234
 
174
235
  # -------------------------
@@ -177,11 +238,7 @@ DEFAULTS.setdefault("GRAMMAR_CANON", {
177
238
 
178
239
  def attach_defaults(G, override: bool = False) -> None:
179
240
  """Escribe DEFAULTS en G.graph (sin sobreescribir si override=False)."""
180
- G.graph.setdefault("_tnfr_defaults_attached", False)
181
- for k, v in DEFAULTS.items():
182
- if override or k not in G.graph:
183
- G.graph[k] = v
184
- G.graph["_tnfr_defaults_attached"] = True
241
+ inject_defaults(G, DEFAULTS, override=override)
185
242
 
186
243
 
187
244
  def inject_defaults(G, defaults: Dict[str, Any] = DEFAULTS, override: bool = False) -> None:
@@ -210,7 +267,11 @@ def merge_overrides(G, **overrides) -> None:
210
267
  ALIAS_VF = ("νf", "nu_f", "nu-f", "nu", "freq", "frequency")
211
268
  ALIAS_THETA = ("θ", "theta", "fase", "phi", "phase")
212
269
  ALIAS_DNFR = ("ΔNFR", "delta_nfr", "dnfr")
213
- ALIAS_EPI = ("EPI", "psi", "PSI", "value")
214
- ALIAS_SI = ("Si", "sense_index", "S_i", "sense", "meaning_index")
215
- ALIAS_dEPI = ("dEPI_dt", "dpsi_dt", "dEPI", "velocity")
216
- ALIAS_D2EPI = ("d2EPI_dt2", "d2psi_dt2", "d2EPI", "accel")
270
+ ALIAS_EPI = ("EPI", "psi", "PSI", "value")
271
+ ALIAS_EPI_KIND = ("EPI_kind", "epi_kind", "source_glifo")
272
+ ALIAS_SI = ("Si", "sense_index", "S_i", "sense", "meaning_index")
273
+ ALIAS_dEPI = ("dEPI_dt", "dpsi_dt", "dEPI", "velocity")
274
+ ALIAS_D2EPI = ("d2EPI_dt2", "d2psi_dt2", "d2EPI", "accel")
275
+ ALIAS_dVF = ("dνf_dt", "dvf_dt", "dnu_dt", "dvf")
276
+ ALIAS_D2VF = ("d2νf_dt2", "d2vf_dt2", "d2nu_dt2", "B")
277
+ ALIAS_dSI = ("δSi", "delta_Si", "dSi")