Tutorial: mapeo de resultados OpenFOAM entre mallas diferentes

Objetivo

Este procedimiento permite transferir una solución CFD ya calculada desde una malla fuente hacia una malla destino diferente que representa la misma geometría física.

Caso validado:

  • Fuente: B068-P180_coldFlow_AP03C_16proc

  • Malla fuente: aproximadamente 1.33 millones de celdas.

  • Tiempo fuente: 0.05 s.

  • Destino: B068-P180_coldFlow_AP04_validation_cleanNCC_v01.

  • Malla destino: aproximadamente 3.15 millones de celdas.

  • Solver: multicomponentFluid.

  • OpenFOAM: dev-4d09840bec89.

  • Interfaz periódica: nonConformalCyclic (NCC).

El mapeo efectivo se realizó con mapFields en modo no consistente, es decir, sin la opción -consistent.

Principio fundamental

mapFields no requiere que la malla fuente y la malla destino tengan las mismas celdas. Puede utilizarse para transferir campos entre discretizaciones diferentes de un mismo dominio físico.

Debe mantenerse coherencia en:

  • sistema de coordenadas;

  • escala geométrica;

  • regiones físicas;

  • condiciones de frontera;

  • campos requeridos por el solver;

  • topología necesaria para que cada malla pueda abrirse correctamente.

En casos con interfaces NCC, la malla no debe considerarse únicamente como constant/polyMesh. También puede existir información dependiente de la malla en constant/fvMesh, particularmente polyFaces.

Regla práctica para NCC

En una malla con nonConformalCyclic se debe tratar como una unidad coherente:

constant/polyMesh/
constant/fvMesh/

y, en particular:

constant/fvMesh/polyFaces

No debe sustituirse únicamente constant/polyMesh conservando un constant/fvMesh/polyFaces heredado de otra malla.

Preparación de la nueva malla

La malla destino debe existir primero como malla geométrica válida, antes de realizar el mapeo.

Una malla básica contiene normalmente:

constant/polyMesh/
    boundary
    faces
    neighbour
    owner
    points

Verifique la malla con:

checkMesh -constant

En el caso AP04 validado:

cells = 3153056
Number of regions = 1

Los defectos conocidos de checkMesh deben estar evaluados previamente. El mapeo no debe utilizarse como mecanismo para ocultar problemas geométricos o topológicos.

Clasificación de los patches físicos

Cuando la superficie de referencia contiene regiones con nombres definidos, se puede reconstruir la clasificación de los patches mediante surfaceToPatch.

Ejemplo:

surfaceToPatch \
  -tol 0.001 \
  ../B068-P180_snappy/constant/triSurface/B068-P180_named_patches.stl

Después de ejecutar la utilidad, se deben verificar explícitamente los patches y sus números de caras.

En AP04 se obtuvieron:

AIR_INLET_PLENUM       49401
GAS_INLET_STAGE_1        136
GAS_INLET_STAGE_2        313
GAS_INLET_STAGE_3        526
OUTLET                 36116
PERIODIC_0             73548
PERIODIC_180           73718
WALLS                 530093

La salida debe indicar:

unmatched faces = 0

Si aparecen caras no clasificadas de forma inesperada, no se debe continuar.

Verificación del tipo de patch

El nombre del patch no garantiza que su tipo OpenFOAM sea correcto.

Por ejemplo, WALLS debe quedar como:

type wall;

y no simplemente como:

type patch;

Verifique el archivo boundary después de cualquier operación de reclasificación.

En el estado pre-NCC utilizado para AP04:

AIR_INLET_PLENUM       type=patch
GAS_INLET_STAGE_1      type=patch
GAS_INLET_STAGE_2      type=patch
GAS_INLET_STAGE_3      type=patch
OUTLET                 type=patch
PERIODIC_0             type=patch
PERIODIC_180           type=patch
WALLS                  type=wall

Construcción de la NCC

La NCC debe construirse una sola vez sobre una malla pre-NCC limpia.

Ejemplo:

createNonConformalCouples \
  -dict system/createNonConformalCouplesDict.AP04.reverse

Para el caso validado se utilizaron:

patches
(
    PERIODIC_180
    PERIODIC_0
);

con transformación rotacional de 180 grados.

La ejecución correcta produjo:

457704 couplings calculated
fvMeshStitcher: Connecting
Writing mesh to 0.050005
End

y creó los patches auxiliares:

