tnfr 1.0__py3-none-any.whl → 2.0.1__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 +47 -34
- tnfr/constants.py +183 -7
- tnfr/dynamics.py +543 -0
- tnfr/helpers.py +198 -0
- tnfr/main.py +0 -0
- tnfr/observers.py +149 -0
- tnfr/ontosim.py +137 -0
- tnfr/operators.py +296 -0
- tnfr-2.0.1.dist-info/METADATA +28 -0
- tnfr-2.0.1.dist-info/RECORD +14 -0
- tnfr-2.0.1.dist-info/entry_points.txt +2 -0
- tnfr-2.0.1.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-2.0.1.dist-info}/WHEEL +0 -0
- {tnfr-1.0.dist-info → tnfr-2.0.1.dist-info}/licenses/LICENSE.txt +0 -0
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
|
-
]
|