Automatización y Desarrollo

Automatización de OpenFOAM con Python: PyFoam y scripting de estudios paramétricos

Aprende a generar casos en bucle, modificar parámetros en diccionarios, lanzar simulaciones en paralelo y parsear residuales con Python.

I

Ing. Javier Solís

Ingeniero de Software y CFD

Publicado: 12 de febrero de 202511 min de lectura

Automatización de Casos OpenFOAM con Python

El formato de texto plano estructurado de los diccionarios de OpenFOAM hace que la plataforma sea ideal para la automatización integral mediante scripts de Python, eliminando por completo los errores humanos en estudios de sensibilidad geométrica o mallas de convergencia.


1. Modificación de Diccionarios con PyFoam

PyFoam es la librería estándar para interactuar con archivos OpenFOAM desde Python sin romper la sintaxis de corchetes ni puntos y coma:

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3 import os from PyFoam.RunDictionary.ParsedParameterFile import ParsedParameterFile # 1. Abrir archivo de control control_dict = ParsedParameterFile("system/controlDict") # 2. Modificar variables de ejecución control_dict["endTime"] = 2000 control_dict["writeInterval"] = 50 control_dict.writeFile() # 3. Modificar condiciones de entrada de velocidad u_dict = ParsedParameterFile("0/U") u_dict["boundaryField"]["inlet"]["value"] = "uniform (25.0 0 0)" u_dict.writeFile() print("✓ Parámetros actualizados con éxito")

2. Bucle Paramétrico de Ángulos de Ataque

El siguiente script clona un caso base, modifica la velocidad incidente descompuesta por componentes $(U_x, U_y)$ y ejecuta el solver en segundo plano:

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import shutil import subprocess import numpy as np angles_deg = [0, 2, 4, 6, 8, 10, 12, 14, 16] u_inf = 30.0 base_dir = "naca0012_base" for alpha in angles_deg: rad = np.radians(alpha) ux = u_inf * np.cos(rad) uy = u_inf * np.sin(rad) run_dir = f"case_alpha_{alpha}" if not os.path.exists(run_dir): shutil.copytree(base_dir, run_dir) u_file = ParsedParameterFile(f"{run_dir}/0/U") u_file["boundaryField"]["inlet"]["freestreamValue"] = f"uniform ({ux:.4f} {uy:.4f} 0)" u_file.writeFile() print(f"Lanzando simulación alpha = {alpha}°...") subprocess.run(["simpleFoam"], cwd=run_dir, stdout=open(f"{run_dir}/log.simpleFoam", "w"))

3. Extracción de Residuales con Matplotlib

Podemos procesar los archivos de registro generados por OpenFOAM para graficar la convergencia de residuales:

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import re import matplotlib.pyplot as plt def plot_residuals(log_file): time = [] p_res = [] u_res = [] with open(log_file, 'r') as f: for line in f: if "Time = " in line: t_match = re.search(r'Time = ([0-9.]+)', line) if t_match: time.append(float(t_match.group(1))) if "Solving for Ux" in line: u_match = re.search(r'Initial residual = ([0-9.eE+-]+)', line) if u_match: u_res.append(float(u_match.group(1))) if "Solving for p" in line: p_match = re.search(r'Initial residual = ([0-9.eE+-]+)', line) if p_match: p_res.append(float(p_match.group(1))) plt.figure(figsize=(8, 5)) plt.semilogy(u_res, label='Residuo Ux') plt.semilogy(p_res, label='Residuo Presión') plt.xlabel('Iteraciones') plt.ylabel('Residual Inicial') plt.title('Historial de Convergencia simpleFoam') plt.grid(True, which="both", ls="--") plt.legend() plt.savefig('convergencia.png', dpi=300) plt.show()