periodic180_on_PERIODIC_180
periodic180_on_PERIODIC_0
nonConformalError_on_PERIODIC_180
nonConformalError_on_PERIODIC_0

Conservar conjuntamente polyMesh y fvMesh

Después de createNonConformalCouples se deben comprobar los dos objetos generados:

ls -lh \
  0.050005/polyMesh/boundary \
  0.050005/fvMesh/polyFaces

Ambos deben pertenecer a la misma generación de la malla NCC.

Para promover esa malla a constant:

cp -a 0.050005/polyMesh constant/polyMesh
cp -a 0.050005/fvMesh   constant/fvMesh

No copie solamente polyMesh.

Comprobación de polyFaces heredado

Si el caso destino se creó inicialmente copiando otro caso, compare los polyFaces:

cmp -s \
  CASO_DESTINO/constant/fvMesh/polyFaces \
  CASO_FUENTE/constant/fvMesh/polyFaces

Un control práctico:

if cmp -s \
  CASO_DESTINO/constant/fvMesh/polyFaces \
  CASO_FUENTE/constant/fvMesh/polyFaces
then
    echo "WARNING: polyFaces identical"
else
    echo "polyFaces differs"
fi

Si las mallas son distintas y polyFaces es binariamente idéntico, debe considerarse sospechoso.

En el caso validado:

AP03C polyFaces = 821 KB
AP04 polyFaces  = 1.8 MB

Esto confirmó que AP04 tenía su propio polyFaces.

Startup test antes del mapeo

Antes de ejecutar mapFields, la malla destino debe ser capaz de abrirse correctamente con el solver.

Configure temporalmente:

startFrom   startTime;
startTime   0.05;

stopAt      endTime;
endTime     0.05;

Ejecute:

foamRun \
  -solver multicomponentFluid \
  -noFunctionObjects

El objetivo es que OpenFOAM construya la malla, conecte la NCC, inicialice el modelo y termine sin avanzar tiempo.

Una prueba satisfactoria alcanza:

fvMeshStitcher: Connecting
Selecting solver multicomponentFluid
...
Starting time loop
End

Esto verifica independientemente:

malla
+ NCC
+ fvMesh
+ diccionarios

No se debe diagnosticar mapFields mientras la malla destino no supere este test.

Preparación del tiempo destino

Debe existir el directorio temporal donde se escribirán los campos mapeados.

Por ejemplo:

0.05/

con los campos requeridos por el solver:

U
p
T
k
omega
O2
N2
CH4
...

En un modelo multicomponente deben existir también todas las especies esperadas por el solver.

Antes de mapear:

cp -a 0.05 0.05.preMap

Configuración de mapFieldsDict

El procedimiento validado utilizó:

FoamFile
{
    format      ascii;
    class       dictionary;
    object      mapFieldsDict;
}

patchMap
(
);

cuttingPatches
(
);

Mapeo efectivo

Para dos mallas distintas de la misma geometría física, el procedimiento efectivo fue ejecutar mapFields sin -consistent.

Ejemplo general:

mapFields \
  ../CASO_FUENTE \
  -sourceTime 0.05 \
  -noFunctionObjects

Caso validado:

mapFields \
  ../B068-P180_coldFlow_AP03C_16proc \
  -sourceTime 0.05 \
  -noFunctionObjects \
  > log.mapFields.AP03C_to_AP04_005_nonConsistent_cleanNCC 2>&1

Verifique el código de retorno:

rc=$?
echo "exit = $rc"

Una ejecución correcta mostró:

Source mesh size: 1333396
Target mesh size: 3153056

Mapping fields for time 0.05

    interpolating OH
    interpolating CN
    ...
    interpolating k
    ...
    interpolating p
    interpolating T
    ...
    interpolating omega
    ...
    interpolating U

End

y:

exit = 0

Uso de -consistent

No debe interpretarse -consistent simplemente como «misma geometría».

La opción supone una correspondencia más estricta entre source y target.

Para este caso, con dos discretizaciones NCC diferentes, la ruta efectiva y validada fue:

mapFields SOURCE -sourceTime TIME

sin:

-consistent

Uso de source reconstruido

Si el caso fuente dispone de una solución reconstruida en el directorio principal del caso, no es necesario mapear desde los processor*.

Por ejemplo:

B068-P180_coldFlow_AP03C_16proc/
    0.05/
    processor0/
    ...
    processor15/

En este caso se utilizó directamente:

