OpenFOAM 14, caso 03: mesh Gmsh importata con gmshToFoam
Terzo caso del percorso Imparare OpenFOAM 14: procedura completa e riproducibile, importazione della mesh e validazione analitica. Rifaccio il canale laminare del caso 01 (già usato nello studio di convergenza del caso 02) con una mesh esterna generata da Gmsh invece che con blockMesh.
1. Scopo
Questa dispensa riproduce il benchmark del canale laminare usando una mesh esterna generata con Gmsh invece di blockMesh. Il modello fisico, le condizioni al contorno, le impostazioni del solutore e il riferimento analitico sono mantenuti volutamente identici a quelli del caso di formazione CFD 01. L’unica variazione controllata è il percorso di generazione della mesh.
Il flusso di lavoro è:
geometria Gmsh → mesh Gmsh → gmshToFoam → OpenFOAM → validazione analitica.
L’esercizio mostra come separare geometria e generazione della mesh dal solutore CFD, conservando un benchmark verificabile.
2. Risultato atteso
| Grandezza | Risultato |
|---|---|
| Generatore di mesh | Gmsh |
| Utility di importazione | gmshToFoam |
| Formato della mesh | MSH 2.2 ASCII |
| Celle | 8000 |
| Tipo di cella | 8000 esaedri |
| checkMesh | Mesh OK |
| Ux massima campionata dalla CFD | 0,0149625470 m/s |
| Ux massima teorica | 0,0150000000 m/s |
| RMSE normalizzato | 0,150343 % |
| Confronto con il caso 01 | numericamente indistinguibile |
3. Prerequisiti
Verificare gli strumenti necessari:
gmsh --version
command -v gmshToFoam
command -v changeDictionary
command -v checkMesh
command -v foamRun
Se Gmsh non è installato su Debian:
su -
apt update
apt install gmsh
exit
L’ambiente di OpenFOAM 14 deve essere già caricato.
4. Creare la cartella del caso
Il nuovo caso riusa la fisica validata del caso 01, in modo che cambi soltanto il metodo di generazione della mesh.
cd $FOAM_RUN
mkdir -p 03_gmsh_channel/{0,constant,system,geometry}
Copiare dal caso 01 i dizionari fisici e numerici già validati:
cp 01_laminar_channel/0/U \
01_laminar_channel/0/p \
03_gmsh_channel/0/
cp 01_laminar_channel/constant/physicalProperties \
01_laminar_channel/constant/momentumTransport \
03_gmsh_channel/constant/
cp 01_laminar_channel/system/controlDict \
01_laminar_channel/system/fvSchemes \
01_laminar_channel/system/fvSolution \
03_gmsh_channel/system/
cd $FOAM_RUN/03_gmsh_channel
In questo caso non si usa nessun blockMeshDict.
5. Geometria Gmsh e mesh strutturata
Creare geometry/channel.geo con il seguente contenuto completo:
// Laminar channel benchmark
// Geometry: 0.5 x 0.01 x 0.001 m
// Structured mesh: 200 x 40 x 1 cells
Mesh.MshFileVersion = 2.2;
Mesh.Binary = 0;
// Points
Point(1) = {0.0, -0.005, 0.0};
Point(2) = {0.5, -0.005, 0.0};
Point(3) = {0.5, 0.005, 0.0};
Point(4) = {0.0, 0.005, 0.0};
Point(5) = {0.0, -0.005, 0.001};
Point(6) = {0.5, -0.005, 0.001};
Point(7) = {0.5, 0.005, 0.001};
Point(8) = {0.0, 0.005, 0.001};
// Edges
Line(1) = {1,2};
Line(2) = {2,3};
Line(3) = {3,4};
Line(4) = {4,1};
Line(5) = {5,6};
Line(6) = {6,7};
Line(7) = {7,8};
Line(8) = {8,5};
Line(9) = {1,5};
Line(10) = {2,6};
Line(11) = {3,7};
Line(12) = {4,8};
// Surfaces
Curve Loop(1) = {1,2,3,4};
Plane Surface(1) = {1};
Curve Loop(2) = {5,6,7,8};
Plane Surface(2) = {2};
Curve Loop(3) = {1,10,-5,-9};
Plane Surface(3) = {3};
Curve Loop(4) = {2,11,-6,-10};
Plane Surface(4) = {4};
Curve Loop(5) = {3,12,-7,-11};
Plane Surface(5) = {5};
Curve Loop(6) = {4,9,-8,-12};
Plane Surface(6) = {6};
// Volume
Surface Loop(1) = {-1,2,3,4,5,6};
Volume(1) = {1};
// Structured resolution
Transfinite Curve {1,3,5,7} = 201;
Transfinite Curve {2,4,6,8} = 41;
Transfinite Curve {9,10,11,12} = 2;
Transfinite Surface {1,2,3,4,5,6};
Recombine Surface {1,2,3,4,5,6};
Transfinite Volume {1} = {1,2,3,4,5,6,7,8};
// OpenFOAM boundary names
Physical Surface("frontAndBack") = {1,2};
Physical Surface("walls") = {3,5};
Physical Surface("outlet") = {4};
Physical Surface("inlet") = {6};
Physical Volume("fluid") = {1};
Le definizioni Transfinite specificano il numero di nodi. Di conseguenza:
- 201 nodi in x → 200 celle
- 41 nodi in y → 40 celle
- 2 nodi in z → 1 cella
Totale:
200 × 40 × 1 = 8000 celle.
6. Generare la mesh con Gmsh
Eseguire:
gmsh -3 geometry/channel.geo \
-format msh2 \
-o geometry/channel.msh
Verificare che il file esista:
ls -lh geometry/channel.msh
Il formato msh2 è usato deliberatamente perché è semplice da gestire per gmshToFoam e conserva i nomi fisici necessari per la mappatura delle patch.
7. Importare la mesh in OpenFOAM
Eseguire:
gmshToFoam geometry/channel.msh | tee log.gmshToFoam
L’importazione validata ha riportato:
Vertices to be read: 16482
Cells to be read: 24480
Cells:
total:8000
hex :8000
prism:0
pyr :0
tet :0
Patch 0 gets name frontAndBack
Patch 1 gets name walls
Patch 2 gets name outlet
Patch 3 gets name inlet
Writing zone 0 to cellZone fluid and cellSet
End
I nomi delle Physical Surface di Gmsh vengono quindi trasferiti ai nomi delle patch di OpenFOAM.
8. Correggere i tipi di patch di OpenFOAM
Dopo gmshToFoam i gruppi di superfici importati sono di norma patch generiche. Il solutore richiede:
walls→wallfrontAndBack→empty
Creare system/changeDictionaryDict:
FoamFile
{
format ascii;
class dictionary;
object changeDictionaryDict;
}
boundary
{
walls
{
type wall;
}
frontAndBack
{
type empty;
}
}
Applicare le modifiche:
changeDictionary
9. Verificare la mesh importata
Eseguire:
checkMesh | tee log.checkMesh
Estrarre i valori principali:
grep -E \
"points:|faces:|cells:|hexahedra:|max aspect ratio|Mesh OK" \
log.checkMesh
Risultato validato:
points: 16482
faces: 32240
cells: 8000
hexahedra: 8000
Mesh OK.
Verificare il file boundary:
grep -A6 -E \
"inlet|outlet|walls|frontAndBack" \
constant/polyMesh/boundary
Tipi di patch operativi attesi:
frontAndBack type empty
walls type wall
outlet type patch
inlet type patch
10. File fisici e numerici
I seguenti file sono volutamente identici a quelli del caso 01:
0/U0/pconstant/physicalPropertiesconstant/momentumTransportsystem/controlDictsystem/fvSchemessystem/fvSolution
Questo è essenziale per il confronto: il generatore di mesh è l’unica variazione voluta.
I valori fisici principali restano:
Uinlet = (0.01 0 0) m/s
nu = 1e-6 m^2/s
simulationType = laminar
Il calcolo stazionario usa solver incompressibleFluid; e endTime 2000;, esattamente come nel caso 01.
11. Eseguire il calcolo CFD
Eseguire:
foamRun | tee log.foamRun
Esaminare la parte finale del log:
tail -40 log.foamRun
Il calcolo deve terminare normalmente con:
End
12. Ricreare il campionamento del profilo di velocità
Creare system/graphUniform:
FoamFile
{
format ascii;
class dictionary;
object graphUniform;
}
start (0.45 -0.004875 0.0005);
end (0.45 0.004875 0.0005);
nPoints 40;
fields (U);
axis y;
#includeEtc "caseDicts/postProcessing/graphs/graphUniform.cfg"
Eseguire:
foamPostProcess \
-solver incompressibleFluid \
-func graphUniform \
-latestTime
Individuare l’output:
find postProcessing/graphUniform -name line.xy
File atteso:
postProcessing/graphUniform/2000/line.xy
13. Validazione analitica quantitativa
Eseguire lo stesso confronto usato per il benchmark con blockMesh:
f=$(find postProcessing/graphUniform -name line.xy | sort -V | tail -1)
awk '
BEGIN {
H=0.01
Umean=0.01
UmaxTheory=1.5*Umean
umax=-1
}
$1 !~ /^#/ && NF >= 2 {
y=$1
u=$2
uth=UmaxTheory*(1-(2*y/H)^2)
e=u-uth
ae=(e<0 ? -e : e)
sumsq += e*e
n++
if (ae > maxerr)
maxerr=ae
if (u > umax)
umax=u
}
END {
rmse=sqrt(sumsq/n)
printf "Points : %d\n",n
printf "CFD maximum Ux : %.10f m/s\n",umax
printf "Theoretical Umax : %.10f m/s\n",UmaxTheory
printf "RMSE : %.10e m/s\n",rmse
printf "Normalized RMSE : %.6f %%\n",100*rmse/UmaxTheory
printf "Maximum abs error : %.10e m/s\n",maxerr
printf "Max error / Umax : %.6f %%\n",100*maxerr/UmaxTheory
}' "$f"
Risultato validato con Gmsh:
Points : 40
CFD maximum Ux : 0.0149625470 m/s
Theoretical Umax : 0.0150000000 m/s
RMSE : 2.2551524240e-05 m/s
Normalized RMSE : 0.150343 %
Maximum abs error : 2.8078000000e-05 m/s
Max error / Umax : 0.187187 %
14. Confronto diretto con il benchmark blockMesh
| Percorso di generazione della mesh | Celle | Umax [m/s] | NRMSE [%] |
|---|---|---|---|
blockMesh – caso 01 | 8000 | 0,0149625470 | 0,150343 |
Gmsh -> gmshToFoam – caso 03 | 8000 | 0,0149625470 | 0,150343 |
I valori di RMSE differiscono soltanto di circa
5,31 × 10−12 m/s,
una differenza trascurabile per questo benchmark. Le due soluzioni CFD sono numericamente indistinguibili.
Ciò conferma che la soluzione non è legata a blockMesh: una mesh strutturata equivalente importata da Gmsh produce lo stesso risultato fisico.
15. Visualizzazione in ParaView
Creare un file marcatore:
cd $FOAM_RUN/03_gmsh_channel
touch gmsh_channel.foam
Se pvserver è già in esecuzione e il client è connesso, non serve riconnettersi. In ParaView:
File -> Open
e aprire:
$FOAM_RUN/03_gmsh_channel/gmsh_channel.foam
Selezionare internalMesh, abilitare U e p, quindi premere Apply.
Visualizzazione consigliata:
Representation = Surface With Edges
Coloring = U -> Magnitude
L’immagine seguente mostra il campo di velocità validato sulla mesh importata da Gmsh.

