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/__init__.py +45 -31
- tnfr/constants.py +183 -7
- tnfr/dynamics.py +543 -0
- tnfr/helpers.py +198 -0
- tnfr/main.py +37 -0
- tnfr/observers.py +149 -0
- tnfr/ontosim.py +137 -0
- tnfr/operators.py +296 -0
- tnfr-3.0.0.dist-info/METADATA +35 -0
- tnfr-3.0.0.dist-info/RECORD +13 -0
- tnfr-3.0.0.dist-info/top_level.txt +1 -0
- core/ontosim.py +0 -757
- matrix/operators.py +0 -496
- tnfr/core/ontosim.py +0 -1074
- tnfr/matrix/operators.py +0 -500
- tnfr/resonance/dynamics.py +0 -1305
- tnfr/utils/helpers.py +0 -357
- tnfr-1.0.dist-info/METADATA +0 -95
- tnfr-1.0.dist-info/RECORD +0 -14
- tnfr-1.0.dist-info/entry_points.txt +0 -2
- tnfr-1.0.dist-info/top_level.txt +0 -3
- {tnfr-1.0.dist-info → tnfr-3.0.0.dist-info}/WHEEL +0 -0
- {tnfr-1.0.dist-info → tnfr-3.0.0.dist-info}/licenses/LICENSE.txt +0 -0
tnfr/resonance/dynamics.py
DELETED
|
@@ -1,1305 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from collections import defaultdict
|
|
3
|
-
from typing import List, Dict, Optional
|
|
4
|
-
import networkx as nx
|
|
5
|
-
import numpy as np
|
|
6
|
-
import random
|
|
7
|
-
|
|
8
|
-
# GESTIÓN TEMPORAL TOPOLÓGICA TNFR #
|
|
9
|
-
|
|
10
|
-
class TemporalCoordinatorTNFR:
|
|
11
|
-
"""
|
|
12
|
-
Coordinador temporal canónico que gestiona tiempo topológico variable
|
|
13
|
-
según frecuencias estructurales νf de cada NFR y principios de entrainment.
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
def __init__(self, sincronizacion_global=True, pulsos_reorganizacion=50):
|
|
17
|
-
# Configuración temporal canónica
|
|
18
|
-
self.sincronizacion_global = sincronizacion_global
|
|
19
|
-
self.frecuencia_pulsos = pulsos_reorganizacion
|
|
20
|
-
self.tiempo_topologico = 0.0
|
|
21
|
-
|
|
22
|
-
# Estados temporales de nodos
|
|
23
|
-
self.cronometros_nodales = {} # tiempo local de cada nodo
|
|
24
|
-
self.fases_sincronizacion = {} # fase temporal de cada nodo
|
|
25
|
-
self.ultimas_activaciones = {} # última activación de cada nodo
|
|
26
|
-
|
|
27
|
-
# Historial de sincronización
|
|
28
|
-
self.historial_entrainment = []
|
|
29
|
-
self.historial_coherencia_temporal = []
|
|
30
|
-
|
|
31
|
-
# Cola de eventos temporales
|
|
32
|
-
self.cola_eventos = [] # [(tiempo_activacion, nodo_id, tipo_evento)]
|
|
33
|
-
|
|
34
|
-
# Parámetros de resonancia temporal
|
|
35
|
-
self.umbral_resonancia = 0.15 # diferencia máxima en νf para resonancia
|
|
36
|
-
self.factor_aceleracion = 1.8 # aceleración temporal por coherencia alta
|
|
37
|
-
|
|
38
|
-
def calcular_paso_temporal_nodal(self, nodo, paso_global):
|
|
39
|
-
# Validación preventiva de parámetros
|
|
40
|
-
vf_nodo = nodo.get("νf", 1.0)
|
|
41
|
-
if not np.isfinite(vf_nodo) or vf_nodo <= 0:
|
|
42
|
-
vf_nodo = 1.0
|
|
43
|
-
nodo["νf"] = 1.0 # Corrección in-situ
|
|
44
|
-
|
|
45
|
-
Si_nodo = nodo.get("Si", 0.5)
|
|
46
|
-
if not np.isfinite(Si_nodo):
|
|
47
|
-
Si_nodo = 0.5
|
|
48
|
-
nodo["Si"] = 0.5
|
|
49
|
-
|
|
50
|
-
vf_nodo = nodo.get("νf", 1.0)
|
|
51
|
-
Si_nodo = nodo.get("Si", 0.5)
|
|
52
|
-
theta_nodo = nodo.get("θ", 0.5)
|
|
53
|
-
estado = nodo.get("estado", "latente")
|
|
54
|
-
|
|
55
|
-
# Paso base según frecuencia estructural (inversa de νf)
|
|
56
|
-
# Alta frecuencia = pasos más pequeños (más actividad)
|
|
57
|
-
paso_base = 1.0 / max(0.1, vf_nodo)
|
|
58
|
-
|
|
59
|
-
# Factor de coherencia: mayor Si permite pasos más largos (estabilidad)
|
|
60
|
-
factor_coherencia = 0.5 + 0.5 * Si_nodo
|
|
61
|
-
|
|
62
|
-
# Factor de activación: nodos activos necesitan pasos más pequeños
|
|
63
|
-
factor_activacion = {
|
|
64
|
-
"activo": 0.7, # pasos más pequeños para actividad
|
|
65
|
-
"latente": 1.0, # pasos normales
|
|
66
|
-
"silencio": 1.5, # pasos más grandes en silencio
|
|
67
|
-
"inactivo": 2.0 # pasos muy grandes si inactivo
|
|
68
|
-
}.get(estado, 1.0)
|
|
69
|
-
|
|
70
|
-
# Factor de umbral: cerca de bifurcación = pasos pequeños
|
|
71
|
-
factor_umbral = 1.0 - 0.3 * min(1.0, theta_nodo)
|
|
72
|
-
|
|
73
|
-
# Combinar todos los factores
|
|
74
|
-
paso_temporal = paso_base * factor_coherencia * factor_activacion * factor_umbral
|
|
75
|
-
|
|
76
|
-
# Limitar al rango [0.1, 5.0] para evitar extremos
|
|
77
|
-
paso_temporal = max(0.1, min(5.0, paso_temporal))
|
|
78
|
-
|
|
79
|
-
return paso_temporal
|
|
80
|
-
|
|
81
|
-
def detectar_nodos_resonantes(self, G):
|
|
82
|
-
"""
|
|
83
|
-
Detecta grupos de nodos con frecuencias νf compatibles para entrainment.
|
|
84
|
-
"""
|
|
85
|
-
nodos_por_frecuencia = defaultdict(list)
|
|
86
|
-
|
|
87
|
-
# Agrupar nodos por bandas de frecuencia
|
|
88
|
-
for nodo_id, nodo_data in G.nodes(data=True):
|
|
89
|
-
vf = nodo_data.get("νf", 1.0)
|
|
90
|
-
if np.isfinite(vf) and vf > 0 and np.isfinite(self.umbral_resonancia):
|
|
91
|
-
try:
|
|
92
|
-
ratio = vf / self.umbral_resonancia
|
|
93
|
-
if np.isfinite(ratio) and abs(ratio) < 1e6: # Límite de seguridad
|
|
94
|
-
banda_freq = round(ratio) * self.umbral_resonancia
|
|
95
|
-
else:
|
|
96
|
-
banda_freq = self.umbral_resonancia # Valor por defecto seguro
|
|
97
|
-
except (ValueError, OverflowError):
|
|
98
|
-
banda_freq = self.umbral_resonancia
|
|
99
|
-
else:
|
|
100
|
-
banda_freq = self.umbral_resonancia # Manejo de casos inválidos
|
|
101
|
-
nodos_por_frecuencia[banda_freq].append(nodo_id)
|
|
102
|
-
|
|
103
|
-
# Identificar grupos resonantes (2+ nodos en misma banda)
|
|
104
|
-
grupos_resonantes = []
|
|
105
|
-
for banda, nodos in nodos_por_frecuencia.items():
|
|
106
|
-
if len(nodos) >= 2:
|
|
107
|
-
# Verificar coherencia estructural dentro del grupo
|
|
108
|
-
coherencias = [G.nodes[n].get("Si", 0) for n in nodos]
|
|
109
|
-
if np.mean(coherencias) > 0.4: # grupo coherente
|
|
110
|
-
grupos_resonantes.append({
|
|
111
|
-
'banda_frecuencia': banda,
|
|
112
|
-
'nodos': nodos,
|
|
113
|
-
'coherencia_grupal': np.mean(coherencias),
|
|
114
|
-
'tamaño': len(nodos)
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
return grupos_resonantes
|
|
118
|
-
|
|
119
|
-
def sincronizar_grupo_resonante(self, G, grupo):
|
|
120
|
-
"""
|
|
121
|
-
Sincroniza temporalmente un grupo de nodos resonantes mediante entrainment.
|
|
122
|
-
"""
|
|
123
|
-
nodos = grupo['nodos']
|
|
124
|
-
banda_freq = grupo['banda_frecuencia']
|
|
125
|
-
|
|
126
|
-
# Calcular fase de sincronización grupal
|
|
127
|
-
fases_actuales = [self.fases_sincronizacion.get(n, 0.0) for n in nodos]
|
|
128
|
-
fase_promedio = np.mean(fases_actuales)
|
|
129
|
-
|
|
130
|
-
# Factor de atracción hacia sincronización
|
|
131
|
-
for nodo_id in nodos:
|
|
132
|
-
nodo = G.nodes[nodo_id]
|
|
133
|
-
fase_actual = self.fases_sincronizacion.get(nodo_id, 0.0)
|
|
134
|
-
|
|
135
|
-
# Calcular corrección de fase hacia el promedio grupal
|
|
136
|
-
diferencia_fase = fase_promedio - fase_actual
|
|
137
|
-
factor_correccion = 0.1 * nodo.get("Si", 0.5) # más Si = más atraído
|
|
138
|
-
|
|
139
|
-
# Aplicar corrección suave
|
|
140
|
-
nueva_fase = fase_actual + factor_correccion * diferencia_fase
|
|
141
|
-
self.fases_sincronizacion[nodo_id] = nueva_fase % (2 * np.pi)
|
|
142
|
-
|
|
143
|
-
# Ajustar cronómetro nodal para sincronización
|
|
144
|
-
ajuste_temporal = np.sin(diferencia_fase) * 0.05
|
|
145
|
-
cronometro_actual = self.cronometros_nodales.get(nodo_id, 0.0)
|
|
146
|
-
self.cronometros_nodales[nodo_id] = cronometro_actual + ajuste_temporal
|
|
147
|
-
|
|
148
|
-
return len(nodos) # cantidad de nodos sincronizados
|
|
149
|
-
|
|
150
|
-
def generar_pulso_reorganizacion_global(self, G, paso_global):
|
|
151
|
-
"""
|
|
152
|
-
Genera pulso de reorganización global que sincroniza toda la red.
|
|
153
|
-
"""
|
|
154
|
-
if paso_global % self.frecuencia_pulsos != 0:
|
|
155
|
-
return False
|
|
156
|
-
|
|
157
|
-
# Calcular coherencia global actual
|
|
158
|
-
EPIs = [G.nodes[n].get("EPI", 1.0) for n in G.nodes]
|
|
159
|
-
coherencia_global = np.mean(EPIs)
|
|
160
|
-
|
|
161
|
-
# Intensidad del pulso basada en necesidad de reorganización
|
|
162
|
-
variabilidad_EPI = np.std(EPIs)
|
|
163
|
-
intensidad_pulso = min(1.0, variabilidad_EPI / coherencia_global)
|
|
164
|
-
|
|
165
|
-
# Aplicar pulso a todos los nodos
|
|
166
|
-
nodos_afectados = 0
|
|
167
|
-
for nodo_id, nodo_data in G.nodes(data=True):
|
|
168
|
-
# Reset parcial del cronómetro según intensidad
|
|
169
|
-
cronometro_actual = self.cronometros_nodales.get(nodo_id, 0.0)
|
|
170
|
-
ajuste = intensidad_pulso * 0.2 * random.uniform(-1, 1)
|
|
171
|
-
self.cronometros_nodales[nodo_id] = cronometro_actual + ajuste
|
|
172
|
-
|
|
173
|
-
# Sincronizar fases hacia coherencia global
|
|
174
|
-
fase_actual = self.fases_sincronizacion.get(nodo_id, 0.0)
|
|
175
|
-
# Fase objetivo basada en frecuencia del nodo
|
|
176
|
-
vf = nodo_data.get("νf", 1.0)
|
|
177
|
-
tiempo_normalizado = self.tiempo_topologico % (4 * np.pi) # Ciclo de normalización
|
|
178
|
-
if np.isfinite(vf) and np.isfinite(tiempo_normalizado):
|
|
179
|
-
fase_objetivo = (vf * tiempo_normalizado) % (2 * np.pi)
|
|
180
|
-
else:
|
|
181
|
-
fase_objetivo = 0.0 # Valor seguro por defecto
|
|
182
|
-
diferencia = fase_objetivo - fase_actual
|
|
183
|
-
self.fases_sincronizacion[nodo_id] = fase_actual + 0.3 * diferencia
|
|
184
|
-
|
|
185
|
-
nodos_afectados += 1
|
|
186
|
-
|
|
187
|
-
return True
|
|
188
|
-
|
|
189
|
-
def calcular_simultaneidad_eventos(self, G, eventos_candidatos):
|
|
190
|
-
"""
|
|
191
|
-
Determina qué eventos pueden ocurrir simultáneamente basado en coherencia.
|
|
192
|
-
"""
|
|
193
|
-
if len(eventos_candidatos) <= 1:
|
|
194
|
-
return eventos_candidatos
|
|
195
|
-
|
|
196
|
-
eventos_simultaneos = []
|
|
197
|
-
eventos_procesados = set()
|
|
198
|
-
|
|
199
|
-
for i, (tiempo_i, nodo_i, evento_i) in enumerate(eventos_candidatos):
|
|
200
|
-
if i in eventos_procesados:
|
|
201
|
-
continue
|
|
202
|
-
|
|
203
|
-
grupo_simultaneo = [(tiempo_i, nodo_i, evento_i)]
|
|
204
|
-
eventos_procesados.add(i)
|
|
205
|
-
|
|
206
|
-
# Buscar eventos compatibles para simultaneidad
|
|
207
|
-
for j, (tiempo_j, nodo_j, evento_j) in enumerate(eventos_candidatos[i+1:], i+1):
|
|
208
|
-
if j in eventos_procesados:
|
|
209
|
-
continue
|
|
210
|
-
|
|
211
|
-
# Verificar criterios de simultaneidad
|
|
212
|
-
diferencia_temporal = abs(tiempo_i - tiempo_j)
|
|
213
|
-
if diferencia_temporal > 0.1: # demasiado separados en tiempo
|
|
214
|
-
continue
|
|
215
|
-
|
|
216
|
-
# Verificar coherencia estructural entre nodos
|
|
217
|
-
nodo_data_i = G.nodes[nodo_i]
|
|
218
|
-
nodo_data_j = G.nodes[nodo_j]
|
|
219
|
-
|
|
220
|
-
diferencia_vf = abs(nodo_data_i.get("νf", 1) - nodo_data_j.get("νf", 1))
|
|
221
|
-
diferencia_Si = abs(nodo_data_i.get("Si", 0) - nodo_data_j.get("Si", 0))
|
|
222
|
-
|
|
223
|
-
# Criterios de compatibilidad para simultaneidad
|
|
224
|
-
if (diferencia_vf < self.umbral_resonancia and
|
|
225
|
-
diferencia_Si < 0.3 and
|
|
226
|
-
len(grupo_simultaneo) < 5): # máximo 5 eventos simultáneos
|
|
227
|
-
|
|
228
|
-
grupo_simultaneo.append((tiempo_j, nodo_j, evento_j))
|
|
229
|
-
eventos_procesados.add(j)
|
|
230
|
-
|
|
231
|
-
eventos_simultaneos.append(grupo_simultaneo)
|
|
232
|
-
|
|
233
|
-
return eventos_simultaneos
|
|
234
|
-
|
|
235
|
-
def avanzar_tiempo_topologico(self, G, paso_global):
|
|
236
|
-
"""
|
|
237
|
-
Función principal que avanza el tiempo topológico de la red.
|
|
238
|
-
"""
|
|
239
|
-
eventos_este_paso = []
|
|
240
|
-
grupos_resonantes = self.detectar_nodos_resonantes(G)
|
|
241
|
-
|
|
242
|
-
if self.tiempo_topologico > 1e6 or not np.isfinite(self.tiempo_topologico):
|
|
243
|
-
self.tiempo_topologico = self.tiempo_topologico % (8 * np.pi) # Normalizar
|
|
244
|
-
if not np.isfinite(self.tiempo_topologico):
|
|
245
|
-
self.tiempo_topologico = 0.0 # Reset completo si persiste NaN
|
|
246
|
-
|
|
247
|
-
# Procesar cada nodo con su tiempo topológico individual
|
|
248
|
-
for nodo_id, nodo_data in G.nodes(data=True):
|
|
249
|
-
# Inicializar cronómetro si es necesario
|
|
250
|
-
if nodo_id not in self.cronometros_nodales:
|
|
251
|
-
self.cronometros_nodales[nodo_id] = 0.0
|
|
252
|
-
self.fases_sincronizacion[nodo_id] = random.uniform(0, 2*np.pi)
|
|
253
|
-
|
|
254
|
-
# Calcular paso temporal para este nodo
|
|
255
|
-
paso_nodal = self.calcular_paso_temporal_nodal(nodo_data, paso_global)
|
|
256
|
-
|
|
257
|
-
# Avanzar cronómetro nodal
|
|
258
|
-
self.cronometros_nodales[nodo_id] += paso_nodal
|
|
259
|
-
|
|
260
|
-
# Actualizar fase de sincronización
|
|
261
|
-
vf = nodo_data.get("νf", 1.0)
|
|
262
|
-
incremento_fase = 2 * np.pi * paso_nodal * vf
|
|
263
|
-
self.fases_sincronizacion[nodo_id] = (self.fases_sincronizacion[nodo_id] + incremento_fase) % (2 * np.pi)
|
|
264
|
-
|
|
265
|
-
# Verificar si el nodo debe activarse en este paso
|
|
266
|
-
tiempo_desde_activacion = self.cronometros_nodales[nodo_id] - self.ultimas_activaciones.get(nodo_id, 0)
|
|
267
|
-
|
|
268
|
-
# Umbral de activación basado en frecuencia y fase
|
|
269
|
-
umbral_activacion = 1.0 / max(0.1, vf) # período de activación
|
|
270
|
-
|
|
271
|
-
if tiempo_desde_activacion >= umbral_activacion:
|
|
272
|
-
eventos_este_paso.append((self.cronometros_nodales[nodo_id], nodo_id, "activacion_temporal"))
|
|
273
|
-
self.ultimas_activaciones[nodo_id] = self.cronometros_nodales[nodo_id]
|
|
274
|
-
|
|
275
|
-
# Control de desbordamiento de cronómetros
|
|
276
|
-
for nodo_id in self.cronometros_nodales:
|
|
277
|
-
if self.cronometros_nodales[nodo_id] > 1e4:
|
|
278
|
-
self.cronometros_nodales[nodo_id] = self.cronometros_nodales[nodo_id] % 100.0
|
|
279
|
-
|
|
280
|
-
# Sincronizar grupos resonantes
|
|
281
|
-
nodos_sincronizados = 0
|
|
282
|
-
for grupo in grupos_resonantes:
|
|
283
|
-
nodos_sincronizados += self.sincronizar_grupo_resonante(G, grupo)
|
|
284
|
-
|
|
285
|
-
# Generar pulso de reorganización global si corresponde
|
|
286
|
-
pulso_global = self.generar_pulso_reorganizacion_global(G, paso_global)
|
|
287
|
-
|
|
288
|
-
# Calcular eventos simultáneos
|
|
289
|
-
grupos_simultaneos = self.calcular_simultaneidad_eventos(G, eventos_este_paso)
|
|
290
|
-
|
|
291
|
-
# Avanzar tiempo topológico global
|
|
292
|
-
incremento_global = np.mean([self.calcular_paso_temporal_nodal(G.nodes[n], paso_global) for n in G.nodes])
|
|
293
|
-
self.tiempo_topologico += incremento_global
|
|
294
|
-
|
|
295
|
-
# Registrar estadísticas temporales
|
|
296
|
-
coherencia_temporal = self.calcular_coherencia_temporal(G)
|
|
297
|
-
self.historial_coherencia_temporal.append((paso_global, coherencia_temporal))
|
|
298
|
-
|
|
299
|
-
# Registrar información de entrainment
|
|
300
|
-
self.historial_entrainment.append({
|
|
301
|
-
'paso': paso_global,
|
|
302
|
-
'grupos_resonantes': len(grupos_resonantes),
|
|
303
|
-
'nodos_sincronizados': nodos_sincronizados,
|
|
304
|
-
'eventos_simultaneos': len([g for g in grupos_simultaneos if len(g) > 1]),
|
|
305
|
-
'pulso_global': pulso_global,
|
|
306
|
-
'coherencia_temporal': coherencia_temporal
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
return {
|
|
310
|
-
'tiempo_topologico': self.tiempo_topologico,
|
|
311
|
-
'grupos_resonantes': grupos_resonantes,
|
|
312
|
-
'eventos_simultaneos': grupos_simultaneos,
|
|
313
|
-
'estadisticas': self.historial_entrainment[-1]
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
def calcular_coherencia_temporal(self, G):
|
|
317
|
-
"""
|
|
318
|
-
Calcula la coherencia temporal global de la red.
|
|
319
|
-
"""
|
|
320
|
-
if len(G.nodes) == 0:
|
|
321
|
-
return 0.0
|
|
322
|
-
|
|
323
|
-
# Coherencia basada en sincronización de fases
|
|
324
|
-
fases = [self.fases_sincronizacion.get(n, 0) for n in G.nodes]
|
|
325
|
-
|
|
326
|
-
# Calcular parámetro de orden de Kuramoto
|
|
327
|
-
suma_compleja = sum(np.exp(1j * fase) for fase in fases)
|
|
328
|
-
parametro_orden = abs(suma_compleja) / len(fases)
|
|
329
|
-
|
|
330
|
-
# Coherencia basada en distribución de cronómetros
|
|
331
|
-
cronometros = [self.cronometros_nodales.get(n, 0) for n in G.nodes]
|
|
332
|
-
variabilidad_cronometros = np.std(cronometros) / (np.mean(cronometros) + 0.1)
|
|
333
|
-
coherencia_cronometros = 1.0 / (1.0 + variabilidad_cronometros)
|
|
334
|
-
|
|
335
|
-
# Combinar ambas métricas
|
|
336
|
-
coherencia_temporal = 0.6 * parametro_orden + 0.4 * coherencia_cronometros
|
|
337
|
-
|
|
338
|
-
return coherencia_temporal
|
|
339
|
-
|
|
340
|
-
def inicializar_coordinador_temporal_canonico():
|
|
341
|
-
"""
|
|
342
|
-
Inicializa el coordinador temporal canónico para OntoSim.
|
|
343
|
-
"""
|
|
344
|
-
return TemporalCoordinatorTNFR(
|
|
345
|
-
sincronizacion_global=True,
|
|
346
|
-
pulsos_reorganizacion=75 # pulso cada 75 pasos
|
|
347
|
-
)
|
|
348
|
-
|
|
349
|
-
def integrar_tiempo_topologico_en_simulacion(G, paso, coordinador_temporal):
|
|
350
|
-
"""
|
|
351
|
-
Función de integración que debe llamarse en cada paso de simular_emergencia().
|
|
352
|
-
Reemplaza la gestión temporal lineal por tiempo topológico canónico.
|
|
353
|
-
"""
|
|
354
|
-
resultado_temporal = coordinador_temporal.avanzar_tiempo_topologico(G, paso)
|
|
355
|
-
|
|
356
|
-
# Aplicar efectos temporales a los nodos
|
|
357
|
-
for nodo_id, nodo_data in G.nodes(data=True):
|
|
358
|
-
# Obtener información temporal del nodo
|
|
359
|
-
cronometro = coordinador_temporal.cronometros_nodales.get(nodo_id, 0)
|
|
360
|
-
fase = coordinador_temporal.fases_sincronizacion.get(nodo_id, 0)
|
|
361
|
-
|
|
362
|
-
# Modular parámetros TNFR según tiempo topológico
|
|
363
|
-
modulacion_temporal = 1.0 + 0.1 * np.sin(fase) # modulación suave
|
|
364
|
-
|
|
365
|
-
# Aplicar modulación a νf (retroalimentación temporal)
|
|
366
|
-
vf_actual = nodo_data.get("νf", 1.0)
|
|
367
|
-
nodo_data["νf"] = vf_actual * modulacion_temporal
|
|
368
|
-
|
|
369
|
-
# Registrar información temporal en el nodo
|
|
370
|
-
nodo_data["cronometro_topologico"] = cronometro
|
|
371
|
-
nodo_data["fase_temporal"] = fase
|
|
372
|
-
nodo_data["ultima_sincronizacion"] = paso
|
|
373
|
-
|
|
374
|
-
return resultado_temporal
|
|
375
|
-
|
|
376
|
-
# Sistema de Bifurcaciones Estructurales Múltiples
|
|
377
|
-
|
|
378
|
-
# Clase para representar una trayectoria de bifurcación
|
|
379
|
-
@dataclass
|
|
380
|
-
class TrayectoriaBifurcacion:
|
|
381
|
-
"""Representa una trayectoria específica en una bifurcación estructural"""
|
|
382
|
-
id: str
|
|
383
|
-
tipo: str
|
|
384
|
-
secuencia_glifica: List[str]
|
|
385
|
-
parametros_iniciales: Dict[str, float]
|
|
386
|
-
viabilidad: float = 1.0
|
|
387
|
-
pasos_completados: int = 0
|
|
388
|
-
activa: bool = True
|
|
389
|
-
convergencia_objetivo: Optional[str] = None
|
|
390
|
-
|
|
391
|
-
# Clase para gestionar espacios de bifurcación
|
|
392
|
-
@dataclass
|
|
393
|
-
class EspacioBifurcacion:
|
|
394
|
-
"""Representa el espacio completo de una bifurcación con múltiples trayectorias"""
|
|
395
|
-
nodo_origen_id: str
|
|
396
|
-
tipo_bifurcacion: str
|
|
397
|
-
trayectorias: List[TrayectoriaBifurcacion]
|
|
398
|
-
paso_inicio: int
|
|
399
|
-
pasos_exploracion: int = 10
|
|
400
|
-
convergencias_detectadas: List[Dict] = None
|
|
401
|
-
|
|
402
|
-
def __post_init__(self):
|
|
403
|
-
if self.convergencias_detectadas is None:
|
|
404
|
-
self.convergencias_detectadas = []
|
|
405
|
-
|
|
406
|
-
# Gestor principal de bifurcaciones TNFR
|
|
407
|
-
class BifurcationManagerTNFR:
|
|
408
|
-
"""Gestor canónico de bifurcaciones estructurales múltiples según principios TNFR"""
|
|
409
|
-
|
|
410
|
-
def __init__(self):
|
|
411
|
-
self.bifurcaciones_activas = {} # {nodo_id: EspacioBifurcacion}
|
|
412
|
-
self.trayectorias_exploradas = []
|
|
413
|
-
self.convergencias_detectadas = []
|
|
414
|
-
self.estadisticas_bifurcacion = {
|
|
415
|
-
'total_bifurcaciones': 0,
|
|
416
|
-
'bifurcaciones_simetricas': 0,
|
|
417
|
-
'bifurcaciones_disonantes': 0,
|
|
418
|
-
'bifurcaciones_fractales': 0,
|
|
419
|
-
'convergencias_exitosas': 0,
|
|
420
|
-
'trayectorias_colapsadas': 0
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
def detectar_bifurcacion_canonica(self, nodo, nodo_id, umbral_aceleracion=0.15):
|
|
424
|
-
"""Detecta si un nodo está en condiciones de bifurcación canónica TNFR"""
|
|
425
|
-
try:
|
|
426
|
-
# Métricas de aceleración estructural
|
|
427
|
-
aceleracion = abs(nodo.get("d2EPI_dt2", 0))
|
|
428
|
-
gradiente = abs(nodo.get("ΔNFR", 0))
|
|
429
|
-
coherencia = nodo.get("Si", 0)
|
|
430
|
-
energia = nodo.get("EPI", 0)
|
|
431
|
-
frecuencia = nodo.get("νf", 1.0)
|
|
432
|
-
|
|
433
|
-
# Validación de valores numéricos
|
|
434
|
-
if not all(np.isfinite([aceleracion, gradiente, coherencia, energia, frecuencia])):
|
|
435
|
-
return False, "valores_no_finitos"
|
|
436
|
-
|
|
437
|
-
# Condiciones múltiples para bifurcación canónica
|
|
438
|
-
condiciones = {
|
|
439
|
-
'aceleracion_critica': aceleracion > umbral_aceleracion,
|
|
440
|
-
'gradiente_alto': gradiente > 0.8,
|
|
441
|
-
'coherencia_suficiente': coherencia > 0.4,
|
|
442
|
-
'energia_minima': energia > 1.2,
|
|
443
|
-
'frecuencia_activa': frecuencia > 0.8
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
# Evaluación de umbral de bifurcación
|
|
447
|
-
condiciones_cumplidas = sum(condiciones.values())
|
|
448
|
-
umbral_minimo = 3 # Al menos 3 de 5 condiciones
|
|
449
|
-
|
|
450
|
-
# Determinación del tipo de bifurcación según las condiciones
|
|
451
|
-
tipo_bifurcacion = self._determinar_tipo_bifurcacion(nodo, condiciones)
|
|
452
|
-
|
|
453
|
-
es_bifurcacion = condiciones_cumplidas >= umbral_minimo
|
|
454
|
-
return es_bifurcacion, tipo_bifurcacion
|
|
455
|
-
|
|
456
|
-
except Exception as e:
|
|
457
|
-
return False, "error_deteccion"
|
|
458
|
-
|
|
459
|
-
def _determinar_tipo_bifurcacion(self, nodo, condiciones):
|
|
460
|
-
"""Determina el tipo de bifurcación según las condiciones estructurales"""
|
|
461
|
-
aceleracion = abs(nodo.get("d2EPI_dt2", 0))
|
|
462
|
-
coherencia = nodo.get("Si", 0)
|
|
463
|
-
energia = nodo.get("EPI", 0)
|
|
464
|
-
|
|
465
|
-
# Bifurcación simétrica: alta coherencia, aceleración moderada
|
|
466
|
-
if coherencia > 0.7 and 0.15 < aceleracion < 0.3:
|
|
467
|
-
return "simetrica"
|
|
468
|
-
|
|
469
|
-
# Bifurcación disonante: baja coherencia, alta aceleración
|
|
470
|
-
elif coherencia < 0.5 and aceleracion > 0.3:
|
|
471
|
-
return "disonante"
|
|
472
|
-
|
|
473
|
-
# Bifurcación fractal-expansiva: alta energía, alta aceleración
|
|
474
|
-
elif energia > 2.0 and aceleracion > 0.25:
|
|
475
|
-
return "fractal_expansiva"
|
|
476
|
-
|
|
477
|
-
# Por defecto: simétrica
|
|
478
|
-
else:
|
|
479
|
-
return "simetrica"
|
|
480
|
-
|
|
481
|
-
def generar_espacio_bifurcacion(self, nodo_id, nodo_data, tipo_bifurcacion, paso_actual):
|
|
482
|
-
"""Genera el espacio completo de bifurcación con múltiples trayectorias"""
|
|
483
|
-
try:
|
|
484
|
-
if tipo_bifurcacion == "simetrica":
|
|
485
|
-
trayectorias = self._generar_bifurcacion_simetrica(nodo_id, nodo_data)
|
|
486
|
-
elif tipo_bifurcacion == "disonante":
|
|
487
|
-
trayectorias = self._generar_bifurcacion_disonante(nodo_id, nodo_data)
|
|
488
|
-
elif tipo_bifurcacion == "fractal_expansiva":
|
|
489
|
-
trayectorias = self._generar_bifurcacion_fractal_expansiva(nodo_id, nodo_data)
|
|
490
|
-
else:
|
|
491
|
-
trayectorias = self._generar_bifurcacion_simetrica(nodo_id, nodo_data)
|
|
492
|
-
|
|
493
|
-
# Crear espacio de bifurcación
|
|
494
|
-
espacio = EspacioBifurcacion(
|
|
495
|
-
nodo_origen_id=nodo_id,
|
|
496
|
-
tipo_bifurcacion=tipo_bifurcacion,
|
|
497
|
-
trayectorias=trayectorias,
|
|
498
|
-
paso_inicio=paso_actual,
|
|
499
|
-
pasos_exploracion=random.randint(8, 15) # Exploración variable
|
|
500
|
-
)
|
|
501
|
-
|
|
502
|
-
# Registrar estadísticas
|
|
503
|
-
self.estadisticas_bifurcacion['total_bifurcaciones'] += 1
|
|
504
|
-
self.estadisticas_bifurcacion[f'bifurcaciones_{tipo_bifurcacion}s'] += 1
|
|
505
|
-
|
|
506
|
-
return espacio
|
|
507
|
-
|
|
508
|
-
except Exception as e:
|
|
509
|
-
return None
|
|
510
|
-
|
|
511
|
-
def _generar_bifurcacion_simetrica(self, nodo_id, nodo_data):
|
|
512
|
-
"""Genera bifurcación simétrica con dos trayectorias complementarias"""
|
|
513
|
-
trayectorias = []
|
|
514
|
-
|
|
515
|
-
# Trayectoria A: Expansión coherente
|
|
516
|
-
trayectoria_a = TrayectoriaBifurcacion(
|
|
517
|
-
id=f"{nodo_id}_sym_A",
|
|
518
|
-
tipo="expansion_coherente",
|
|
519
|
-
secuencia_glifica=["VA'L", "R'A", "I'L"],
|
|
520
|
-
parametros_iniciales={
|
|
521
|
-
"EPI": nodo_data.get("EPI", 1.0) * 1.2,
|
|
522
|
-
"νf": nodo_data.get("νf", 1.0) * 1.1,
|
|
523
|
-
"Si": nodo_data.get("Si", 0.5) * 1.15
|
|
524
|
-
},
|
|
525
|
-
convergencia_objetivo="coherencia_expandida"
|
|
526
|
-
)
|
|
527
|
-
|
|
528
|
-
# Trayectoria B: Contracción resonante
|
|
529
|
-
trayectoria_b = TrayectoriaBifurcacion(
|
|
530
|
-
id=f"{nodo_id}_sym_B",
|
|
531
|
-
tipo="contraccion_resonante",
|
|
532
|
-
secuencia_glifica=["NUL", "UM", "IL"],
|
|
533
|
-
parametros_iniciales={
|
|
534
|
-
"EPI": nodo_data.get("EPI", 1.0) * 0.8,
|
|
535
|
-
"νf": nodo_data.get("νf", 1.0) * 0.9,
|
|
536
|
-
"Si": nodo_data.get("Si", 0.5) * 1.2
|
|
537
|
-
},
|
|
538
|
-
convergencia_objetivo="coherencia_concentrada"
|
|
539
|
-
)
|
|
540
|
-
|
|
541
|
-
trayectorias.extend([trayectoria_a, trayectoria_b])
|
|
542
|
-
return trayectorias
|
|
543
|
-
|
|
544
|
-
def _generar_bifurcacion_disonante(self, nodo_id, nodo_data):
|
|
545
|
-
"""Genera bifurcación disonante con múltiples resoluciones"""
|
|
546
|
-
trayectorias = []
|
|
547
|
-
|
|
548
|
-
# Trayectoria A: Mutación directa
|
|
549
|
-
trayectoria_a = TrayectoriaBifurcacion(
|
|
550
|
-
id=f"{nodo_id}_dis_A",
|
|
551
|
-
tipo="mutacion_directa",
|
|
552
|
-
secuencia_glifica=["THOL"],
|
|
553
|
-
parametros_iniciales={
|
|
554
|
-
"EPI": nodo_data.get("EPI", 1.0) * 1.5,
|
|
555
|
-
"νf": nodo_data.get("νf", 1.0) * 1.3,
|
|
556
|
-
"ΔNFR": nodo_data.get("ΔNFR", 0) * 1.4
|
|
557
|
-
},
|
|
558
|
-
convergencia_objetivo="mutacion_estable"
|
|
559
|
-
)
|
|
560
|
-
|
|
561
|
-
# Trayectoria B: Reorganización recursiva
|
|
562
|
-
trayectoria_b = TrayectoriaBifurcacion(
|
|
563
|
-
id=f"{nodo_id}_dis_B",
|
|
564
|
-
tipo="reorganizacion_recursiva",
|
|
565
|
-
secuencia_glifica=["RE'MESH", "NA'V"],
|
|
566
|
-
parametros_iniciales={
|
|
567
|
-
"EPI": nodo_data.get("EPI", 1.0) * 0.9,
|
|
568
|
-
"νf": nodo_data.get("νf", 1.0),
|
|
569
|
-
"Si": nodo_data.get("Si", 0.5) * 1.3
|
|
570
|
-
},
|
|
571
|
-
convergencia_objetivo="reorganizacion_estable"
|
|
572
|
-
)
|
|
573
|
-
|
|
574
|
-
# Trayectoria C: Silencio regenerativo
|
|
575
|
-
trayectoria_c = TrayectoriaBifurcacion(
|
|
576
|
-
id=f"{nodo_id}_dis_C",
|
|
577
|
-
tipo="silencio_regenerativo",
|
|
578
|
-
secuencia_glifica=["SH'A", "A'L"],
|
|
579
|
-
parametros_iniciales={
|
|
580
|
-
"EPI": nodo_data.get("EPI", 1.0) * 0.7,
|
|
581
|
-
"νf": nodo_data.get("νf", 1.0) * 0.8,
|
|
582
|
-
"Si": nodo_data.get("Si", 0.5) * 0.9
|
|
583
|
-
},
|
|
584
|
-
convergencia_objetivo="regeneracion_silenciosa"
|
|
585
|
-
)
|
|
586
|
-
|
|
587
|
-
trayectorias.extend([trayectoria_a, trayectoria_b, trayectoria_c])
|
|
588
|
-
return trayectorias
|
|
589
|
-
|
|
590
|
-
def _generar_bifurcacion_fractal_expansiva(self, nodo_id, nodo_data):
|
|
591
|
-
"""Genera bifurcación fractal-expansiva con sub-nodos derivados"""
|
|
592
|
-
trayectorias = []
|
|
593
|
-
|
|
594
|
-
# Trayectoria principal: Nodo padre con T'HOL
|
|
595
|
-
trayectoria_padre = TrayectoriaBifurcacion(
|
|
596
|
-
id=f"{nodo_id}_frac_padre",
|
|
597
|
-
tipo="autoorganizacion_padre",
|
|
598
|
-
secuencia_glifica=["THOL"],
|
|
599
|
-
parametros_iniciales=nodo_data.copy(),
|
|
600
|
-
convergencia_objetivo="autoorganizacion_estable"
|
|
601
|
-
)
|
|
602
|
-
|
|
603
|
-
# Sub-nodos derivados con variaciones
|
|
604
|
-
for i in range(3): # 3 sub-nodos derivados
|
|
605
|
-
variacion = 0.8 + 0.4 * random.random() # Variación 0.8-1.2
|
|
606
|
-
|
|
607
|
-
trayectoria_derivado = TrayectoriaBifurcacion(
|
|
608
|
-
id=f"{nodo_id}_frac_der_{i}",
|
|
609
|
-
tipo="derivado_fractal",
|
|
610
|
-
secuencia_glifica=["A'L", "E'N"],
|
|
611
|
-
parametros_iniciales={
|
|
612
|
-
"EPI": nodo_data.get("EPI", 1.0) * variacion,
|
|
613
|
-
"νf": nodo_data.get("νf", 1.0) * variacion,
|
|
614
|
-
"Si": nodo_data.get("Si", 0.5) * (0.5 + 0.5 * variacion),
|
|
615
|
-
"derivado_de": nodo_id,
|
|
616
|
-
"factor_variacion": variacion
|
|
617
|
-
},
|
|
618
|
-
convergencia_objetivo="derivacion_coherente"
|
|
619
|
-
)
|
|
620
|
-
|
|
621
|
-
trayectorias.append(trayectoria_derivado)
|
|
622
|
-
|
|
623
|
-
trayectorias.insert(0, trayectoria_padre) # Padre al inicio
|
|
624
|
-
return trayectorias
|
|
625
|
-
|
|
626
|
-
def procesar_bifurcaciones_activas(self, G, paso_actual):
|
|
627
|
-
"""Procesa todas las bifurcaciones activas en el paso actual"""
|
|
628
|
-
resultados = {
|
|
629
|
-
'trayectorias_procesadas': 0,
|
|
630
|
-
'convergencias_detectadas': 0,
|
|
631
|
-
'bifurcaciones_completadas': [],
|
|
632
|
-
'nuevos_nodos_generados': []
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
bifurcaciones_completadas = []
|
|
636
|
-
|
|
637
|
-
for nodo_id, espacio_bifurcacion in list(self.bifurcaciones_activas.items()):
|
|
638
|
-
try:
|
|
639
|
-
# Verificar si la bifurcación ha completado su exploración
|
|
640
|
-
pasos_transcurridos = paso_actual - espacio_bifurcacion.paso_inicio
|
|
641
|
-
|
|
642
|
-
if pasos_transcurridos < espacio_bifurcacion.pasos_exploracion:
|
|
643
|
-
# Evolucionar trayectorias activas
|
|
644
|
-
resultado_evolucion = self._evolucionar_trayectorias(
|
|
645
|
-
espacio_bifurcacion, pasos_transcurridos, G
|
|
646
|
-
)
|
|
647
|
-
resultados['trayectorias_procesadas'] += resultado_evolucion['procesadas']
|
|
648
|
-
|
|
649
|
-
else:
|
|
650
|
-
# Convergencia final de trayectorias
|
|
651
|
-
resultado_convergencia = self._converger_bifurcacion(
|
|
652
|
-
espacio_bifurcacion, G, nodo_id
|
|
653
|
-
)
|
|
654
|
-
|
|
655
|
-
if resultado_convergencia['exitosa']:
|
|
656
|
-
resultados['convergencias_detectadas'] += 1
|
|
657
|
-
resultados['bifurcaciones_completadas'].append(nodo_id)
|
|
658
|
-
resultados['nuevos_nodos_generados'].extend(
|
|
659
|
-
resultado_convergencia.get('nodos_generados', [])
|
|
660
|
-
)
|
|
661
|
-
bifurcaciones_completadas.append(nodo_id)
|
|
662
|
-
|
|
663
|
-
# Actualizar estadísticas
|
|
664
|
-
self.estadisticas_bifurcacion['convergencias_exitosas'] += 1
|
|
665
|
-
|
|
666
|
-
except Exception as e:
|
|
667
|
-
bifurcaciones_completadas.append(nodo_id) # Eliminar bifurcación problemática
|
|
668
|
-
|
|
669
|
-
# Limpiar bifurcaciones completadas
|
|
670
|
-
for nodo_id in bifurcaciones_completadas:
|
|
671
|
-
if nodo_id in self.bifurcaciones_activas:
|
|
672
|
-
del self.bifurcaciones_activas[nodo_id]
|
|
673
|
-
|
|
674
|
-
return resultados
|
|
675
|
-
|
|
676
|
-
def _evolucionar_trayectorias(self, espacio_bifurcacion, paso_relativo, G):
|
|
677
|
-
"""Evoluciona las trayectorias de una bifurcación en el paso actual"""
|
|
678
|
-
resultado = {'procesadas': 0, 'colapsadas': 0}
|
|
679
|
-
|
|
680
|
-
for trayectoria in espacio_bifurcacion.trayectorias:
|
|
681
|
-
if not trayectoria.activa:
|
|
682
|
-
continue
|
|
683
|
-
|
|
684
|
-
try:
|
|
685
|
-
# Aplicar transformación glífica según el paso relativo
|
|
686
|
-
if paso_relativo < len(trayectoria.secuencia_glifica):
|
|
687
|
-
glifo_actual = trayectoria.secuencia_glifica[paso_relativo]
|
|
688
|
-
|
|
689
|
-
# Aplicar transformación específica de la trayectoria
|
|
690
|
-
self._aplicar_transformacion_trayectoria(
|
|
691
|
-
trayectoria, glifo_actual, G, espacio_bifurcacion.nodo_origen_id
|
|
692
|
-
)
|
|
693
|
-
|
|
694
|
-
trayectoria.pasos_completados += 1
|
|
695
|
-
resultado['procesadas'] += 1
|
|
696
|
-
|
|
697
|
-
# Evaluar viabilidad de la trayectoria
|
|
698
|
-
viabilidad = self._evaluar_viabilidad_trayectoria(trayectoria)
|
|
699
|
-
trayectoria.viabilidad = viabilidad
|
|
700
|
-
|
|
701
|
-
# Marcar como inactiva si la viabilidad es muy baja
|
|
702
|
-
if viabilidad < 0.2:
|
|
703
|
-
trayectoria.activa = False
|
|
704
|
-
resultado['colapsadas'] += 1
|
|
705
|
-
self.estadisticas_bifurcacion['trayectorias_colapsadas'] += 1
|
|
706
|
-
|
|
707
|
-
except Exception as e:
|
|
708
|
-
trayectoria.activa = False
|
|
709
|
-
resultado['colapsadas'] += 1
|
|
710
|
-
|
|
711
|
-
return resultado
|
|
712
|
-
|
|
713
|
-
def _aplicar_transformacion_trayectoria(self, trayectoria, glifo, G, nodo_origen_id):
|
|
714
|
-
"""Aplica una transformación glífica específica a una trayectoria"""
|
|
715
|
-
try:
|
|
716
|
-
# Obtener nodo origen desde el grafo
|
|
717
|
-
if nodo_origen_id not in G.nodes():
|
|
718
|
-
return
|
|
719
|
-
|
|
720
|
-
nodo_data = G.nodes[nodo_origen_id]
|
|
721
|
-
|
|
722
|
-
# Aplicar transformación según el glifo y tipo de trayectoria
|
|
723
|
-
if glifo == "VAL": # Expansión
|
|
724
|
-
factor = 1.15 if trayectoria.tipo == "expansion_coherente" else 1.05
|
|
725
|
-
trayectoria.parametros_iniciales["EPI"] *= factor
|
|
726
|
-
|
|
727
|
-
elif glifo == "NUL": # Contracción
|
|
728
|
-
factor = 0.85 if trayectoria.tipo == "contraccion_resonante" else 0.95
|
|
729
|
-
trayectoria.parametros_iniciales["EPI"] *= factor
|
|
730
|
-
|
|
731
|
-
elif glifo == "ZHIR": # Mutación
|
|
732
|
-
trayectoria.parametros_iniciales["EPI"] += 0.5
|
|
733
|
-
trayectoria.parametros_iniciales["νf"] *= 1.2
|
|
734
|
-
|
|
735
|
-
elif glifo == "RA": # Propagación
|
|
736
|
-
trayectoria.parametros_iniciales["Si"] *= 1.1
|
|
737
|
-
|
|
738
|
-
elif glifo == "IL": # Estabilización
|
|
739
|
-
# Convergencia hacia valores estables
|
|
740
|
-
epi_objetivo = 1.5
|
|
741
|
-
trayectoria.parametros_iniciales["EPI"] = (
|
|
742
|
-
trayectoria.parametros_iniciales["EPI"] * 0.8 + epi_objetivo * 0.2
|
|
743
|
-
)
|
|
744
|
-
|
|
745
|
-
elif glifo == "THOL": # Autoorganización
|
|
746
|
-
# Equilibrar todos los parámetros
|
|
747
|
-
for param in ["EPI", "νf", "Si"]:
|
|
748
|
-
if param in trayectoria.parametros_iniciales:
|
|
749
|
-
valor_actual = trayectoria.parametros_iniciales[param]
|
|
750
|
-
valor_equilibrado = max(0.8, min(2.0, valor_actual))
|
|
751
|
-
trayectoria.parametros_iniciales[param] = valor_equilibrado
|
|
752
|
-
|
|
753
|
-
except Exception as e:
|
|
754
|
-
pass
|
|
755
|
-
|
|
756
|
-
def _evaluar_viabilidad_trayectoria(self, trayectoria):
|
|
757
|
-
"""Evalúa la viabilidad estructural de una trayectoria"""
|
|
758
|
-
try:
|
|
759
|
-
# Obtener parámetros actuales
|
|
760
|
-
epi = trayectoria.parametros_iniciales.get("EPI", 1.0)
|
|
761
|
-
vf = trayectoria.parametros_iniciales.get("νf", 1.0)
|
|
762
|
-
si = trayectoria.parametros_iniciales.get("Si", 0.5)
|
|
763
|
-
|
|
764
|
-
# Validación numérica
|
|
765
|
-
if not all(np.isfinite([epi, vf, si])):
|
|
766
|
-
return 0.0
|
|
767
|
-
|
|
768
|
-
# Criterios de viabilidad TNFR
|
|
769
|
-
criterios = []
|
|
770
|
-
|
|
771
|
-
# 1. Rango estructural válido
|
|
772
|
-
criterios.append(1.0 if 0.3 <= epi <= 3.5 else 0.0)
|
|
773
|
-
|
|
774
|
-
# 2. Frecuencia resonante
|
|
775
|
-
criterios.append(1.0 if 0.2 <= vf <= 2.5 else 0.0)
|
|
776
|
-
|
|
777
|
-
# 3. Coherencia mínima
|
|
778
|
-
criterios.append(1.0 if si >= 0.1 else 0.0)
|
|
779
|
-
|
|
780
|
-
# 4. Equilibrio energético
|
|
781
|
-
ratio_equilibrio = min(epi/vf, vf/epi) if vf > 0 else 0
|
|
782
|
-
criterios.append(ratio_equilibrio)
|
|
783
|
-
|
|
784
|
-
# 5. Progreso en secuencia
|
|
785
|
-
progreso = trayectoria.pasos_completados / max(len(trayectoria.secuencia_glifica), 1)
|
|
786
|
-
criterios.append(min(progreso, 1.0))
|
|
787
|
-
|
|
788
|
-
# Viabilidad como promedio ponderado
|
|
789
|
-
viabilidad = np.mean(criterios)
|
|
790
|
-
return max(0.0, min(1.0, viabilidad))
|
|
791
|
-
|
|
792
|
-
except Exception as e:
|
|
793
|
-
return 0.0
|
|
794
|
-
|
|
795
|
-
def _converger_bifurcacion(self, espacio_bifurcacion, G, nodo_origen_id):
|
|
796
|
-
"""Convierte el espacio de bifurcación en configuración final estable"""
|
|
797
|
-
resultado = {
|
|
798
|
-
'exitosa': False,
|
|
799
|
-
'nodos_generados': [],
|
|
800
|
-
'configuracion_final': None
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
try:
|
|
804
|
-
# Filtrar trayectorias viables
|
|
805
|
-
trayectorias_viables = [
|
|
806
|
-
t for t in espacio_bifurcacion.trayectorias
|
|
807
|
-
if t.activa and t.viabilidad > 0.3
|
|
808
|
-
]
|
|
809
|
-
|
|
810
|
-
if not trayectorias_viables:
|
|
811
|
-
return resultado
|
|
812
|
-
|
|
813
|
-
# Ordenar por viabilidad
|
|
814
|
-
trayectorias_viables.sort(key=lambda t: t.viabilidad, reverse=True)
|
|
815
|
-
|
|
816
|
-
# Seleccionar trayectoria ganadora o fusionar múltiples
|
|
817
|
-
if len(trayectorias_viables) == 1:
|
|
818
|
-
# Una sola trayectoria viable
|
|
819
|
-
resultado = self._aplicar_trayectoria_ganadora(
|
|
820
|
-
trayectorias_viables[0], G, nodo_origen_id
|
|
821
|
-
)
|
|
822
|
-
else:
|
|
823
|
-
# Múltiples trayectorias: fusionar las más compatibles
|
|
824
|
-
resultado = self._fusionar_trayectorias_compatibles(
|
|
825
|
-
trayectorias_viables[:3], G, nodo_origen_id # Máximo 3 trayectorias
|
|
826
|
-
)
|
|
827
|
-
|
|
828
|
-
if resultado['exitosa']:
|
|
829
|
-
# Registrar convergencia
|
|
830
|
-
convergencia_info = {
|
|
831
|
-
'nodo_origen': nodo_origen_id,
|
|
832
|
-
'tipo_bifurcacion': espacio_bifurcacion.tipo_bifurcacion,
|
|
833
|
-
'trayectorias_fusionadas': len(trayectorias_viables),
|
|
834
|
-
'configuracion_final': resultado['configuracion_final']
|
|
835
|
-
}
|
|
836
|
-
self.convergencias_detectadas.append(convergencia_info)
|
|
837
|
-
|
|
838
|
-
return resultado
|
|
839
|
-
|
|
840
|
-
except Exception as e:
|
|
841
|
-
return resultado
|
|
842
|
-
|
|
843
|
-
def _aplicar_trayectoria_ganadora(self, trayectoria, G, nodo_origen_id):
|
|
844
|
-
"""Aplica la configuración de una trayectoria ganadora al nodo origen"""
|
|
845
|
-
try:
|
|
846
|
-
if nodo_origen_id not in G.nodes():
|
|
847
|
-
return {'exitosa': False}
|
|
848
|
-
|
|
849
|
-
nodo_data = G.nodes[nodo_origen_id]
|
|
850
|
-
|
|
851
|
-
# Aplicar parámetros finales de la trayectoria
|
|
852
|
-
for param, valor in trayectoria.parametros_iniciales.items():
|
|
853
|
-
if param in ["EPI", "νf", "Si", "ΔNFR"] and np.isfinite(valor):
|
|
854
|
-
nodo_data[param] = max(0.1, min(3.0, valor)) # Límites de seguridad
|
|
855
|
-
|
|
856
|
-
# Marcar convergencia exitosa
|
|
857
|
-
nodo_data["ultima_bifurcacion"] = {
|
|
858
|
-
'tipo': trayectoria.tipo,
|
|
859
|
-
'convergencia': trayectoria.convergencia_objetivo,
|
|
860
|
-
'viabilidad_final': trayectoria.viabilidad
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
return {
|
|
864
|
-
'exitosa': True,
|
|
865
|
-
'configuracion_final': trayectoria.parametros_iniciales.copy(),
|
|
866
|
-
'nodos_generados': [nodo_origen_id]
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
except Exception as e:
|
|
870
|
-
return {'exitosa': False}
|
|
871
|
-
|
|
872
|
-
def _fusionar_trayectorias_compatibles(self, trayectorias, G, nodo_origen_id):
|
|
873
|
-
"""Fusiona múltiples trayectorias compatibles en una configuración híbrida"""
|
|
874
|
-
try:
|
|
875
|
-
if nodo_origen_id not in G.nodes():
|
|
876
|
-
return {'exitosa': False}
|
|
877
|
-
|
|
878
|
-
# Calcular promedios ponderados por viabilidad
|
|
879
|
-
total_viabilidad = sum(t.viabilidad for t in trayectorias)
|
|
880
|
-
if total_viabilidad == 0:
|
|
881
|
-
return {'exitosa': False}
|
|
882
|
-
|
|
883
|
-
configuracion_fusionada = {}
|
|
884
|
-
|
|
885
|
-
for param in ["EPI", "νf", "Si", "ΔNFR"]:
|
|
886
|
-
suma_ponderada = sum(
|
|
887
|
-
t.parametros_iniciales.get(param, 1.0) * t.viabilidad
|
|
888
|
-
for t in trayectorias
|
|
889
|
-
)
|
|
890
|
-
valor_fusionado = suma_ponderada / total_viabilidad
|
|
891
|
-
|
|
892
|
-
if np.isfinite(valor_fusionado):
|
|
893
|
-
configuracion_fusionada[param] = max(0.1, min(3.0, valor_fusionado))
|
|
894
|
-
|
|
895
|
-
# Aplicar configuración fusionada
|
|
896
|
-
nodo_data = G.nodes[nodo_origen_id]
|
|
897
|
-
for param, valor in configuracion_fusionada.items():
|
|
898
|
-
nodo_data[param] = valor
|
|
899
|
-
|
|
900
|
-
# Marcar fusión exitosa
|
|
901
|
-
nodo_data["ultima_bifurcacion"] = {
|
|
902
|
-
'tipo': 'fusion_multiple',
|
|
903
|
-
'trayectorias_fusionadas': len(trayectorias),
|
|
904
|
-
'viabilidad_promedio': total_viabilidad / len(trayectorias)
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
return {
|
|
908
|
-
'exitosa': True,
|
|
909
|
-
'configuracion_final': configuracion_fusionada,
|
|
910
|
-
'nodos_generados': [nodo_origen_id]
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
except Exception as e:
|
|
914
|
-
return {'exitosa': False}
|
|
915
|
-
|
|
916
|
-
def obtener_estadisticas_bifurcacion(self):
|
|
917
|
-
"""Retorna estadísticas completas del sistema de bifurcaciones"""
|
|
918
|
-
stats = self.estadisticas_bifurcacion.copy()
|
|
919
|
-
stats.update({
|
|
920
|
-
'bifurcaciones_activas': len(self.bifurcaciones_activas),
|
|
921
|
-
'tasa_convergencia': (
|
|
922
|
-
stats['convergencias_exitosas'] / max(stats['total_bifurcaciones'], 1)
|
|
923
|
-
),
|
|
924
|
-
'tasa_colapso': (
|
|
925
|
-
stats['trayectorias_colapsadas'] / max(stats['total_bifurcaciones'] * 2.5, 1)
|
|
926
|
-
)
|
|
927
|
-
})
|
|
928
|
-
return stats
|
|
929
|
-
|
|
930
|
-
def integrar_bifurcaciones_canonicas_en_simulacion(G, paso, coordinador_temporal, bifurcation_manager):
|
|
931
|
-
"""
|
|
932
|
-
Función principal de integración de bifurcaciones canónicas en OntoSim
|
|
933
|
-
Reemplaza la lógica simple de aplicar T'HOL automáticamente
|
|
934
|
-
"""
|
|
935
|
-
resultados = {
|
|
936
|
-
'nuevas_bifurcaciones': 0,
|
|
937
|
-
'trayectorias_procesadas': 0,
|
|
938
|
-
'convergencias_completadas': 0,
|
|
939
|
-
'nodos_modificados': []
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
try:
|
|
943
|
-
# Procesar bifurcaciones existentes primero
|
|
944
|
-
if hasattr(bifurcation_manager, 'bifurcaciones_activas'):
|
|
945
|
-
resultado_procesamiento = bifurcation_manager.procesar_bifurcaciones_activas(G, paso)
|
|
946
|
-
resultados['trayectorias_procesadas'] = resultado_procesamiento['trayectorias_procesadas']
|
|
947
|
-
resultados['convergencias_completadas'] = resultado_procesamiento['convergencias_detectadas']
|
|
948
|
-
resultados['nodos_modificados'].extend(resultado_procesamiento['nuevos_nodos_generados'])
|
|
949
|
-
|
|
950
|
-
# Detectar nuevas bifurcaciones
|
|
951
|
-
for nodo_id, nodo_data in G.nodes(data=True):
|
|
952
|
-
if nodo_data.get("estado") != "activo":
|
|
953
|
-
continue
|
|
954
|
-
|
|
955
|
-
# Verificar si el nodo ya está en bifurcación
|
|
956
|
-
if nodo_id in bifurcation_manager.bifurcaciones_activas:
|
|
957
|
-
continue
|
|
958
|
-
|
|
959
|
-
# Detectar condición de bifurcación canónica
|
|
960
|
-
es_bifurcacion, tipo_bifurcacion = bifurcation_manager.detectar_bifurcacion_canonica(
|
|
961
|
-
nodo_data, nodo_id
|
|
962
|
-
)
|
|
963
|
-
|
|
964
|
-
if es_bifurcacion and tipo_bifurcacion != "error_deteccion":
|
|
965
|
-
# Generar espacio de bifurcación múltiple
|
|
966
|
-
espacio_bifurcacion = bifurcation_manager.generar_espacio_bifurcacion(
|
|
967
|
-
nodo_id, nodo_data, tipo_bifurcacion, paso
|
|
968
|
-
)
|
|
969
|
-
|
|
970
|
-
if espacio_bifurcacion:
|
|
971
|
-
# Registrar bifurcación activa
|
|
972
|
-
bifurcation_manager.bifurcaciones_activas[nodo_id] = espacio_bifurcacion
|
|
973
|
-
resultados['nuevas_bifurcaciones'] += 1
|
|
974
|
-
resultados['nodos_modificados'].append(nodo_id)
|
|
975
|
-
|
|
976
|
-
return resultados
|
|
977
|
-
|
|
978
|
-
except Exception as e:
|
|
979
|
-
return resultados
|
|
980
|
-
|
|
981
|
-
def reemplazar_deteccion_bifurcacion_simple(G, paso, umbrales, bifurcation_manager):
|
|
982
|
-
"""
|
|
983
|
-
Función que reemplaza la detección simple de bifurcaciones en OntoSim
|
|
984
|
-
Debe llamarse en lugar del bloque original de detección de bifurcaciones
|
|
985
|
-
"""
|
|
986
|
-
nodos_bifurcados = []
|
|
987
|
-
|
|
988
|
-
try:
|
|
989
|
-
# Parámetros dinámicos para detección
|
|
990
|
-
umbral_aceleracion = umbrales.get('bifurcacion_umbral', 0.15)
|
|
991
|
-
|
|
992
|
-
for nodo_id, nodo_data in G.nodes(data=True):
|
|
993
|
-
if nodo_data.get("estado") != "activo":
|
|
994
|
-
continue
|
|
995
|
-
|
|
996
|
-
# Skip nodos ya en bifurcación
|
|
997
|
-
if nodo_id in bifurcation_manager.bifurcaciones_activas:
|
|
998
|
-
continue
|
|
999
|
-
|
|
1000
|
-
# Usar detección canónica en lugar de simple
|
|
1001
|
-
es_bifurcacion, tipo_bifurcacion = bifurcation_manager.detectar_bifurcacion_canonica(
|
|
1002
|
-
nodo_data, nodo_id, umbral_aceleracion
|
|
1003
|
-
)
|
|
1004
|
-
|
|
1005
|
-
if es_bifurcacion:
|
|
1006
|
-
nodos_bifurcados.append((nodo_id, tipo_bifurcacion))
|
|
1007
|
-
|
|
1008
|
-
return nodos_bifurcados
|
|
1009
|
-
|
|
1010
|
-
except Exception as e:
|
|
1011
|
-
return []
|
|
1012
|
-
|
|
1013
|
-
def mostrar_trayectorias_activas(bifurcation_manager):
|
|
1014
|
-
"""Muestra detalles de las trayectorias activas"""
|
|
1015
|
-
if not bifurcation_manager.bifurcaciones_activas:
|
|
1016
|
-
return "No hay bifurcaciones activas"
|
|
1017
|
-
|
|
1018
|
-
detalles = []
|
|
1019
|
-
for nodo_id, espacio in bifurcation_manager.bifurcaciones_activas.items():
|
|
1020
|
-
trayectorias_activas = [t for t in espacio.trayectorias if t.activa]
|
|
1021
|
-
viabilidades = [f"{t.viabilidad:.2f}" for t in trayectorias_activas]
|
|
1022
|
-
|
|
1023
|
-
detalles.append(
|
|
1024
|
-
f" {nodo_id}: {espacio.tipo_bifurcacion} "
|
|
1025
|
-
f"({len(trayectorias_activas)} activas, viabilidades: {viabilidades})"
|
|
1026
|
-
)
|
|
1027
|
-
|
|
1028
|
-
return "Trayectorias activas:\n" + "\n".join(detalles)
|
|
1029
|
-
|
|
1030
|
-
def limpiar_bifurcaciones_obsoletas(bifurcation_manager, paso_actual, limite_pasos=50):
|
|
1031
|
-
"""Limpia bifurcaciones que han excedido el tiempo máximo de exploración"""
|
|
1032
|
-
bifurcaciones_obsoletas = []
|
|
1033
|
-
|
|
1034
|
-
for nodo_id, espacio in list(bifurcation_manager.bifurcaciones_activas.items()):
|
|
1035
|
-
pasos_transcurridos = paso_actual - espacio.paso_inicio
|
|
1036
|
-
|
|
1037
|
-
if pasos_transcurridos > limite_pasos:
|
|
1038
|
-
bifurcaciones_obsoletas.append(nodo_id)
|
|
1039
|
-
|
|
1040
|
-
for nodo_id in bifurcaciones_obsoletas:
|
|
1041
|
-
del bifurcation_manager.bifurcaciones_activas[nodo_id]
|
|
1042
|
-
|
|
1043
|
-
return len(bifurcaciones_obsoletas)
|
|
1044
|
-
|
|
1045
|
-
# =========================================================================================
|
|
1046
|
-
# SISTEMA DE UMBRALES DINÁMICOS
|
|
1047
|
-
# =========================================================================================
|
|
1048
|
-
|
|
1049
|
-
def calcular_umbrales_dinamicos(C_t, densidad_nodal, fase_simulacion="emergencia"):
|
|
1050
|
-
|
|
1051
|
-
# Factor de sensibilidad basado en desviación de C(t) del punto de equilibrio
|
|
1052
|
-
equilibrio_base = 1.0
|
|
1053
|
-
desviacion_C_t = abs(C_t - equilibrio_base)
|
|
1054
|
-
|
|
1055
|
-
# Sensibilidad adaptativa: más restrictivo cuando C(t) está lejos del equilibrio
|
|
1056
|
-
sensibilidad = max(0.4, min(2.0, 1.0 + 0.8 * desviacion_C_t))
|
|
1057
|
-
|
|
1058
|
-
# Factor de densidad: redes densas requieren umbrales más estrictos
|
|
1059
|
-
factor_densidad = max(0.7, min(1.5, 1.0 - 0.1 * (densidad_nodal - 3.0)))
|
|
1060
|
-
|
|
1061
|
-
# Ajuste por fase de simulación
|
|
1062
|
-
multiplicadores_fase = {
|
|
1063
|
-
"emergencia": 1.2, # más tolerante para permitir emergencia inicial
|
|
1064
|
-
"estabilizacion": 0.8, # más restrictivo para consolidar estructuras
|
|
1065
|
-
"bifurcacion": 1.5 # muy tolerante para permitir reorganización
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
factor_fase = multiplicadores_fase.get(fase_simulacion, 1.0)
|
|
1069
|
-
|
|
1070
|
-
# Cálculo de umbrales fundamentales
|
|
1071
|
-
sensibilidad_final = sensibilidad * factor_densidad * factor_fase
|
|
1072
|
-
|
|
1073
|
-
return {
|
|
1074
|
-
# Umbrales de conexión (para crear/eliminar aristas)
|
|
1075
|
-
'θ_conexion': 0.12 * sensibilidad_final,
|
|
1076
|
-
'EPI_conexion': 1.8 * sensibilidad_final,
|
|
1077
|
-
'νf_conexion': 0.2 * sensibilidad_final,
|
|
1078
|
-
'Si_conexion': 0.25 * sensibilidad_final,
|
|
1079
|
-
|
|
1080
|
-
# Umbrales críticos nodales
|
|
1081
|
-
'θ_mutacion': 0.25 * sensibilidad_final, # para activar Z'HIR
|
|
1082
|
-
'θ_colapso': 0.45 * sensibilidad_final, # para activar SH'A
|
|
1083
|
-
'θ_autoorganizacion': 0.35 * sensibilidad_final, # para activar T'HOL
|
|
1084
|
-
|
|
1085
|
-
# Límites de estabilidad estructural
|
|
1086
|
-
'EPI_max_dinamico': max(2.5, C_t * 2.8), # límite superior adaptativo
|
|
1087
|
-
'EPI_min_coherencia': max(0.4, C_t * 0.3), # límite inferior para coherencia
|
|
1088
|
-
|
|
1089
|
-
# Umbrales de bifurcación estructural
|
|
1090
|
-
'bifurcacion_aceleracion': 0.15 * sensibilidad_final,
|
|
1091
|
-
'bifurcacion_gradiente': 0.8 * sensibilidad_final,
|
|
1092
|
-
|
|
1093
|
-
# Metadatos de cálculo
|
|
1094
|
-
'C_t_usado': C_t,
|
|
1095
|
-
'sensibilidad_calculada': sensibilidad_final,
|
|
1096
|
-
'factor_densidad': factor_densidad,
|
|
1097
|
-
'fase': fase_simulacion
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
def evaluar_condiciones_emergencia_dinamica(nodo, umbrales, campo_coherencia):
|
|
1101
|
-
"""
|
|
1102
|
-
Evalúa si un nodo cumple condiciones de emergencia con umbrales dinámicos.
|
|
1103
|
-
|
|
1104
|
-
Args:
|
|
1105
|
-
nodo: Diccionario con parámetros del nodo
|
|
1106
|
-
umbrales: Umbrales calculados dinámicamente
|
|
1107
|
-
campo_coherencia: C(t) actual de la red
|
|
1108
|
-
|
|
1109
|
-
Returns:
|
|
1110
|
-
tuple: (puede_emerger, razon_rechazo)
|
|
1111
|
-
"""
|
|
1112
|
-
# Verificación de coherencia estructural mínima
|
|
1113
|
-
if nodo.get("EPI", 0) < umbrales['EPI_min_coherencia']:
|
|
1114
|
-
return False, f"EPI insuficiente: {nodo.get('EPI', 0):.3f} < {umbrales['EPI_min_coherencia']:.3f}"
|
|
1115
|
-
|
|
1116
|
-
# Verificación de frecuencia resonante
|
|
1117
|
-
if nodo.get("νf", 0) < 0.3: # mínimo absoluto para vibración
|
|
1118
|
-
return False, f"Frecuencia demasiado baja: {nodo.get('νf', 0):.3f}"
|
|
1119
|
-
|
|
1120
|
-
# Verificación de compatibilidad con campo de coherencia
|
|
1121
|
-
fase_nodo = nodo.get("fase", 0.5)
|
|
1122
|
-
if abs(fase_nodo - 0.0) > 0.7 and campo_coherencia > 1.2:
|
|
1123
|
-
return False, f"Disonancia con campo: fase={fase_nodo:.3f}, C(t)={campo_coherencia:.3f}"
|
|
1124
|
-
|
|
1125
|
-
# Verificación de gradiente nodal dentro de límites
|
|
1126
|
-
ΔNFR = abs(nodo.get("ΔNFR", 0))
|
|
1127
|
-
if ΔNFR > umbrales['bifurcacion_gradiente']:
|
|
1128
|
-
return False, f"Gradiente excesivo: {ΔNFR:.3f} > {umbrales['bifurcacion_gradiente']:.3f}"
|
|
1129
|
-
|
|
1130
|
-
return True, "Condiciones de emergencia cumplidas"
|
|
1131
|
-
|
|
1132
|
-
def detectar_fase_simulacion(G, paso_actual, historial_C_t, ventana=50):
|
|
1133
|
-
"""
|
|
1134
|
-
Detecta la fase actual de la simulación para ajustar umbrales.
|
|
1135
|
-
|
|
1136
|
-
Args:
|
|
1137
|
-
G: Grafo actual
|
|
1138
|
-
paso_actual: Paso de simulación actual
|
|
1139
|
-
historial_C_t: Historia de coherencia total [(paso, C_t), ...]
|
|
1140
|
-
ventana: Ventana de pasos para análisis de tendencias
|
|
1141
|
-
|
|
1142
|
-
Returns:
|
|
1143
|
-
str: "emergencia", "estabilizacion", "bifurcacion"
|
|
1144
|
-
"""
|
|
1145
|
-
if len(historial_C_t) < ventana:
|
|
1146
|
-
return "emergencia"
|
|
1147
|
-
|
|
1148
|
-
# Analizar últimos valores de C(t)
|
|
1149
|
-
valores_recientes = [c_t for _, c_t in historial_C_t[-ventana:]]
|
|
1150
|
-
|
|
1151
|
-
# Calcular variabilidad
|
|
1152
|
-
variabilidad = np.std(valores_recientes)
|
|
1153
|
-
tendencia = np.mean(valores_recientes[-10:]) - np.mean(valores_recientes[:10])
|
|
1154
|
-
|
|
1155
|
-
# Contar nodos activos
|
|
1156
|
-
nodos_activos = sum(1 for n in G.nodes if G.nodes[n].get("estado") == "activo")
|
|
1157
|
-
fraccion_activa = nodos_activos / len(G.nodes) if G.nodes else 0
|
|
1158
|
-
|
|
1159
|
-
# Lógica de clasificación
|
|
1160
|
-
if variabilidad > 0.3 and abs(tendencia) > 0.2:
|
|
1161
|
-
return "bifurcacion" # alta variabilidad y cambio direccional
|
|
1162
|
-
elif variabilidad < 0.05 and fraccion_activa > 0.6:
|
|
1163
|
-
return "estabilizacion" # baja variabilidad, muchos nodos activos
|
|
1164
|
-
else:
|
|
1165
|
-
return "emergencia" # estado por defecto
|
|
1166
|
-
|
|
1167
|
-
def aplicar_umbrales_dinamicos_conexiones(G, umbrales):
|
|
1168
|
-
"""
|
|
1169
|
-
Aplica umbrales dinámicos para gestión de conexiones de red.
|
|
1170
|
-
|
|
1171
|
-
Args:
|
|
1172
|
-
G: Grafo de red
|
|
1173
|
-
umbrales: Umbrales calculados dinámicamente
|
|
1174
|
-
|
|
1175
|
-
Returns:
|
|
1176
|
-
dict: Estadísticas de conexiones creadas/eliminadas
|
|
1177
|
-
"""
|
|
1178
|
-
conexiones_creadas = 0
|
|
1179
|
-
conexiones_eliminadas = 0
|
|
1180
|
-
nodos_lista = list(G.nodes)
|
|
1181
|
-
|
|
1182
|
-
for i in range(len(nodos_lista)):
|
|
1183
|
-
for j in range(i + 1, len(nodos_lista)):
|
|
1184
|
-
n1, n2 = nodos_lista[i], nodos_lista[j]
|
|
1185
|
-
nodo1, nodo2 = G.nodes[n1], G.nodes[n2]
|
|
1186
|
-
|
|
1187
|
-
# Evaluar condiciones de resonancia con umbrales dinámicos
|
|
1188
|
-
condiciones_resonancia = [
|
|
1189
|
-
abs(nodo1.get("θ", 0) - nodo2.get("θ", 0)) < umbrales['θ_conexion'],
|
|
1190
|
-
abs(nodo1.get("EPI", 0) - nodo2.get("EPI", 0)) < umbrales['EPI_conexion'],
|
|
1191
|
-
abs(nodo1.get("νf", 1) - nodo2.get("νf", 1)) < umbrales['νf_conexion'],
|
|
1192
|
-
abs(nodo1.get("Si", 0) - nodo2.get("Si", 0)) < umbrales['Si_conexion']
|
|
1193
|
-
]
|
|
1194
|
-
|
|
1195
|
-
# Criterio: al menos 3 de 4 condiciones cumplidas
|
|
1196
|
-
resonancia_suficiente = sum(condiciones_resonancia) >= 3
|
|
1197
|
-
|
|
1198
|
-
# Verificar saturación de conexiones
|
|
1199
|
-
vecinos_n1 = len(list(G.neighbors(n1)))
|
|
1200
|
-
vecinos_n2 = len(list(G.neighbors(n2)))
|
|
1201
|
-
max_conexiones = int(8 * umbrales['sensibilidad_calculada'])
|
|
1202
|
-
|
|
1203
|
-
saturacion = vecinos_n1 >= max_conexiones and vecinos_n2 >= max_conexiones
|
|
1204
|
-
|
|
1205
|
-
# Lógica de conexión/desconexión
|
|
1206
|
-
existe_conexion = G.has_edge(n1, n2)
|
|
1207
|
-
|
|
1208
|
-
if resonancia_suficiente and not saturacion and not existe_conexion:
|
|
1209
|
-
G.add_edge(n1, n2)
|
|
1210
|
-
conexiones_creadas += 1
|
|
1211
|
-
elif not resonancia_suficiente and existe_conexion:
|
|
1212
|
-
G.remove_edge(n1, n2)
|
|
1213
|
-
conexiones_eliminadas += 1
|
|
1214
|
-
|
|
1215
|
-
return {
|
|
1216
|
-
'conexiones_creadas': conexiones_creadas,
|
|
1217
|
-
'conexiones_eliminadas': conexiones_eliminadas,
|
|
1218
|
-
'umbrales_usados': umbrales
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
def evaluar_activacion_glifica_dinamica(nodo, umbrales, vecinos_data=None):
|
|
1222
|
-
"""
|
|
1223
|
-
Evalúa qué glifo debería activarse basado en umbrales dinámicos.
|
|
1224
|
-
|
|
1225
|
-
Args:
|
|
1226
|
-
nodo: Datos del nodo
|
|
1227
|
-
umbrales: Umbrales dinámicos calculados
|
|
1228
|
-
vecinos_data: Lista de datos de nodos vecinos
|
|
1229
|
-
|
|
1230
|
-
Returns:
|
|
1231
|
-
str or None: Glifo a activar o None si no hay activación
|
|
1232
|
-
"""
|
|
1233
|
-
# Z'HIR - Mutación por umbral de cambio estructural
|
|
1234
|
-
θ_actual = nodo.get("θ", 0)
|
|
1235
|
-
θ_prev = nodo.get("θ_prev", θ_actual)
|
|
1236
|
-
|
|
1237
|
-
if abs(θ_actual - θ_prev) > umbrales['θ_mutacion']:
|
|
1238
|
-
return "ZHIR"
|
|
1239
|
-
|
|
1240
|
-
# SH'A - Colapso por pérdida de coherencia
|
|
1241
|
-
if (nodo.get("EPI", 0) < umbrales['EPI_min_coherencia'] and
|
|
1242
|
-
abs(nodo.get("ΔNFR", 0)) > umbrales['θ_colapso']):
|
|
1243
|
-
return "SHA"
|
|
1244
|
-
|
|
1245
|
-
# T'HOL - Autoorganización por aceleración estructural
|
|
1246
|
-
aceleracion = abs(nodo.get("d2EPI_dt2", 0))
|
|
1247
|
-
if aceleracion > umbrales.get('bifurcacion_aceleracion', 0.15):
|
|
1248
|
-
return "THOL"
|
|
1249
|
-
|
|
1250
|
-
# R'A - Resonancia con vecinos (requiere datos de vecinos)
|
|
1251
|
-
if vecinos_data and len(vecinos_data) > 0:
|
|
1252
|
-
θ_vecinos = [v.get("θ", 0) for v in vecinos_data]
|
|
1253
|
-
resonancia_promedio = sum(abs(θ_actual - θ_v) for θ_v in θ_vecinos) / len(θ_vecinos)
|
|
1254
|
-
|
|
1255
|
-
if resonancia_promedio < umbrales['θ_conexion'] * 0.5: # muy sincronizado
|
|
1256
|
-
return "RA"
|
|
1257
|
-
|
|
1258
|
-
if (nodo.get("EPI", 0) < umbrales.get('EPI_min_coherencia', 0.4) and
|
|
1259
|
-
abs(nodo.get("ΔNFR", 0)) > umbrales.get('θ_colapso', 0.45)):
|
|
1260
|
-
return "SHA"
|
|
1261
|
-
|
|
1262
|
-
return None
|
|
1263
|
-
|
|
1264
|
-
def gestionar_conexiones_canonico(G, paso, historia_Ct):
|
|
1265
|
-
"""
|
|
1266
|
-
Reemplaza la gestión manual de conexiones por sistema canónico TNFR.
|
|
1267
|
-
Esta función debe reemplazar el bloque de gestión de aristas en simular_emergencia().
|
|
1268
|
-
"""
|
|
1269
|
-
# Calcular coherencia total actual
|
|
1270
|
-
if len(G.nodes) == 0:
|
|
1271
|
-
C_t = 0
|
|
1272
|
-
else:
|
|
1273
|
-
C_t = sum(G.nodes[n]["EPI"] for n in G.nodes) / len(G)
|
|
1274
|
-
|
|
1275
|
-
# Calcular densidad nodal promedio
|
|
1276
|
-
densidad_promedio = sum(len(list(G.neighbors(n))) for n in G.nodes) / len(G.nodes) if G.nodes else 0
|
|
1277
|
-
|
|
1278
|
-
# Detectar fase actual de simulación
|
|
1279
|
-
fase_actual = detectar_fase_simulacion(G, paso, historia_Ct)
|
|
1280
|
-
|
|
1281
|
-
# Calcular umbrales dinámicos
|
|
1282
|
-
umbrales = calcular_umbrales_dinamicos(C_t, densidad_promedio, fase_actual)
|
|
1283
|
-
|
|
1284
|
-
# Aplicar gestión de conexiones canónica
|
|
1285
|
-
estadisticas = aplicar_umbrales_dinamicos_conexiones(G, umbrales)
|
|
1286
|
-
|
|
1287
|
-
return umbrales, estadisticas
|
|
1288
|
-
|
|
1289
|
-
__all__ = [
|
|
1290
|
-
'TemporalCoordinatorTNFR',
|
|
1291
|
-
'EspacioBifurcacion',
|
|
1292
|
-
'BifurcationManagerTNFR',
|
|
1293
|
-
'calcular_umbrales_dinamicos',
|
|
1294
|
-
'aplicar_umbrales_dinamicos_conexiones',
|
|
1295
|
-
'integrar_tiempo_topologico_en_simulacion',
|
|
1296
|
-
'integrar_bifurcaciones_canonicas_en_simulacion',
|
|
1297
|
-
'reemplazar_deteccion_bifurcacion_simple',
|
|
1298
|
-
'mostrar_trayectorias_activas',
|
|
1299
|
-
'limpiar_bifurcaciones_obsoletas',
|
|
1300
|
-
'detectar_fase_simulacion',
|
|
1301
|
-
'inicializar_coordinador_temporal_canonico',
|
|
1302
|
-
'evaluar_condiciones_emergencia_dinamica',
|
|
1303
|
-
'evaluar_activacion_glifica_dinamica',
|
|
1304
|
-
'gestionar_conexiones_canonico'
|
|
1305
|
-
]
|