mapFields \
  ../B068-P180_coldFlow_AP03C_16proc \
  -sourceTime 0.05

sin -parallelSource.

Validación posterior al mapeo

Después del mapping, repita el startup test del solver con:

startTime   0.05;
endTime     0.05;

Ejecute:

foamRun \
  -solver multicomponentFluid \
  -noFunctionObjects

En el caso validado:

exit = 0

El solver inicializó correctamente:

multicomponentFluid
kOmegaSST
53 species

y alcanzó:

Starting time loop
End

Como comprobación adicional, el Courant cambió después del mapping a:

Courant Number mean: 0.004459740956
Courant Number max:  0.19484275

Esto confirmó que el campo U había sido realmente transferido al target.

Descomposición del caso destino

La descomposición debe realizarse después del mapping.

Elimine una descomposición anterior:

rm -rf processor*

Descomponga nuevamente:

decomposePar -force \
  > log.decomposePar 2>&1

Compruebe el número de particiones:

find . -maxdepth 1 -type d -name 'processor*' | wc -l

Para 16 procesos:

16

En un caso NCC verifique también polyFaces:

find processor* \
  -path '*/constant/fvMesh/polyFaces' \
  -type f | wc -l

Resultado esperado:

16

Compruebe igualmente que el estado inicial fue descompuesto:

find processor* \
  -path '*/0.05/U' \
  -type f | wc -l

Resultado esperado:

16

Ejecución paralela

Una vez validado el target:

mpirun -np 16 \
  foamRun \
  -solver multicomponentFluid \
  -parallel \
  > log.run 2>&1 &

La simulación continúa sobre la nueva malla utilizando como estado inicial la solución interpolada desde la malla anterior.

Secuencia recomendada

La secuencia completa y reproducible es:

1. Crear nueva malla
       |
       v
2. checkMesh
       |
       v
3. Crear o clasificar patches físicos
       |
       v
4. Confirmar tipos de patch
       |
       v
5. Guardar snapshot PRE-NCC
       |
       v
6. createNonConformalCouples una sola vez
       |
       v
7. Preservar juntos:
     polyMesh/
     fvMesh/polyFaces
       |
       v
8. Startup test de la nueva malla
       |
       v
9. Preparar tiempo destino
       |
       v
10. Backup del tiempo destino
       |
       v
11. mapFields SOURCE -sourceTime TIME
       |
       v
12. Confirmar End y exit=0
       |
       v
13. Startup test post-mapping
       |
       v
14. decomposePar
       |
       v
15. Confirmar polyFaces en todos los processors
       |
       v
16. Ejecutar CFD en paralelo

Plantilla reutilizable

Con el caso destino como directorio actual:

SOURCE=../Caso_malla_A
TARGET_TIME=0.05

cp -a "$TARGET_TIME" "${TARGET_TIME}.preMap"

mapFields \
  "$SOURCE" \
  -sourceTime "$TARGET_TIME" \
  -noFunctionObjects \
  > log.mapFields 2>&1

rc=$?

echo "mapFields exit = $rc"
tail -n 100 log.mapFields

Solo continúe si obtiene:

End
mapFields exit = 0

Después haga el startup test, con startTime=endTime:

foamRun \
  -solver multicomponentFluid \
  -noFunctionObjects

Finalmente:

rm -rf processor*

decomposePar -force \
  > log.decomposePar 2>&1

mpirun -np 16 \
  foamRun \
  -solver multicomponentFluid \
  -parallel \
  > log.run 2>&1 &

Conclusión

Una solución CFD puede transferirse entre discretizaciones distintas de una misma geometría siempre que:

  • la nueva malla sea válida por sí misma;

  • sus patches físicos estén correctamente clasificados;

  • la NCC se haya construido sobre la malla definitiva;

  • polyMesh y fvMesh/polyFaces pertenezcan a la misma generación;

  • el solver pueda abrir la nueva malla antes del mapping;

  • mapFields se ejecute en el modo adecuado para mallas diferentes;

  • el estado mapeado se valide nuevamente antes de descomponer y correr.

Para el caso B068-P180, la ruta validada quedó:

AP03C @ 0.05 s
       |
       | mapFields no-consistent
       v
AP04 @ 0.05 s
       |
       | startup validation
       v
decomposePar
       |
       v
continuación CFD sobre AP04

Este procedimiento permite reutilizar soluciones previamente calculadas después de una remallada, evitando reiniciar la simulación desde condiciones iniciales.