16. Controllo delle linee di corrente
Si possono usare le stesse impostazioni dello Stream Tracer del caso 01:
| Proprietà | Valore |
|---|---|
| Vectors | U |
| Integration Direction | FORWARD |
| Seed Type | Line |
| Point1 | (0.001, -0.0045, 0.0005) |
| Point2 | (0.001, +0.0045, 0.0005) |
| Resolution | 20 |
| Maximum Streamline Length | 0,5 m |
Le linee di corrente devono restare rette e parallele all’asse del canale, come nella soluzione ottenuta con blockMesh.
17. Risoluzione dei problemi più comuni
| Sintomo | Controllo / correzione |
|---|---|
| i nomi fisici di Gmsh non compaiono come patch di OpenFOAM | assicurarsi che le superfici siano dichiarate come gruppi Physical Surface |
| il comportamento dell’importazione è incoerente | generare un file MSH 2.2 ASCII con -format msh2 |
walls resta una patch generica | eseguire changeDictionary con il dizionario fornito |
frontAndBack resta una patch generica | impostarne il tipo a empty prima del calcolo CFD |
il numero di celle di checkMesh non è 8000 | verificare il numero di nodi transfinite: 201, 41 e 2 |
| ParaView non mostra il file del caso | crearlo con touch gmsh_channel.foam |
| non compare nessun file di post-processing | usare foamPostProcess -solver incompressibleFluid -func graphUniform -latestTime |
18. Criteri di accettazione
| Criterio di accettazione | Stato |
|---|---|
| Gmsh genera la mesh di volume strutturata | SUPERATO |
gmshToFoam importa tutte le patch con nome | SUPERATO |
| la mesh importata contiene 8000 esaedri | SUPERATO |
checkMesh riporta Mesh OK | SUPERATO |
walls è wall e frontAndBack è empty | SUPERATO |
| il calcolo OpenFOAM converge normalmente | SUPERATO |
| il profilo di velocità concorda con la teoria di Poiseuille | SUPERATO |
| NRMSE = 0,150343 % | SUPERATO |
| il risultato con Gmsh coincide con il benchmark blockMesh | SUPERATO |
| la visualizzazione in ParaView è corretta | SUPERATO |
Stato finale: CASO 03 COMPLETATO E VALIDATO.
19. Lista di controllo per la riproducibilità
- Verificare Gmsh e le utility di OpenFOAM.
- Creare
03_gmsh_channel. - Copiare dal caso 01 i file fisici/numerici validati.
- Creare il file completo
geometry/channel.geo. - Generare
geometry/channel.mshin formato MSH 2.2. - Importare con
gmshToFoam. - Creare
changeDictionaryDicted eseguirechangeDictionary. - Eseguire
checkMeshe verificare 8000 esaedri piùMesh OK. - Eseguire
foamRun. - Creare
graphUniforme campionare il profilo di velocità finale. - Confrontare il profilo campionato con la soluzione analitica di Poiseuille.
- Creare
gmsh_channel.foam. - Aprire il caso tramite la connessione client-server di ParaView già esistente.
- Verificare U, p, gli spigoli della mesh e le linee di corrente.
Il caso dimostra un flusso di lavoro completo con mesh esterna, mantenendo la stessa fisica CFD validata del caso di riferimento.
Caso precedente: caso 02 – studio di convergenza della mesh. Caso successivo: caso 04 – mesh parametrica con Gmsh e Python, dove la stessa geometria viene generata da uno script Python parametrico.