tnfr 1.0__py3-none-any.whl → 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tnfr might be problematic. Click here for more details.

tnfr/core/ontosim.py DELETED
@@ -1,1074 +0,0 @@
1
- from typing import List
2
- import networkx as nx
3
- from dataclasses import dataclass
4
- from collections import Counter
5
- import math
6
- from math import isnan
7
- import numpy as np
8
- import random
9
- from tnfr.resonance.dynamics import inicializar_coordinador_temporal_canonico
10
- from tnfr.resonance.dynamics import BifurcationManagerTNFR
11
- from tnfr.resonance.dynamics import integrar_bifurcaciones_canonicas_en_simulacion
12
- from tnfr.resonance.dynamics import integrar_tiempo_topologico_en_simulacion
13
- from tnfr.resonance.dynamics import evaluar_activacion_glifica_dinamica
14
- from tnfr.matrix.operators import acoplar_nodos
15
- from tnfr.matrix.operators import aplicar_remesh_si_estabilizacion_global
16
- from tnfr.matrix.operators import detectar_EPIs_compuestas
17
- from tnfr.matrix.operators import glifo_por_estructura
18
- from tnfr.matrix.operators import aplicar_glifo
19
- from tnfr.matrix.operators import transicion_glifica_canonica
20
- from tnfr.matrix.operators import interpretar_sintaxis_glífica
21
- from tnfr.utils.helpers import evaluar_si_nodal
22
- from tnfr.utils.helpers import emergencia_nodal
23
- from tnfr.utils.helpers import detectar_macronodos
24
- from tnfr.utils.helpers import algo_se_mueve
25
- from tnfr.utils.helpers import reciente_glifo
26
- from tnfr.utils.helpers import detectar_nodos_pulsantes
27
- from tnfr.utils.helpers import promover_emergente
28
-
29
- def inicializar_nfr_emergente(forma_base, campo_coherencia=None):
30
- """
31
- Inicializa NFR siguiendo condiciones de emergencia nodal TNFR.
32
-
33
- Reemplaza las heurísticas ad-hoc por evaluación estructural canónica.
34
- """
35
- # Verificar condiciones de emergencia
36
- if not cumple_condiciones_emergencia(forma_base, campo_coherencia):
37
- return None
38
-
39
- # Calcular parámetros estructurales
40
- EPI = evaluar_coherencia_estructural(forma_base)
41
- νf = calcular_frecuencia_resonante(forma_base)
42
- Wi_t = generar_matriz_coherencia(forma_base)
43
- fase = sincronizar_con_campo(campo_coherencia, νf)
44
-
45
- # Calcular parámetros derivados
46
- # ΔNFR: gradiente nodal basado en estabilidad interna de Wi_t
47
- estabilidad_interna = np.trace(Wi_t) / len(Wi_t)
48
- ΔNFR = round((1.0 - estabilidad_interna) * 0.5 - 0.1, 3) # rango típico [-0.1, 0.4]
49
-
50
- # Si: índice de sentido basado en coherencia estructural y frecuencia
51
- Si = round((EPI / 2.5) * (νf / 3.0) * (1.0 - fase), 3) # decrece con disonancia
52
-
53
- # θ: umbral estructural basado en EPI y estabilidad
54
- θ = round(min(1.0, EPI * estabilidad_interna * 0.4), 3)
55
-
56
- # Crear NFR canónico
57
- nfr = {
58
- "estado": "activo",
59
- "glifo": "ninguno",
60
- "categoria": "ninguna",
61
- "EPI": EPI,
62
- "EPI_prev": EPI,
63
- "EPI_prev2": EPI,
64
- "EPI_prev3": EPI,
65
- "νf": νf,
66
- "ΔNFR": ΔNFR,
67
- "Si": Si,
68
- "θ": θ,
69
- "Wi_t": Wi_t,
70
- "fase": fase,
71
- "simetria_interna": round(estabilidad_interna, 3)
72
- }
73
-
74
- return nfr
75
-
76
- def crear_red_desde_datos(datos: List[dict]) -> nx.Graph:
77
- """Crea red TNFR desde datos estructurados - NUEVA FUNCIÓN"""
78
- G = nx.Graph()
79
- campo_coherencia = {}
80
-
81
- for nodo_data in datos:
82
- nodo_id = nodo_data.get('id', f"nodo_{len(G)}")
83
-
84
- # Usar inicialización canónica existente
85
- if 'forma_base' in nodo_data:
86
- nfr = inicializar_nfr_emergente(nodo_data['forma_base'], campo_coherencia)
87
- if nfr:
88
- G.add_node(nodo_id, **nfr)
89
- campo_coherencia[nodo_id] = nfr
90
- else:
91
- # Datos ya procesados
92
- G.add_node(nodo_id, **nodo_data)
93
-
94
- # Usar conectividad canónica existente
95
- umbrales, _ = gestionar_conexiones_canonico(G, 0, [])
96
- return G
97
-
98
- def calcular_frecuencia_resonante(forma_base):
99
- """
100
- Determina νf por patrones vibratorios estructurales.
101
-
102
- La frecuencia resonante depende de:
103
- - Alternancia estructural (consonante/vocal)
104
- - Densidad energética (consonantes oclusivas vs continuas)
105
- - Fluidez topológica (transiciones suaves)
106
- """
107
- if not forma_base:
108
- return 1.0
109
-
110
- forma_norm = forma_base.lower()
111
- longitud = len(forma_norm)
112
-
113
- # Clasificación fonética TNFR
114
- vocales = "aeiouáéíóúü"
115
- oclusivas = "pbtdkgqc" # alta energía, baja frecuencia
116
- continuas = "fvszjlmnr" # media energía, alta frecuencia
117
- fluidas = "wyh" # baja energía, muy alta frecuencia
118
-
119
- # Factor de alternancia (patrones alternos)
120
- alternancias = 0
121
- for i in range(longitud - 1):
122
- actual = forma_norm[i] in vocales
123
- siguiente = forma_norm[i+1] in vocales
124
- if actual != siguiente: # transición vocal-consonante o viceversa
125
- alternancias += 1
126
-
127
- factor_alternancia = alternancias / max(longitud - 1, 1)
128
-
129
- # Factor de densidad energética
130
- densidad_oclusiva = sum(1 for c in forma_norm if c in oclusivas) / longitud
131
- densidad_continua = sum(1 for c in forma_norm if c in continuas) / longitud
132
- densidad_fluida = sum(1 for c in forma_norm if c in fluidas) / longitud
133
-
134
- # Las continuas y fluidas aumentan frecuencia, las oclusivas la reducen
135
- factor_energia = (
136
- -0.5 * densidad_oclusiva + # reducen frecuencia
137
- 0.3 * densidad_continua + # aumentan ligeramente
138
- 0.7 * densidad_fluida # aumentan significativamente
139
- )
140
-
141
- # Factor de fluidez (transiciones suaves entre fonemas similares)
142
- def categoria_fonetica(c):
143
- if c in vocales: return 'V'
144
- elif c in oclusivas: return 'O'
145
- elif c in continuas: return 'C'
146
- elif c in fluidas: return 'F'
147
- else: return 'X'
148
-
149
- transiciones_suaves = 0
150
- for i in range(longitud - 1):
151
- cat1 = categoria_fonetica(forma_norm[i])
152
- cat2 = categoria_fonetica(forma_norm[i+1])
153
- # Transiciones suaves: V-C, C-V, C-F, F-V
154
- if (cat1, cat2) in [('V','C'), ('C','V'), ('C','F'), ('F','V'), ('V','F'), ('F','C')]:
155
- transiciones_suaves += 1
156
-
157
- factor_fluidez = transiciones_suaves / max(longitud - 1, 1)
158
-
159
- # Frecuencia base según longitud (formas más largas tienden a menor frecuencia)
160
- freq_base = 1.2 - min(0.4, longitud / 20)
161
-
162
- # Combinar todos los factores
163
- νf = freq_base * (
164
- 1.0 +
165
- 0.4 * factor_alternancia + # alternancia aumenta frecuencia
166
- 0.3 * factor_energia + # balance energético
167
- 0.3 * factor_fluidez # fluidez aumenta frecuencia
168
- )
169
-
170
- # Limitar al rango válido [0.1, 3.0]
171
- νf = max(0.1, min(3.0, νf))
172
-
173
- return round(νf, 3)
174
-
175
- def _deben_conectarse_canonico(n1: dict, n2: dict) -> bool:
176
- """Mejora la lógica existente con umbral áureo"""
177
- phi = (1 + math.sqrt(5)) / 2 # φ ≈ 1.618
178
-
179
- diferencia_vf = abs(n1.get('νf', 1) - n2.get('νf', 1))
180
- diferencia_fase = abs(n1.get('fase', 0) - n2.get('fase', 0)) % (2 * math.pi)
181
-
182
- return (diferencia_vf < 0.01 * phi and
183
- diferencia_fase < math.pi / 2)
184
-
185
- def simular_emergencia(G, pasos=100):
186
-
187
- umbrales = {
188
- 'θ_min': 0.18,
189
- 'EPI_max_dinamico': 3.0,
190
- 'θ_mutacion': 0.25,
191
- 'θ_colapso': 0.45,
192
- 'bifurcacion_aceleracion': 0.15,
193
- 'EPI_min_coherencia': 0.4, # ← Añade este valor por defecto
194
- 'θ_conexion': 0.12,
195
- 'EPI_conexion': 1.8,
196
- 'νf_conexion': 0.2,
197
- 'Si_conexion': 0.25,
198
- 'θ_autoorganizacion': 0.35,
199
- 'bifurcacion_gradiente': 0.8,
200
- 'sensibilidad_calculada': 1.0,
201
- 'factor_densidad': 1.0,
202
- 'fase': 'emergencia'
203
- }
204
-
205
- global historia_Ct
206
- if 'historia_Ct' not in globals():
207
- historia_Ct = []
208
- historia_epi = []
209
- historia_glifos = ["paso,nodo,glifo"]
210
- historial_glifos_por_nodo = {}
211
- G_historia = []
212
- registro_conexiones = []
213
- coordinador_temporal = inicializar_coordinador_temporal_canonico()
214
- bifurcation_manager = BifurcationManagerTNFR()
215
-
216
- historial_temporal = []
217
-
218
- glifo_categoria = {
219
- "AL": "activador", "EN": "receptor", "IL": "estabilizador",
220
- "OZ": "disonante", "UM": "acoplador", "RA": "resonador",
221
- "SHA": "latente", "VAL": "expansivo", "NUL": "contractivo",
222
- "THOL": "autoorganizador", "ZHIR": "mutante", "NAV": "transicional",
223
- "REMESH": "recursivo"
224
- }
225
-
226
- # Activación mínima inicial si todos están inactivos o silenciosos
227
- if all(G.nodes[n]["estado"] in ["latente", "silencio"] for n in G.nodes):
228
- for n in G.nodes:
229
- if G.nodes[n]["EPI"] > 0.8 and G.nodes[n]["νf"] > 0.5:
230
- G.nodes[n]["estado"] = "activo"
231
- G.nodes[n]["glifo"] = "AL"
232
- break # activa solo uno, para iniciar pulso
233
-
234
- for paso in range(pasos):
235
- nodos_activos = [n for n in G.nodes if G.nodes[n]["estado"] == "activo"]
236
- paso_data = []
237
-
238
- acoplar_nodos(G)
239
-
240
- # Cálculo de umbrales adaptativos para emergencia nodal
241
- vf_values = [G.nodes[n]["νf"] for n in G.nodes if G.nodes[n]["estado"] == "activo"]
242
- dNFR_values = [G.nodes[n]["ΔNFR"] for n in G.nodes if G.nodes[n]["estado"] == "activo"]
243
-
244
- media_vf = np.mean(vf_values) if vf_values else 0
245
- std_dNFR = np.std(dNFR_values) if dNFR_values else 0
246
-
247
- for n in list(G.nodes):
248
-
249
- nodo = G.nodes[n]
250
- def valor_valido(x):
251
- return x is not None and not isinstance(x, str) and not isnan(x)
252
-
253
- for n in list(G.nodes):
254
- nodo = G.nodes[n]
255
-
256
- for clave in ["EPI_prev", "EPI_prev2", "EPI_prev3"]:
257
- if not valor_valido(nodo.get(clave)):
258
- nodo[clave] = nodo.get("EPI", 1.0)
259
-
260
- if nodo["estado"] == "activo":
261
- # Dinámica basal influida por νf y sentido
262
- factor_ruido = random.uniform(0.98, 1.02) + 0.02 * random.uniform(-1, 1) * (1 - nodo["Si"])
263
- modulador = factor_ruido * (1 + 0.02 * min(nodo.get("νf", 1.0), 5)) # cap νf por seguridad
264
-
265
- nodo["EPI"] *= modulador
266
-
267
- # Evitar NaN o valores extremos
268
- if not np.isfinite(nodo["EPI"]) or nodo["EPI"] > 10:
269
- nodo["EPI"] = 1.0 + random.uniform(-0.05, 0.05) # reset suave)
270
- if nodo["EPI"] > 1e4:
271
- nodo["EPI"] = 1e4
272
- nodo["ΔNFR"] += random.uniform(-0.08, 0.08) * (1.1 - nodo["Si"])
273
- nodo["ΔNFR"] = max(min(nodo["ΔNFR"], 1.5), -1.5)
274
-
275
- # Condición de apagado nodal si pierde coherencia estructural
276
- if (
277
- nodo["EPI"] < 0.85
278
- and abs(nodo["ΔNFR"]) > 0.4
279
- and nodo["Si"] < 0.3
280
- ):
281
- nodo["estado"] = "inactivo"
282
-
283
- evaluar_si_nodal(nodo, paso)
284
-
285
- if (
286
- nodo["estado"] == "silencio"
287
- and abs(nodo["ΔNFR"] - nodo["νf"]) < 0.05
288
- and nodo.get("Si", 0) > 0.25
289
- and nodo.get("d2EPI_dt2", 0) > 0.03
290
- and not reciente_glifo(n, "NAV", historial_glifos_por_nodo, pasos=6)
291
- ):
292
- aplicar_glifo(G, nodo, n, "NAV", historial_glifos_por_nodo, paso)
293
- historia_glifos.append(f"{paso},{n},NAV")
294
- nodo["estado"] = "activo"
295
-
296
- if (
297
- nodo["EPI"] < 0.6
298
- and abs(nodo["ΔNFR"]) > 0.75
299
- and nodo["Si"] < 0.25
300
- and not reciente_glifo(n, "SHA", historial_glifos_por_nodo, pasos=6)
301
- ):
302
- aplicar_glifo(G, nodo, n, "SHA", historial_glifos_por_nodo, paso)
303
- historia_glifos.append(f"{paso},{n},SHA")
304
- continue
305
-
306
- if (
307
- nodo["estado"] == "latente"
308
- and abs(nodo["ΔNFR"]) < 0.05
309
- and nodo["Si"] > 0.3
310
- and not reciente_glifo(n, "EN", historial_glifos_por_nodo, pasos=10)
311
- ):
312
- aplicar_glifo(G, nodo, n, "EN", historial_glifos_por_nodo, paso)
313
- historia_glifos.append(f"{paso},{n},EN")
314
-
315
- if (
316
- nodo["glifo"] == "IL"
317
- and nodo["Si"] > 0.55
318
- and nodo["νf"] > 1.25
319
- and abs(nodo["ΔNFR"]) < 0.15 # Baja necesidad de reorganización
320
- and not reciente_glifo(n, "RA", historial_glifos_por_nodo, pasos=8)
321
- ):
322
- aplicar_glifo(G, nodo, n, "RA", historial_glifos_por_nodo, paso)
323
- historia_glifos.append(f"{paso},{n},RA")
324
-
325
- vecinos = list(G.neighbors(n))
326
- if (
327
- nodo["estado"] == "activo"
328
- and vecinos
329
- and sum(1 for v in vecinos if abs(G.nodes[v]["θ"] - nodo["θ"]) < 0.08) >= 2
330
- and not reciente_glifo(n, "UM", historial_glifos_por_nodo, pasos=8)
331
- ):
332
- aplicar_glifo(G, nodo, n, "UM", historial_glifos_por_nodo, paso)
333
- historia_glifos.append(f"{paso},{n},UM")
334
-
335
- if (
336
- abs(nodo.get("d2EPI_dt2", 0)) > 0.25
337
- and nodo["Si"] > 0.6
338
- and not reciente_glifo(n, "ZHIR", historial_glifos_por_nodo, pasos=10)
339
- ):
340
- aplicar_glifo(G, nodo, n, "ZHIR", historial_glifos_por_nodo, paso)
341
- historia_glifos.append(f"{paso},{n},ZHIR")
342
-
343
- if emergencia_nodal(nodo, media_vf, std_dNFR):
344
- glifo = glifo_por_estructura(nodo, G)
345
- if glifo:
346
- aplicar_glifo(G, nodo, n, glifo, historial_glifos_por_nodo, paso)
347
- historia_glifos.append(f"{paso},{n},{glifo}")
348
- nodo["categoria"] = glifo_categoria.get(glifo, "ninguna")
349
-
350
- # Evaluación glífica con umbrales dinámicos (mejora canónica)
351
- vecinos_data = [G.nodes[v] for v in G.neighbors(n)]
352
- glifo_dinamico = evaluar_activacion_glifica_dinamica(nodo, umbrales, vecinos_data)
353
-
354
- if glifo_dinamico and not reciente_glifo(n, glifo_dinamico, historial_glifos_por_nodo, pasos=8):
355
- aplicar_glifo(G, nodo, n, glifo_dinamico, historial_glifos_por_nodo, paso)
356
- historia_glifos.append(f"{paso},{n},{glifo_dinamico}")
357
-
358
- glifo_siguiente = transicion_glifica_canonica(nodo)
359
- if glifo_siguiente:
360
- aplicar_glifo(G, nodo, n, glifo_siguiente, historial_glifos_por_nodo, paso)
361
- historia_glifos.append(f"{paso},{n},{glifo_siguiente}")
362
- nodo["glifo"] = glifo_siguiente
363
- nodo["categoria"] = glifo_categoria.get(glifo_siguiente, "ninguna")
364
-
365
- # Activación estructural de VAL (expansión controlada)
366
- if (
367
- nodo["Si"] > 0.8
368
- and nodo["EPI"] > 1.2
369
- and abs(nodo["ΔNFR"]) < 0.2
370
- and nodo.get("dEPI_dt", 0) > 0.15
371
- and not reciente_glifo(n, "VAL", historial_glifos_por_nodo, pasos=10)
372
- ):
373
- if "expansiones_val" not in nodo:
374
- nodo["expansiones_val"] = 0
375
-
376
- if nodo["expansiones_val"] < 3:
377
- activar_val_si_estabilidad(n, G, paso, historial_glifos_por_nodo)
378
- nodo["expansiones_val"] += 1
379
- else:
380
- aplicar_glifo(G, nodo, n, "THOL", historial_glifos_por_nodo, paso)
381
- historia_glifos.append(f"{paso},{n},THOL")
382
-
383
- if nodo.get("glifo") == "VAL":
384
- condiciones_contraccion = (
385
- abs(nodo.get("d2EPI_dt2", 0)) < 0.05 and
386
- abs(nodo.get("ΔNFR", 0)) < 0.1 and
387
- nodo.get("νf", 1.0) < 1.0 and
388
- abs(nodo.get("EPI", 0) - nodo.get("EPI_prev", 0)) < 0.01
389
- )
390
-
391
- if condiciones_contraccion:
392
- aplicar_glifo(G, nodo, n, "NUL", historial_glifos_por_nodo, paso)
393
- historia_glifos.append(f"{paso},{n},NUL")
394
- nodo["glifo"] = "NUL"
395
- nodo["categoria"] = glifo_categoria.get("NUL", "ninguna")
396
-
397
- paso_data.append({
398
- "nodo": n,
399
- "paso": paso,
400
- "EPI": round(nodo["EPI"], 2)
401
- })
402
- nodo["EPI_prev3"] = nodo.get("EPI_prev2", nodo["EPI_prev"])
403
- nodo["EPI_prev2"] = nodo.get("EPI_prev", nodo["EPI"])
404
- nodo["EPI_prev"] = nodo["EPI"] if np.isfinite(nodo["EPI"]) else 1.0
405
-
406
- # Cálculo de ∂EPI/∂t = νf · ΔNFR
407
- dEPI_dt = nodo["νf"] * nodo["ΔNFR"]
408
- nodo["dEPI_dt"] = dEPI_dt
409
- if "historial_dEPI_dt" not in nodo:
410
- nodo["historial_dEPI_dt"] = []
411
- nodo["historial_dEPI_dt"].append((paso, dEPI_dt))
412
-
413
- # Registrar evolución de νf y ΔNFR
414
- if "historial_vf" not in nodo:
415
- nodo["historial_vf"] = []
416
- if "historial_dNFR" not in nodo:
417
- nodo["historial_dNFR"] = []
418
-
419
- nodo["historial_vf"].append((paso, nodo["νf"]))
420
- nodo["historial_dNFR"].append((paso, nodo["ΔNFR"]))
421
-
422
- # Calcular aceleración estructural ∂²EPI/∂t² solo si los valores son válidos
423
- if all(np.isfinite([nodo.get("EPI", 0), nodo.get("EPI_prev", 0), nodo.get("EPI_prev2", 0)])):
424
- aceleracion = nodo["EPI"] - 2 * nodo["EPI_prev"] + nodo["EPI_prev2"]
425
- else:
426
- aceleracion = 0.0 # O un valor neutro que no active mutaciones erróneas
427
-
428
- nodo["d2EPI_dt2"] = aceleracion
429
-
430
- # Umbral de bifurcación: si se supera, aplicar THOL
431
- resultado_bifurcaciones = integrar_bifurcaciones_canonicas_en_simulacion(
432
- G, paso, coordinador_temporal, bifurcation_manager
433
- )
434
-
435
- # Evaluar contracción si hay disonancia o colapso de sentido (NU´L)
436
- if nodo.get("estado") == "activo":
437
- aplicar_contraccion_nul(n, G, paso, historial_glifos_por_nodo)
438
-
439
- # === CONTROL DE EXPANSIÓN INFINITA ===
440
- if "expansiones_val" not in nodo:
441
- nodo["expansiones_val"] = 0
442
-
443
- if nodo["expansiones_val"] >= 3:
444
- continue # evita expansión si ya lo hizo demasiadas veces
445
-
446
- # Aquí sí puede expandirse:
447
- activar_val_si_estabilidad(n, G, paso, historial_glifos_por_nodo)
448
- nodo["expansiones_val"] += 1
449
-
450
- if (
451
- nodo.get("estado") == "activo"
452
- and nodo.get("Si", 0) > 0.8
453
- and nodo.get("EPI", 0) > 1.1
454
- and abs(nodo.get("ΔNFR", 0)) < 0.25
455
- and nodo.get("dEPI_dt", 0) > 0.15
456
- and not reciente_glifo(n, "VAL", historial_glifos_por_nodo, pasos=8)
457
- ):
458
- activar_val_si_estabilidad(n, G, paso, historial_glifos_por_nodo)
459
-
460
- # Guardar aceleración para graficar más tarde
461
- if "historial_aceleracion" not in nodo:
462
- nodo["historial_aceleracion"] = []
463
- nodo["historial_aceleracion"].append((paso, aceleracion))
464
-
465
- # Gestión temporal topológica TNFR
466
- resultado_temporal = integrar_tiempo_topologico_en_simulacion(G, paso, coordinador_temporal)
467
- historial_temporal.append(resultado_temporal['estadisticas'])
468
-
469
- # Gestión de conexiones con información temporal
470
- umbrales, estadisticas_conexiones = gestionar_conexiones_canonico(G, paso, historia_Ct)
471
-
472
- # Calcular coherencia total C(t) al final del paso
473
- C_t = sum(G.nodes[n]["EPI"] for n in G.nodes) / len(G)
474
- historia_Ct.append((paso, C_t))
475
-
476
- historia_epi.append(paso_data)
477
-
478
- G_snapshot = nx.Graph()
479
- G_snapshot.add_nodes_from([(n, G.nodes[n].copy()) for n in G.nodes])
480
- G_snapshot.add_edges_from(G.edges)
481
- G_historia.append(G_snapshot)
482
-
483
- for nodo_id in list(historial_glifos_por_nodo.keys()):
484
- glifos = historial_glifos_por_nodo[nodo_id]
485
-
486
- if (
487
- len(glifos) >= 3
488
- and glifos[-1][1] == glifos[-2][1] == glifos[-3][1]
489
- and abs(G.nodes[nodo_id]["EPI"] - G.nodes[nodo_id]["EPI_prev"]) < 0.05
490
- ):
491
- aplicar_glifo(G, G.nodes[nodo_id], nodo_id, "REMESH", historial_glifos_por_nodo, paso)
492
- historia_glifos.append(f"{paso},{nodo_id},REMESH")
493
-
494
- aplicar_remesh_si_estabilizacion_global(G, historial_glifos_por_nodo, historia_glifos, paso)
495
- aplicar_remesh_grupal(G, historial_glifos_por_nodo)
496
- epi_compuestas = detectar_EPIs_compuestas(G, umbrales)
497
- if algo_se_mueve(G, historial_glifos_por_nodo, paso):
498
- historial_macronodos, macronodes_info = detectar_macronodos(G, historial_glifos_por_nodo, epi_compuestas, paso)
499
-
500
- else:
501
- macronodes_info = {'nodos': [], 'conexiones': []}
502
-
503
- # Evaluar exceso de VAL y promover reorganización estructural
504
- for nodo_id, glifos in historial_glifos_por_nodo.items():
505
- ultimos = [g for _, g in glifos[-6:]] # últimos 6 glifos del nodo
506
- if ultimos.count("VAL") >= 4 and "THOL" not in ultimos and "ZHIR" not in ultimos:
507
- nodo = G.nodes[nodo_id]
508
-
509
- # Se decide el glifo correctivo en función de su Si y ΔNFR
510
- if nodo["Si"] > 0.5 and abs(nodo["ΔNFR"]) < 0.2:
511
- aplicar_glifo(G, nodo, nodo_id, "THOL", historial_glifos_por_nodo, paso)
512
- historia_glifos.append(f"{paso},{nodo_id},THOL")
513
- else:
514
- aplicar_glifo(G, nodo, nodo_id, "ZHIR", historial_glifos_por_nodo, paso)
515
- historia_glifos.append(f"{paso},{nodo_id},ZHIR")
516
-
517
- nodos_activos = [n for n in G.nodes if G.nodes[n]["estado"] == "activo"]
518
-
519
- # Limpiar bifurcaciones obsoletas cada 300 pasos
520
- if paso % 300 == 0:
521
- obsoletas = limpiar_bifurcaciones_obsoletas(bifurcation_manager, paso)
522
-
523
- lecturas = interpretar_sintaxis_glífica(historial_glifos_por_nodo)
524
-
525
- # Diagnóstico simbólico final
526
- diagnostico = []
527
- for nodo in G.nodes:
528
- nombre = nodo
529
- datos = G.nodes[nodo]
530
- glifos_nodo = [g[1] for g in historial_glifos_por_nodo.get(nombre, [])]
531
- mutó = "ZHIR" in glifos_nodo
532
- en_epi = any(nombre in grupo["nodos"] for grupo in epi_compuestas)
533
- lectura = lecturas.get(nombre, {}).get("trayectoria", [])
534
-
535
- diagnostico.append({
536
- "palabra": nombre,
537
- "glifos": glifos_nodo,
538
- "lectura_sintactica": lectura,
539
- "mutó": mutó,
540
- "en_epi_compuesta": en_epi,
541
- "Si": datos.get("Si", 0),
542
- "estado": datos.get("estado", "latente"),
543
- "categoría": datos.get("categoria", "sin categoría")
544
- })
545
-
546
- nodos_pulsantes = detectar_nodos_pulsantes(historial_glifos_por_nodo)
547
-
548
- for nodo_id in nodos_pulsantes:
549
- nodo = G.nodes[nodo_id]
550
- historial = historial_glifos_por_nodo.get(nodo_id, [])
551
- ultimos = [g for _, g in historial][-6:]
552
-
553
- if nodo["glifo"] in ["THOL", "ZHIR", "REMESH"]:
554
- continue # ya está mutado o recursivo
555
-
556
- nodo = G.nodes[nodo_id]
557
-
558
- # Evaluar emergente canónico
559
- if abs(nodo["EPI"] - nodo["EPI_prev"]) < 0.01 and abs(nodo["ΔNFR"]) < 0.05:
560
- glifo = "REMESH"
561
- elif abs(nodo.get("θ", 0) - nodo.get("θ_prev", 0)) > 0.2:
562
- glifo = "ZHIR"
563
- elif nodo.get("Si", 0) > 0.8 and nodo.get("glifo") == "UM":
564
- glifo = "RA"
565
- else:
566
- glifo = "THOL"
567
-
568
- if nodo_id in G:
569
- promover_emergente(nodo_id, G, paso, historial_glifos_por_nodo, historia_glifos)
570
-
571
- bifurcation_stats = bifurcation_manager.obtener_estadisticas_bifurcacion()
572
- return historia_epi, G, epi_compuestas, lecturas, G_historia, historial_glifos_por_nodo, historial_temporal, bifurcation_stats
573
-
574
- def aplicar_contraccion_nul(nodo_id, G, paso, historial_glifos_por_nodo):
575
- nodo = G.nodes[nodo_id]
576
-
577
- condiciones = (
578
- nodo.get("Si", 1.0) < 0.3 and
579
- abs(nodo.get("ΔNFR", 0.0)) > 0.8 and
580
- nodo.get("estado") == "activo" and
581
- nodo.get("d2EPI_dt2", 0) < -0.05
582
- )
583
-
584
- if not condiciones:
585
- return False
586
-
587
- # Aplicar contracción resonante
588
- nodo["EPI"] = round(nodo["EPI"] * 0.7, 3)
589
- nodo["estado"] = "latente"
590
- nodo["glifo"] = "NUL"
591
- nodo["categoria"] = "contractivo"
592
-
593
- historial_glifos_por_nodo.setdefault(nodo_id, []).append((paso, "NUL"))
594
-
595
- return True
596
-
597
- def activar_val_si_estabilidad(nodo_id, G, paso, historial_glifos_por_nodo):
598
- nodo = G.nodes[nodo_id]
599
-
600
- # Restricción por sobreexpansión
601
- if nodo.get("expansiones_val", 0) >= 3:
602
- return None
603
-
604
- condiciones = (
605
- nodo.get("Si", 0) > 0.85 and
606
- abs(nodo.get("ΔNFR", 0)) < 0.2 and
607
- nodo.get("dEPI_dt", 0) > 0.18 and
608
- nodo.get("d2EPI_dt2", 0) > 0.2 and
609
- nodo.get("estado") == "activo"
610
- )
611
-
612
- if not condiciones:
613
- return None
614
-
615
- nuevo_id = f"{nodo_id}_VAL_{random.randint(1000, 9999)}"
616
- if nuevo_id in G:
617
- return None
618
-
619
- nuevo_nodo = {
620
- "EPI": round(nodo["EPI"] * random.uniform(1.0, 1.1), 3),
621
- "EPI_prev": nodo["EPI"],
622
- "EPI_prev2": nodo.get("EPI_prev", nodo["EPI"]),
623
- "EPI_prev3": nodo.get("EPI_prev2", nodo["EPI"]),
624
- "glifo": "VAL",
625
- "categoria": "expansivo",
626
- "estado": "activo",
627
- "νf": round(nodo["νf"] * random.uniform(1.0, 1.05), 3),
628
- "ΔNFR": round(nodo["ΔNFR"] * 0.9, 3),
629
- "θ": round(nodo["θ"] + random.uniform(-0.01, 0.01), 3),
630
- "Si": nodo["Si"] * 0.98,
631
- "historial_glifos": [(paso, "VAL")],
632
- "historial_vf": [(paso, nodo["νf"])],
633
- "historial_dNFR": [(paso, nodo["ΔNFR"])],
634
- "historial_dEPI_dt": [(paso, nodo.get("dEPI_dt", 0))],
635
- "historial_Si": [(paso, nodo["Si"])]
636
- }
637
-
638
- G.add_node(nuevo_id, **nuevo_nodo)
639
- G.add_edge(nodo_id, nuevo_id)
640
-
641
- historial_glifos_por_nodo.setdefault(nodo_id, []).append((paso, "VAL"))
642
- historial_glifos_por_nodo[nuevo_id] = [(paso, "VAL")]
643
-
644
- nodo["expansiones_val"] = nodo.get("expansiones_val", 0) + 1
645
-
646
- return nuevo_id
647
-
648
- def aplicar_remesh_grupal(G, historial_glifos_por_nodo):
649
- nodos_aplicados = set()
650
-
651
- for nodo_id in G.nodes:
652
- if nodo_id in nodos_aplicados:
653
- continue
654
-
655
- historial = historial_glifos_por_nodo.get(nodo_id, [])
656
- if len(historial) < 3:
657
- continue
658
-
659
- ultimos_glifos = [g for _, g in historial[-3:]]
660
- if len(set(ultimos_glifos)) != 1:
661
- continue
662
-
663
- glifo_recurrente = ultimos_glifos[0]
664
-
665
- vecinos = list(G.neighbors(nodo_id))
666
- grupo = [nodo_id]
667
-
668
- for v_id in vecinos:
669
- v_nodo = G.nodes[v_id]
670
- v_hist = historial_glifos_por_nodo.get(v_id, [])
671
- if len(v_hist) >= 3:
672
- if [g for _, g in v_hist[-3:]] == ultimos_glifos:
673
- if abs(v_nodo.get("θ", 0) - G.nodes[nodo_id].get("θ", 0)) < 0.1:
674
- if abs(v_nodo.get("EPI", 0) - v_nodo.get("EPI_prev", v_nodo.get("EPI", 0))) < 0.01:
675
- if v_nodo.get("ΔNFR", 1.0) < 0.2:
676
- grupo.append(v_id)
677
-
678
- if len(grupo) >= 3:
679
- for g_id in grupo:
680
- g_nodo = G.nodes[g_id]
681
- g_nodo["EPI_prev"] = g_nodo.get("EPI_prev", g_nodo["EPI"])
682
- g_nodo["EPI_prev2"] = g_nodo.get("EPI_prev2", g_nodo["EPI"])
683
- g_nodo["EPI"] = (g_nodo["EPI_prev"] + g_nodo["EPI_prev2"]) / 2
684
- g_nodo["Si"] *= 0.98
685
- g_nodo["νf"] *= 0.98
686
- g_nodo["ΔNFR"] *= 0.95
687
- g_nodo["glifo"] = "REMESH"
688
- ultimo_paso = historial_glifos_por_nodo[g_id][-1][0] if historial_glifos_por_nodo[g_id] else 0
689
- historial_glifos_por_nodo[g_id].append((ultimo_paso + 1, "REMESH"))
690
- nodos_aplicados.add(g_id)
691
-
692
- def cumple_condiciones_emergencia(forma_base, campo_coherencia):
693
- """
694
- Evalúa si una forma puede generar un NFR según criterios TNFR.
695
-
696
- Condiciones de emergencia nodal:
697
- 1. Frecuencia estructural mínima νf > 0.3
698
- 2. Coherencia interna suficiente (estructura no degenerada)
699
- 3. Acoplamiento posible con campo de coherencia
700
- """
701
- if not forma_base or len(forma_base) < 2:
702
- return False
703
-
704
- # Evaluar diversidad estructural interna
705
- diversidad = len(set(forma_base)) / len(forma_base)
706
- if diversidad < 0.3: # demasiado repetitivo
707
- return False
708
-
709
- # Evaluar potencial de frecuencia resonante
710
- freq_potencial = calcular_frecuencia_resonante(forma_base)
711
- if freq_potencial < 0.3: # frecuencia insuficiente para emergencia
712
- return False
713
-
714
- # Evaluar compatibilidad con campo de coherencia
715
- if campo_coherencia and len(campo_coherencia) > 0:
716
- coherencia_promedio = np.mean([nodo.get("EPI", 1.0) for nodo in campo_coherencia.values()])
717
- if coherencia_promedio > 0 and freq_potencial > coherencia_promedio * 2.5:
718
- return False # demasiado energético para el campo actual
719
-
720
- return True
721
-
722
- def evaluar_coherencia_estructural(forma_base):
723
- """
724
- Calcula EPI basado en estructura interna real según TNFR.
725
-
726
- Evalúa:
727
- - Simetría funcional de la forma
728
- - Estabilidad topológica interna
729
- - Resistencia a mutaciones
730
- """
731
- if not forma_base:
732
- return 1.0
733
-
734
- # Análisis de simetría funcional
735
- forma_norm = forma_base.lower()
736
- longitud = len(forma_norm)
737
-
738
- # Factor de simetría: evalúa patrones internos
739
- def calcular_simetria(s):
740
- centro = len(s) // 2
741
- if len(s) % 2 == 0:
742
- izq, der = s[:centro], s[centro:][::-1]
743
- else:
744
- izq, der = s[:centro], s[centro+1:][::-1]
745
-
746
- coincidencias = sum(1 for a, b in zip(izq, der) if a == b)
747
- return coincidencias / max(len(izq), 1)
748
-
749
- simetria = calcular_simetria(forma_norm)
750
-
751
- # Factor de diversidad estructural
752
- diversidad = len(set(forma_norm)) / longitud
753
-
754
- # Factor de estabilidad (resistencia a mutaciones puntuales)
755
- # Basado en la distribución de caracteres
756
- contador = Counter(forma_norm)
757
- entropia = -sum((freq/longitud) * np.log2(freq/longitud) for freq in contador.values())
758
- estabilidad = min(1.0, entropia / 3.0) # normalizada
759
-
760
- # Factor de coherencia por patrones vocálicos/consonánticos
761
- vocales = "aeiouáéíóúü"
762
- patron_vocal = sum(1 for c in forma_norm if c in vocales) / longitud
763
- coherencia_fonetica = min(1.0, abs(0.4 - patron_vocal) * 2.5) # óptimo cerca de 40% vocales
764
-
765
- # Combinar factores según pesos TNFR
766
- EPI = (
767
- 0.3 * simetria + # simetría estructural
768
- 0.25 * diversidad + # diversidad interna
769
- 0.25 * estabilidad + # resistencia mutacional
770
- 0.2 * coherencia_fonetica # coherencia fónica
771
- )
772
-
773
- # Normalizar al rango [0.5, 2.5] típico de EPIs
774
- EPI_normalizada = 0.5 + EPI * 2.0
775
-
776
- return round(EPI_normalizada, 3)
777
-
778
- def generar_matriz_coherencia(forma_base):
779
- """
780
- Crea matriz Wi(t) para evaluar estabilidad topológica interna.
781
-
782
- Modela subnodos internos como caracteres y sus acoplamientos.
783
- """
784
- if not forma_base or len(forma_base) < 2:
785
- return np.array([[1.0]])
786
-
787
- longitud = len(forma_base)
788
- Wi = np.zeros((longitud, longitud))
789
-
790
- # Acoplamiento entre caracteres adyacentes (fuerte)
791
- for i in range(longitud - 1):
792
- Wi[i][i+1] = Wi[i+1][i] = 0.8
793
-
794
- # Acoplamiento entre caracteres similares (débil)
795
- for i in range(longitud):
796
- for j in range(i+2, longitud):
797
- if forma_base[i].lower() == forma_base[j].lower():
798
- Wi[i][j] = Wi[j][i] = 0.3
799
-
800
- # Autocoherencia (diagonal)
801
- np.fill_diagonal(Wi, 1.0)
802
-
803
- # Normalizar filas para que sumen aproximadamente 1
804
- for i in range(longitud):
805
- suma_fila = np.sum(Wi[i])
806
- if suma_fila > 0:
807
- Wi[i] = Wi[i] / suma_fila
808
-
809
- return Wi
810
-
811
- def sincronizar_con_campo(campo_coherencia, νf_nodo):
812
- """
813
- Calcula fase del nodo respecto al campo de coherencia global.
814
-
815
- La fase determina si el nodo está sincronizado o en disonancia
816
- con el estado actual de la red.
817
- """
818
- if not campo_coherencia or len(campo_coherencia) == 0:
819
- return 0.0 # fase neutra si no hay campo
820
-
821
- # Calcular frecuencia promedio del campo
822
- frecuencias_campo = [nodo.get("νf", 1.0) for nodo in campo_coherencia.values()]
823
- freq_promedio_campo = np.mean(frecuencias_campo)
824
-
825
- # Calcular diferencia de fase basada en frecuencias
826
- diferencia_freq = abs(νf_nodo - freq_promedio_campo)
827
-
828
- # Convertir a fase: diferencias pequeñas = sincronización, grandes = disonancia
829
- if diferencia_freq < 0.1:
830
- fase = 0.0 # sincronización perfecta
831
- elif diferencia_freq < 0.3:
832
- fase = 0.25 # sincronización parcial
833
- elif diferencia_freq < 0.6:
834
- fase = 0.5 # neutral
835
- elif diferencia_freq < 1.0:
836
- fase = 0.75 # disonancia parcial
837
- else:
838
- fase = 1.0 # disonancia completa
839
-
840
- return round(fase, 3)
841
-
842
- def gestionar_conexiones_canonico(G, paso, historia_Ct):
843
- """
844
- Reemplaza la gestión manual de conexiones por sistema canónico TNFR.
845
- Esta función debe reemplazar el bloque de gestión de aristas en simular_emergencia().
846
- """
847
- # Calcular coherencia total actual
848
- if len(G.nodes) == 0:
849
- C_t = 0
850
- else:
851
- C_t = sum(G.nodes[n]["EPI"] for n in G.nodes) / len(G)
852
-
853
- # Calcular densidad nodal promedio
854
- densidad_promedio = sum(len(list(G.neighbors(n))) for n in G.nodes) / len(G.nodes) if G.nodes else 0
855
-
856
- # Detectar fase actual de simulación
857
- fase_actual = detectar_fase_simulacion(G, paso, historia_Ct)
858
-
859
- # Calcular umbrales dinámicos
860
- umbrales = calcular_umbrales_dinamicos(C_t, densidad_promedio, fase_actual)
861
-
862
- # Aplicar gestión de conexiones canónica
863
- estadisticas = aplicar_umbrales_dinamicos_conexiones(G, umbrales)
864
-
865
- return umbrales, estadisticas
866
-
867
- def detectar_fase_simulacion(G, paso_actual, historial_C_t, ventana=50):
868
- """
869
- Detecta la fase actual de la simulación para ajustar umbrales.
870
-
871
- Args:
872
- G: Grafo actual
873
- paso_actual: Paso de simulación actual
874
- historial_C_t: Historia de coherencia total [(paso, C_t), ...]
875
- ventana: Ventana de pasos para análisis de tendencias
876
-
877
- Returns:
878
- str: "emergencia", "estabilizacion", "bifurcacion"
879
- """
880
- if len(historial_C_t) < ventana:
881
- return "emergencia"
882
-
883
- # Analizar últimos valores de C(t)
884
- valores_recientes = [c_t for _, c_t in historial_C_t[-ventana:]]
885
-
886
- # Calcular variabilidad
887
- variabilidad = np.std(valores_recientes)
888
- tendencia = np.mean(valores_recientes[-10:]) - np.mean(valores_recientes[:10])
889
-
890
- # Contar nodos activos
891
- nodos_activos = sum(1 for n in G.nodes if G.nodes[n].get("estado") == "activo")
892
- fraccion_activa = nodos_activos / len(G.nodes) if G.nodes else 0
893
-
894
- # Lógica de clasificación
895
- if variabilidad > 0.3 and abs(tendencia) > 0.2:
896
- return "bifurcacion" # alta variabilidad y cambio direccional
897
- elif variabilidad < 0.05 and fraccion_activa > 0.6:
898
- return "estabilizacion" # baja variabilidad, muchos nodos activos
899
- else:
900
- return "emergencia" # estado por defecto
901
-
902
- def calcular_umbrales_dinamicos(C_t, densidad_nodal, fase_simulacion="emergencia"):
903
-
904
- # Factor de sensibilidad basado en desviación de C(t) del punto de equilibrio
905
- equilibrio_base = 1.0
906
- desviacion_C_t = abs(C_t - equilibrio_base)
907
-
908
- # Sensibilidad adaptativa: más restrictivo cuando C(t) está lejos del equilibrio
909
- sensibilidad = max(0.4, min(2.0, 1.0 + 0.8 * desviacion_C_t))
910
-
911
- # Factor de densidad: redes densas requieren umbrales más estrictos
912
- factor_densidad = max(0.7, min(1.5, 1.0 - 0.1 * (densidad_nodal - 3.0)))
913
-
914
- # Ajuste por fase de simulación
915
- multiplicadores_fase = {
916
- "emergencia": 1.2, # más tolerante para permitir emergencia inicial
917
- "estabilizacion": 0.8, # más restrictivo para consolidar estructuras
918
- "bifurcacion": 1.5 # muy tolerante para permitir reorganización
919
- }
920
-
921
- factor_fase = multiplicadores_fase.get(fase_simulacion, 1.0)
922
-
923
- # Cálculo de umbrales fundamentales
924
- sensibilidad_final = sensibilidad * factor_densidad * factor_fase
925
-
926
- return {
927
- # Umbrales de conexión (para crear/eliminar aristas)
928
- 'θ_conexion': 0.12 * sensibilidad_final,
929
- 'EPI_conexion': 1.8 * sensibilidad_final,
930
- 'νf_conexion': 0.2 * sensibilidad_final,
931
- 'Si_conexion': 0.25 * sensibilidad_final,
932
-
933
- # Umbrales críticos nodales
934
- 'θ_mutacion': 0.25 * sensibilidad_final, # para activar Z'HIR
935
- 'θ_colapso': 0.45 * sensibilidad_final, # para activar SH'A
936
- 'θ_autoorganizacion': 0.35 * sensibilidad_final, # para activar T'HOL
937
-
938
- # Límites de estabilidad estructural
939
- 'EPI_max_dinamico': max(2.5, C_t * 2.8), # límite superior adaptativo
940
- 'EPI_min_coherencia': max(0.4, C_t * 0.3), # límite inferior para coherencia
941
-
942
- # Umbrales de bifurcación estructural
943
- 'bifurcacion_aceleracion': 0.15 * sensibilidad_final,
944
- 'bifurcacion_gradiente': 0.8 * sensibilidad_final,
945
-
946
- # Metadatos de cálculo
947
- 'C_t_usado': C_t,
948
- 'sensibilidad_calculada': sensibilidad_final,
949
- 'factor_densidad': factor_densidad,
950
- 'fase': fase_simulacion
951
- }
952
-
953
- def calcular_umbrales_dinamicos(C_t, densidad_nodal, fase_simulacion="emergencia"):
954
-
955
- # Factor de sensibilidad basado en desviación de C(t) del punto de equilibrio
956
- equilibrio_base = 1.0
957
- desviacion_C_t = abs(C_t - equilibrio_base)
958
-
959
- # Sensibilidad adaptativa: más restrictivo cuando C(t) está lejos del equilibrio
960
- sensibilidad = max(0.4, min(2.0, 1.0 + 0.8 * desviacion_C_t))
961
-
962
- # Factor de densidad: redes densas requieren umbrales más estrictos
963
- factor_densidad = max(0.7, min(1.5, 1.0 - 0.1 * (densidad_nodal - 3.0)))
964
-
965
- # Ajuste por fase de simulación
966
- multiplicadores_fase = {
967
- "emergencia": 1.2, # más tolerante para permitir emergencia inicial
968
- "estabilizacion": 0.8, # más restrictivo para consolidar estructuras
969
- "bifurcacion": 1.5 # muy tolerante para permitir reorganización
970
- }
971
-
972
- factor_fase = multiplicadores_fase.get(fase_simulacion, 1.0)
973
-
974
- # Cálculo de umbrales fundamentales
975
- sensibilidad_final = sensibilidad * factor_densidad * factor_fase
976
-
977
- return {
978
- # Umbrales de conexión (para crear/eliminar aristas)
979
- 'θ_conexion': 0.12 * sensibilidad_final,
980
- 'EPI_conexion': 1.8 * sensibilidad_final,
981
- 'νf_conexion': 0.2 * sensibilidad_final,
982
- 'Si_conexion': 0.25 * sensibilidad_final,
983
-
984
- # Umbrales críticos nodales
985
- 'θ_mutacion': 0.25 * sensibilidad_final, # para activar Z'HIR
986
- 'θ_colapso': 0.45 * sensibilidad_final, # para activar SH'A
987
- 'θ_autoorganizacion': 0.35 * sensibilidad_final, # para activar T'HOL
988
-
989
- # Límites de estabilidad estructural
990
- 'EPI_max_dinamico': max(2.5, C_t * 2.8), # límite superior adaptativo
991
- 'EPI_min_coherencia': max(0.4, C_t * 0.3), # límite inferior para coherencia
992
-
993
- # Umbrales de bifurcación estructural
994
- 'bifurcacion_aceleracion': 0.15 * sensibilidad_final,
995
- 'bifurcacion_gradiente': 0.8 * sensibilidad_final,
996
-
997
- # Metadatos de cálculo
998
- 'C_t_usado': C_t,
999
- 'sensibilidad_calculada': sensibilidad_final,
1000
- 'factor_densidad': factor_densidad,
1001
- 'fase': fase_simulacion
1002
- }
1003
-
1004
- def aplicar_umbrales_dinamicos_conexiones(G, umbrales):
1005
- """
1006
- Aplica umbrales dinámicos para gestión de conexiones de red.
1007
-
1008
- Args:
1009
- G: Grafo de red
1010
- umbrales: Umbrales calculados dinámicamente
1011
-
1012
- Returns:
1013
- dict: Estadísticas de conexiones creadas/eliminadas
1014
- """
1015
- conexiones_creadas = 0
1016
- conexiones_eliminadas = 0
1017
- nodos_lista = list(G.nodes)
1018
-
1019
- for i in range(len(nodos_lista)):
1020
- for j in range(i + 1, len(nodos_lista)):
1021
- n1, n2 = nodos_lista[i], nodos_lista[j]
1022
- nodo1, nodo2 = G.nodes[n1], G.nodes[n2]
1023
-
1024
- # Evaluar condiciones de resonancia con umbrales dinámicos
1025
- condiciones_resonancia = [
1026
- abs(nodo1.get("θ", 0) - nodo2.get("θ", 0)) < umbrales['θ_conexion'],
1027
- abs(nodo1.get("EPI", 0) - nodo2.get("EPI", 0)) < umbrales['EPI_conexion'],
1028
- abs(nodo1.get("νf", 1) - nodo2.get("νf", 1)) < umbrales['νf_conexion'],
1029
- abs(nodo1.get("Si", 0) - nodo2.get("Si", 0)) < umbrales['Si_conexion']
1030
- ]
1031
-
1032
- # Criterio: al menos 3 de 4 condiciones cumplidas
1033
- resonancia_suficiente = sum(condiciones_resonancia) >= 3
1034
-
1035
- # Verificar saturación de conexiones
1036
- vecinos_n1 = len(list(G.neighbors(n1)))
1037
- vecinos_n2 = len(list(G.neighbors(n2)))
1038
- max_conexiones = int(8 * umbrales['sensibilidad_calculada'])
1039
-
1040
- saturacion = vecinos_n1 >= max_conexiones and vecinos_n2 >= max_conexiones
1041
-
1042
- # Lógica de conexión/desconexión
1043
- existe_conexion = G.has_edge(n1, n2)
1044
-
1045
- if resonancia_suficiente and not saturacion and not existe_conexion:
1046
- G.add_edge(n1, n2)
1047
- conexiones_creadas += 1
1048
- elif not resonancia_suficiente and existe_conexion:
1049
- G.remove_edge(n1, n2)
1050
- conexiones_eliminadas += 1
1051
-
1052
- return {
1053
- 'conexiones_creadas': conexiones_creadas,
1054
- 'conexiones_eliminadas': conexiones_eliminadas,
1055
- 'umbrales_usados': umbrales
1056
- }
1057
-
1058
- __all__ = [
1059
- 'inicializar_nfr_emergente',
1060
- '_deben_conectarse_canonico',
1061
- 'calcular_frecuencia_resonante',
1062
- 'crear_red_desde_datos',
1063
- 'simular_emergencia',
1064
- 'aplicar_contraccion_nul',
1065
- 'activar_val_si_estabilidad',
1066
- 'aplicar_remesh_grupal',
1067
- 'cumple_condiciones_emergencia',
1068
- 'evaluar_coherencia_estructural',
1069
- 'generar_matriz_coherencia',
1070
- 'sincronizar_con_campo',
1071
- 'gestionar_conexiones_canonico',
1072
- 'calcular_umbrales_dinamicos',
1073
- 'aplicar_umbrales_dinamicos_conexiones'
1074
- ]