Lección aprendida OpenSees N.º 05
Mi base de análisis en OpenSees no se reproducía: el material recordaba el modelo anterior
Resumen. Volví a correr 20 análisis con el mismo código y las mismas entradas, y ninguno dio igual. La causa fue un material que guarda memoria entre modelos cuando corren en el mismo proceso de Python. El arreglo cabe en una línea.
Si sigues una receta al pie de la letra dos veces, esperas el mismo pastel. En cálculo pasa igual: mismo código, mismas entradas, mismo resultado. Es la base de todo lo demás. Si no puedes repetir un análisis, no puedes revisarlo, ni corregirlo, ni defenderlo ante un revisor.
Por eso me inquietó tanto lo que encontré en una base de varios cientos de análisis dinámicos no lineales en OpenSees, con un material de concreto que se fisura. Para revisar un resultado volví a correr 20 casos tal cual. Ninguno dio igual que la primera vez. La mayoría cambió un par de por ciento, pero uno cambió casi 90 % y otro pasó de “sin daño” a “fisurado”.
En esta nota cuento cómo busqué la causa, cuál era y cómo se arregla. Si corres muchos modelos en paralelo con Python, te puede estar pasando sin que lo sepas.
Buscar la causa descartando
Cuando algo no se repite, la tentación es culpar al “ruido numérico” y seguir. Preferí descartar causas una por una, cada una con su propio script de comprobación:
- Las entradas: acelerogramas y parámetros eran idénticos, bit a bit.
- La malla y los materiales: el periodo de vibración coincidía hasta el último dígito.
- El código: idéntico a la versión que generó la base.
- Las librerías: mismas versiones de OpenSeesPy y NumPy.
- Caos numérico: si cambiaba un dato en una parte en mil millones, el resultado cambiaba en la misma proporción. El problema no amplificaba pequeñas diferencias.
- Azar: el mismo caso, corrido dos veces seguidas, daba exactamente lo mismo.
- Restos del modelo anterior después de
ops.wipe(): no los había, o eso parecía. - La tolerancia de convergencia: la probé aparte.
La prueba que lo explicó
Corrí un mismo caso de tres maneras:
| Cómo lo corrí | Resultado |
|---|---|
| Solo, en un proceso de Python nuevo | referencia |
| Después de otro modelo, en el mismo proceso | ≈ 6 % mayor |
| Dos veces seguidas, solo él | idéntico |
Ahí estaba. El resultado dependía de qué modelo se había corrido antes en el mismo proceso. La base original se generó con 14 procesos en paralelo, cada uno con unos 57 casos en fila. Cada resultado dependía, sin que nadie lo supiera, de su lugar en la fila.
No era la tolerancia
Mi primera sospecha fue la tolerancia de convergencia. La apreté cien veces y luego diez mil veces. La diferencia entre las dos versiones no se movió: seguía en casi 6 %, y cada versión era estable hasta el noveno decimal.
Eso quiere decir que no había un resultado “bueno” y uno “malo”. Había dos respuestas bien calculadas de dos problemas distintos. Algo del material sobrevivía de un modelo al siguiente, a pesar de ops.wipe(). Una pista más: el material imprime su mensaje de presentación una sola vez por proceso, aunque construyas cien modelos.
El arreglo
Darle a cada modelo un proceso nuevo, sin memoria:
from multiprocessing import Pool
with Pool(processes=14, maxtasksperchild=1) as pool:
resultados = pool.map(correr_caso, casos)
maxtasksperchild=1 le dice a Python que cierre cada proceso después de un caso y abra uno limpio para el siguiente. Para pruebas sueltas, corro cada caso en un subprocess. Abrir procesos cuesta algo de tiempo; a cambio, cada fila de la base se puede repetir.
Lo que hago ahora
- Antes de usar una base, vuelvo a correr entre 10 y 20 casos en procesos limpios y comparo.
- Guardo las versiones de las librerías y el comando exacto junto con los resultados.
- Si un material o elemento guarda estado global, cada modelo va en su propio proceso.
“Mismo código y mismas entradas” no alcanza. También tiene que empezar desde cero.
Contenido educativo y referencial. Las opiniones son mías y no representan a ningún empleador. Para un proyecto real manda el criterio del ingeniero responsable y la norma vigente.
← Volver a las notas