OpenFOAM: condizioni al contorno e modelli fisici in un caso completo
Settima guida operativa su OpenFOAM installato su un server Linux e usato da Windows, dopo quelle su server e trasferimenti, monitoraggio e convergenza, analisi quantitativa con ParaView, verifica e validazione, campagne parametriche e generazione della mesh. Questa collega la mesh al problema fisico: condizioni al contorno, proprietà e modelli in un caso completo. I riquadri di approfondimento si aprono con un clic: contengono tutti i file del pacchetto, la soluzione analitica di riferimento del condotto con i valori numerici attesi e le procedure di verifica.
Dalla mesh a un caso CFD completo, verificabile e riproducibile
Manuale operativo · OpenFOAM Foundation 14 · calcolo su Linux e lavoro quotidiano da Windows
Edizione: ottobre 2026 · Livello: intermedio · Licenza degli esempi originali: GNU GPL v3 o successiva.
Obiettivo. Imparare a collegare dominio, proprietà fisiche, condizioni iniziali e al contorno, algoritmo numerico e osservabili. Il pacchetto contiene un caso completo e tre modalità di esecuzione, conservando sempre i template originali. I riquadri di richiamo possono essere ignorati dal lettore che già conosce la fluidodinamica.

1. Dalla mesh al problema fisico
Una mesh valida non definisce, da sola, una simulazione: occorre scegliere quale equazione risolvere, quale fluido modellare, quali valori imporre sulle patch e come riconoscere un risultato credibile. Le condizioni al contorno non sono un dettaglio informatico: fanno parte dell’esperimento numerico.
Un caso OpenFOAM dispone di 0/ (campi e condizioni iniziali), constant/ (proprietà e modelli fisici), system/ (controlli, discretizzazione, soluzione) e di constant/polyMesh/ dopo la generazione della mesh. In questa guida il caso è stazionario, laminare, incomprimibile e isotermo, quindi non richiede campi di temperatura o turbolenza.
Richiamo — equazioni. Per un fluido incomprimibile newtoniano a densità costante, la continuità richiede ∇·U = 0. Il bilancio della quantità di moto comprende trasporto convettivo, gradiente di pressione e viscosità. La definizione del problema dipende anche dai valori imposti al contorno, non soltanto dal solver.
Approfondimento · Le equazioni che il modulo incompressibleFluid risolve davvero
Per un fluido newtoniano a densità costante, in forma cinematica (divisa per ρ):
∇·U = 0
∂U/∂t + ∇·(U U) = −∇p + ∇·[ ν_eff (∇U + (∇U)ᵀ) ]
p = p_fisica / ρ [m²/s²]
ν_eff = ν (laminare) oppure ν + ν_t (RAS/LES)
Due conseguenze pratiche. Primo: il solver non conosce ρ, quindi né la pressione né le forze escono in unità SI finché non le si moltiplica per una densità dichiarata. Secondo: nel caso incomprimibile la pressione non è una variabile termodinamica ma il moltiplicatore che impone ∇·U = 0; per questo è definita a meno di una costante, ed è il motivo per cui serve almeno un valore di riferimento (qui p = 0 all’uscita). Il termine div((nuEff*dev2(T(grad(U))))) presente in fvSchemes corrisponde alla parte trasposta dello sforzo viscoso.
Approfondimento · Problema ben posto: quante condizioni servono e dove
Per le equazioni di Navier–Stokes incomprimibili, su ogni porzione di contorno si prescrive in genere o la velocità (ingressi, pareti) o una condizione «di trazione», che in pratica OpenFOAM approssima imponendo la pressione e un gradiente nullo di velocità (uscite). La combinazione corretta per un condotto è quindi: velocità o portata imposta all’ingresso con gradiente nullo di pressione; pressione imposta all’uscita con gradiente nullo di velocità; aderenza sulle pareti. Imporre sia velocità sia pressione sulla stessa patch sovradetermina il problema; non imporre la pressione da nessuna parte lo lascia indeterminato.
Approfondimento · Perché laminare: i numeri adimensionali del caso
Nei condotti la transizione al moto turbolento avviene, in condizioni ordinarie, intorno a Re ≈ 2000–2300 basato sul diametro idraulico; con ingressi molto disturbati può anticipare, con ingressi molto curati può ritardare di molto. Con Re ≈ 133 il caso è laminare con ampio margine. Il secondo numero da guardare è il rapporto fra lunghezza del canale e lunghezza d’ingresso, L/L_e ≈ 0,20/0,044 ≈ 4,5: il moto si sviluppa e resta sviluppato per circa i tre quarti del canale, condizione necessaria per confrontarlo con la soluzione analitica.
2. Versione di OpenFOAM e compatibilità dei dizionari
Questa guida è costruita per OpenFOAM Foundation 14, non per le release OpenCFD/ESI. Nella Foundation 14 il modulo incompressibleFluid è selezionato tramite solver incompressibleFluid; in system/controlDict ed è eseguito con foamRun. La viscosità cinematica del nostro esempio è in constant/physicalProperties (viscosityModel constant; e nu); il tipo di trasporto della quantità di moto in constant/momentumTransport (simulationType laminar;). Copiare alla cieca un tutorial con transportProperties, turbulenceProperties, application simpleFoam oppure un nome di solver diverso può significare copiare una sintassi di altra versione.
Verifica sul server con:
source /opt/OpenFOAM-14/etc/bashrc
foamVersion
command -v foamRun
Regola: se si cambia versione, partire dal tutorial fornito con quella versione, confrontare i dizionari e verificare il log. I file della guida sono originali e non modificano l’installazione di OpenFOAM.
Approfondimento · Foundation e OpenCFD: la tabella delle differenze che si incontrano subito
| Aspetto | OpenFOAM Foundation 14 | Release OpenCFD/ESI e Foundation datate |
|---|---|---|
| lancio | foamRun con solver incompressibleFluid; | simpleFoam, pimpleFoam (application) |
| viscosità | constant/physicalProperties | constant/transportProperties |
| turbolenza | constant/momentumTransport | constant/turbulenceProperties |
| algoritmo | dizionario PIMPLE anche per casi stazionari | SIMPLE / PIMPLE |
| versione | foamVersion → 14 | foamVersion oppure intestazione del log |
Il controllo più rapido è leggere l’intestazione di un log qualsiasi e confrontare i nomi dei file con quelli del tutorial installato. Un dizionario in più non sempre produce errore: viene semplicemente ignorato, e questo è il caso più insidioso perché si crede di aver impostato qualcosa che il solver non legge.
ls "$FOAM_TUTORIALS/incompressibleFluid"
ls "$FOAM_TUTORIALS/incompressibleFluid/pitzDailySteady/constant" "$FOAM_TUTORIALS/incompressibleFluid/pitzDailySteady/system"
foamDictionary "$FOAM_TUTORIALS/incompressibleFluid/pitzDailySteady/system/fvSolution" -keywords
Approfondimento · Convertire un caso simpleFoam in un caso Foundation 14
| Nel caso di partenza | In Foundation 14 |
|---|---|
application simpleFoam; | solver incompressibleFluid; e lancio con foamRun |
constant/transportProperties: transportModel Newtonian; nu …; | constant/physicalProperties: viscosityModel constant; nu …; |
constant/turbulenceProperties | constant/momentumTransport |
dizionario SIMPLE in fvSolution | dizionario PIMPLE (vedere pitzDailySteady) |
postProcess -func … | foamPostProcess -func … |
La conversione non si fa a memoria: si copia il tutorial equivalente della versione 14 e vi si trasferiscono mesh, campi e valori del caso originale, controllando ogni dizionario con foamDictionary -keywords. Il log del primo lancio va letto per intero alla ricerca di avvisi su voci ignorate.
3. Geometria e numeri del caso completo
L’esempio è un canale rettangolare 3D lungo 0,20 m, alto 0,010 m e profondo 0,005 m. La mesh ha 80 × 16 × 8 = 10.240 celle e tre patch: inlet, outlet e walls. Il fluido è un liquido ideale di esercitazione con ν = 1 × 10⁻⁶ m²/s; al primo ingresso si impone U = (0,020, 0, 0) m/s. La densità ρ = 1000 kg/m³ è usata soltanto per convertire la pressione cinematica in pascal quando si analizzano i risultati: non è un parametro richiesto dal solver incomprimibile a viscosità cinematica costante.
| Grandezza | Valore | Implicazione |
|---|---|---|
| Area trasversale A | 5 × 10⁻⁵ m² | A = altezza × profondità |
| Portata teorica Q | 1 × 10⁻⁶ m³/s | Q = A · U media all’ingresso |
| Diametro idraulico Dₕ | 6,667 × 10⁻³ m | Dₕ = 2ab/(a+b) |
| Reynolds basato su Dₕ | circa 133,3 | Re = U Dₕ / ν |
| Tempo convettivo L/U | 10 s | Indicazione fisica; non è il deltaT di un caso stazionario |
Il canale è volutamente semplice: permette di isolare gli effetti delle condizioni al contorno e della viscosità. Non confondere un confronto didattico con una validazione sperimentale o con l’indipendenza dalla mesh (Guida 4).
Approfondimento · La soluzione analitica del condotto rettangolare: i numeri da aspettarsi
Per moto laminare completamente sviluppato in un condotto rettangolare esiste una soluzione in serie (Shah e London). Con sezione 10 mm × 5 mm (rapporto 0,5) il prodotto fra fattore d’attrito di Darcy e Reynolds vale f_D·Re ≈ 62,19 (Fanning 15,55). Da qui:
| Grandezza | ν = 1·10⁻⁶ m²/s | ν = 2·10⁻⁶ m²/s |
|---|---|---|
| Re (Dₕ) | 133,3 | 66,7 |
| f_D | 0,466 | 0,933 |
| gradiente di pressione sviluppato | ≈ 14,0 Pa/m | ≈ 28,0 Pa/m |
| Δp su 0,20 m, solo tratto sviluppato | ≈ 2,80 Pa (2,80·10⁻³ m²/s²) | ≈ 5,60 Pa (5,60·10⁻³ m²/s²) |
| velocità massima al centro | ≈ 0,0398 m/s (1,99·U) | ≈ 0,0398 m/s |
| lunghezza d’ingresso ≈ 0,05·Re·Dₕ | ≈ 44 mm (22 % del canale) | ≈ 22 mm |
Con ingresso uniforme la Δp calcolata sarà maggiore del valore sviluppato: il tratto d’ingresso aggiunge una caduta concentrata dovuta all’accelerazione del nucleo e agli sforzi di parete più alti. Per un confronto pulito con la teoria si misura il gradiente fra due sezioni nel tratto sviluppato, per esempio x = 0,10 m e x = 0,18 m, non fra ingresso e uscita. Questo è un vero controllo di verifica, molto più informativo del solo «STATUS OK».
# gradiente sviluppato e Δp attesi (Python, solo libreria standard)
fRe, Dh, U, nu, rho, L = 62.19, 2*0.01*0.005/0.015, 0.02, 1e-6, 1000, 0.08
Re = U*Dh/nu
dpdx = fRe/Re/Dh * 0.5*rho*U**2 # Pa/m
print(Re, dpdx, dpdx*L, dpdx*L/rho) # Re, Pa/m, Pa on L, m2/s2 on L
Approfondimento · La mesh del caso e la risoluzione del profilo
Le celle misurano 2,5 mm in x e 0,625 mm in y e z: rapporto d’aspetto 4, allineato con la direzione del moto, dove i gradienti sono deboli. Sul lato corto (5 mm) ci sono 8 celle, sul lato lungo (10 mm) 16: per un profilo quasi parabolico è una risoluzione sufficiente a cogliere la forma, ma un confronto quantitativo con la velocità massima (1,99·U) o con il gradiente di pressione richiede lo studio di convergenza della Guida 4, per esempio con 40×8×4, 80×16×8 e 160×32×16 celle (rapporto r = 2).
Approfondimento · Perché questo canale non si può ridurre a 2D
La tentazione di risparmiare celle con un modello bidimensionale qui porta a errori grossolani, perché la profondità (5 mm) è minore dell’altezza (10 mm) e le pareti laterali dominano l’attrito. Gradiente di pressione sviluppato a parità di velocità media (0,02 m/s, μ = 10⁻³ Pa·s):
| Modello | Formula | Gradiente |
|---|---|---|
| condotto rettangolare 3D | f_D·Re = 62,19 | ≈ 14,0 Pa/m |
| lastre piane, gioco 10 mm (2D x–y) | 12·μ·U / h² | 2,4 Pa/m |
| lastre piane, gioco 5 mm (2D x–z) | 12·μ·U / h² | 9,6 Pa/m |
Un modello 2D è lecito solo quando la dimensione trascurata è molto maggiore delle altre (rapporto d’aspetto della sezione di almeno 8–10) o quando il problema è davvero piano per simmetria. È una decisione di modellazione da giustificare con numeri, non una scorciatoia di calcolo.
4. Le patch: un contratto fra mesh e campi
La patch inlet in system/blockMeshDict deve chiamarsi esattamente inlet anche in 0/U e 0/p; analogamente outlet e walls. La patch geometrica ha un tipo (patch, wall, symmetryPlane, ecc.); la condizione del campo ha un altro tipo (fixedValue, zeroGradient, noSlip, ecc.). Non sono intercambiabili.

| Condizione | Significato | Uso frequente e limite |
|---|---|---|
fixedValue | Impone il valore del campo (Dirichlet) | Velocità di ingresso, riferimento di pressione |
zeroGradient | Gradiente normale nullo (Neumann omogenea) | Pressione all’ingresso; U su uscite semplici |
noSlip | Velocità nulla sulla parete fissa | Pareti viscose; non va confusa con slip |
flowRateInletVelocity | Impone portata e ricava la velocità normale | Utile se si conosce Q anziché U |
pressureInletOutletVelocity | Comportamento della velocità compatibile con una patch a pressione | Può gestire il flusso inverso; non elimina da sola una cattiva uscita |
inletOutlet | Uscita con valore di ingresso in caso di ritorno | Spesso per scalari trasportati; specificare inletValue |
symmetryPlane / empty | Vincolo geometrico speciale | Necessita coerenza con la patch nella mesh; empty solo per geometria realmente 2D |
Attenzione: zeroGradient non significa pressione nulla né portata prescritta. Un’uscita troppo vicina a una scia può alterare la soluzione: cambiare condizione non sostituisce uno studio dell’estensione del dominio.
Approfondimento · Tipo di patch e tipo di condizione: chi vince e perché
Il tipo geometrico scritto in constant/polyMesh/boundary vincola le condizioni ammesse. Le patch empty, symmetryPlane, wedge e cyclic sono vincolate: nei campi il tipo deve essere lo stesso, altrimenti il solver si ferma. Il tipo wall non impone nulla ai campi di velocità e pressione, ma è necessario a funzioni che cercano le pareti: distanza dalla parete, funzioni di parete dei modelli RAS, calcolo di y⁺ e di wallShearStress. Una parete dichiarata patch funziona in laminare ma rompe silenziosamente un caso turbolento.
foamDictionary constant/polyMesh/boundary -entry walls/type -value 2>/dev/null || grep -A3 walls constant/polyMesh/boundary
foamDictionary 0/U -entry boundaryField -keywords
foamDictionary 0/p -entry boundaryField -keywords
Approfondimento · Ingresso e uscita in situazioni reali: le condizioni più usate
| Situazione | U | p | Nota |
|---|---|---|---|
| portata nota | flowRateInletVelocity | zeroGradient | profilo uniforme o, nelle versioni recenti, opzione profile |
| profilo misurato | fixedValue non uniforme o tabellato | zeroGradient | il profilo deve rispettare la portata |
| serbatoio a pressione nota | pressureInletOutletVelocity | totalPressure | la portata diventa un risultato |
| uscita con possibile riflusso | inletOutlet (inletValue (0 0 0)) | fixedValue / totalPressure | evita che il riflusso entri con velocità arbitraria |
| parete mobile | movingWallVelocity / fixedValue | zeroGradient | lid-driven cavity, nastri |
| parete senza attrito | slip | zeroGradient | solo dove l’attrito è davvero trascurabile |
I nomi esatti e le voci richieste da ciascuna condizione si controllano nella documentazione API della propria versione o con foamInfo (Parte 8 del percorso).
foamInfo flowRateInletVelocity
foamInfo pressureInletOutletVelocity
foamInfo totalPressure
Approfondimento · Condizioni dipendenti dal tempo e condizioni miste
Per un ingresso che varia nel tempo (avvio graduale, pulsazione) si usa una condizione che legge una funzione del tempo, per esempio una tabella interpolata; per condizioni di tipo Robin, che combinano valore e gradiente, esiste mixed. Esempio di rampa d’ingresso in 10 s, da usare solo in un caso transitorio:
inlet
{
type uniformFixedValue;
uniformValue table
(
(0 (0 0 0))
(10 (0.02 0 0))
);
}
La sintassi delle funzioni del tempo (table, tableFile, sine, …) si controlla con foamInfo uniformFixedValue. In un caso stazionario una rampa non ha senso fisico: il «tempo» conta le iterazioni.
Approfondimento · Riferimento di pressione senza patch a pressione imposta
Se nessuna patch impone la pressione (per esempio un dominio chiuso, o un condotto con ingresso e uscita entrambi a velocità imposta), il livello di pressione è indeterminato e va fissato in una cella. Nel dizionario dell’algoritmo si aggiungono pRefCell (o pRefPoint) e pRefValue. In un dominio chiuso la portata netta imposta deve essere esattamente nulla, altrimenti l’equazione della pressione non ha soluzione.
PIMPLE
{
// ...
pRefPoint (0.1 0.005 0.0025);
pRefValue 0;
}
5. Condizioni iniziali e al contorno: 0/U e 0/p
Nel template il file 0/U contiene velocità interna inizialmente nulla e un ingresso uniforme:
dimensions [0 1 -1 0 0 0 0];
internalField uniform (0 0 0);
boundaryField
{
inlet { type fixedValue; value uniform (0.02 0 0); }
outlet { type zeroGradient; }
walls { type noSlip; }
}
Il file 0/p impone una pressione cinematica di riferimento all’uscita:
dimensions [0 2 -2 0 0 0 0];
internalField uniform 0;
boundaryField
{
inlet { type zeroGradient; }
outlet { type fixedValue; value uniform 0; }
walls { type zeroGradient; }
}
Il p=0 all’uscita fissa la scala di riferimento della pressione in questo problema, non la pressione assoluta del liquido. Per il solver incomprimibile selezionato vale la relazione Δp_fisica = ρ · Δp_cinematica: i valori letti nel campo p sono in m²/s², non in pascal.
Richiamo — condizioni iniziali. In un calcolo stazionario, internalField uniform (0 0 0) è soltanto la prima ipotesi iterativa. Non rappresenta necessariamente una condizione fisica a t = 0. Una cattiva inizializzazione può rallentare o compromettere la convergenza anche senza cambiare l’eventuale soluzione stazionaria.
Approfondimento · I file del caso completi: 0/U e 0/p
/* OpenFOAM Foundation 14 - Guide 7 (original educational case) */
FoamFile
{
version 2.0;
format ascii;
class volVectorField;
location "0";
object U;
}
dimensions [0 1 -1 0 0 0 0];
internalField uniform (0 0 0);
boundaryField
{
inlet
{
type fixedValue;
value uniform (0.02 0 0);
}
outlet
{
type zeroGradient;
}
walls
{
type noSlip;
}
}
/* OpenFOAM Foundation 14 - Guide 7 (original educational case) */
FoamFile
{
version 2.0;
format ascii;
class volScalarField;
location "0";
object p;
}
// Incompressible kinematic pressure: p_phys/rho, in m2/s2.
dimensions [0 2 -2 0 0 0 0];
internalField uniform 0;
boundaryField
{
inlet
{
type zeroGradient;
}
outlet
{
type fixedValue;
value uniform 0;
}
walls
{
type zeroGradient;
}
}
Le dimensioni seguono l’ordine SI di OpenFOAM [massa lunghezza tempo temperatura quantità corrente intensità]: [0 1 -1 0 0 0 0] sono m/s, [0 2 -2 0 0 0 0] m²/s². OpenFOAM verifica la coerenza dimensionale di ogni operazione: una pressione scritta in Pa [1 -1 -2 0 0 0 0] in un solver cinematico produce un errore di dimensioni, non un risultato sbagliato silenzioso.
Approfondimento · Inizializzare meglio: potentialFoam e campi non uniformi
Partire da velocità nulla è lecito ma lento: le prime iterazioni devono «inventare» un campo che rispetti la continuità. Due alternative pratiche. La prima è inizializzare il campo interno con la velocità media, internalField uniform (0.02 0 0);, che rispetta già la portata. La seconda è risolvere un flusso potenziale che soddisfa ∇·U = 0 con le stesse condizioni al contorno e usarlo come campo iniziale:
potentialFoam -help
potentialFoam -writep > logs/log.potentialFoam 2>&1 # scrive U (e p) in 0/
potentialFoam sovrascrive i campi in 0/: va eseguito sulla copia di lavoro, mai sul template, e il fatto va annotato. Nei casi stazionari la soluzione finale non deve dipendere dall’inizializzazione; se dipende, il problema ha più soluzioni o non è convergente.
Approfondimento · Ingresso uniforme o profilo sviluppato: conseguenze sulle misure
L’ingresso uniforme del template è una scelta di comodo: genera un tratto di sviluppo di circa 44 mm in cui gradiente di pressione e sforzo di parete sono maggiori del valore sviluppato. Se l’obiettivo è il comportamento del canale in una rete, si preferisce imporre il profilo sviluppato oppure allungare il dominio a monte e misurare solo a valle. Un’opzione pratica, nelle versioni che la offrono, è il parametro profile di flowRateInletVelocity (profili di strato limite laminare o turbolento); in ogni caso la scelta va dichiarata nella scheda del caso.
6. Materiale, modello e algoritmo sono livelli diversi
Nel file constant/physicalProperties:
viscosityModel constant;
nu 1e-06;
Nel file constant/momentumTransport:
simulationType laminar;
In system/controlDict, solver incompressibleFluid; sceglie il modulo. In system/fvSchemes, ddtSchemes { default steadyState; } seleziona la discretizzazione stazionaria. In system/fvSolution, PIMPLE imposta la procedura di accoppiamento pressione-velocità, mentre solvers e relaxationFactors controllano gli algoritmi lineari e il rilassamento. Sono tre scelte indipendenti: fisica, modello di trasporto e numerica.
Perché qui non attivare direttamente kOmegaSST? Un modello RAS richiede campi appropriati (k, omega, nut), condizioni sulle pareti, intensità/lunghezze di scala coerenti e una mesh adeguata vicino alla parete. Non basta cambiare una riga. Inoltre, per questo esempio Re ≈ 133, un modello turbolento non è giustificato dalla sola dimensione del canale.
Approfondimento · I dizionari constant/ e system/ completi
/* OpenFOAM Foundation 14 - Guide 7 (original educational case) */
FoamFile
{
version 2.0;
format ascii;
class dictionary;
location "constant";
object physicalProperties;
}
viscosityModel constant;
nu 1e-06; // m2/s; example fluid
/* OpenFOAM Foundation 14 - Guide 7 (original educational case) */
FoamFile
{
version 2.0;
format ascii;
class dictionary;
location "constant";
object momentumTransport;
}
simulationType laminar;
/* OpenFOAM Foundation 14 - Guide 7 (original educational case) */
FoamFile
{
version 2.0;
format ascii;
class dictionary;
location "system";
object controlDict;
}
solver incompressibleFluid;
startFrom startTime;
startTime 0;
stopAt endTime;
endTime 700;
deltaT 1; // pseudo-time iteration for steadyState
writeControl timeStep;
writeInterval 100;
purgeWrite 0;
writeFormat ascii;
writePrecision 9;
writeCompression off;
timeFormat general;
timePrecision 6;
runTimeModifiable true;
functions
{
inletFlux
{
type surfaceFieldValue;
libs ("libfieldFunctionObjects.so");
log true;
writeControl writeTime;
writeFields false;
patch inlet;
operation sum;
fields (phi);
}
outletFlux
{
type surfaceFieldValue;
libs ("libfieldFunctionObjects.so");
log true;
writeControl writeTime;
writeFields false;
patch outlet;
operation sum;
fields (phi);
}
}
/* OpenFOAM Foundation 14 - Guide 7 (original educational case) */
FoamFile
{
version 2.0;
format ascii;
class dictionary;
location "system";
object fvSchemes;
}
ddtSchemes
{
default steadyState;
}
gradSchemes
{
default Gauss linear;
}
divSchemes
{
default none;
div(phi,U) bounded Gauss linearUpwind grad(U);
div((nuEff*dev2(T(grad(U))))) Gauss linear;
}
laplacianSchemes
{
default Gauss linear corrected;
}
interpolationSchemes
{
default linear;
}
snGradSchemes
{
default corrected;
}
wallDist
{
method meshWave;
}
/* OpenFOAM Foundation 14 - Guide 7 (original educational case) */
FoamFile
{
version 2.0;
format ascii;
class dictionary;
location "system";
object fvSolution;
}
solvers
{
p
{
solver GAMG;
smoother DIC;
tolerance 1e-8;
relTol 0.05;
}
pFinal
{
$p;
relTol 0;
}
U
{
solver smoothSolver;
smoother symGaussSeidel;
tolerance 1e-10;
relTol 0.1;
}
UFinal
{
$U;
relTol 0;
}
}
PIMPLE
{
momentumPredictor no;
nOuterCorrectors 1;
nNonOrthogonalCorrectors 0;
}
relaxationFactors
{
fields { p 0.3; }
equations { U 0.7; }
}
/* OpenFOAM Foundation 14 - Guide 7 (original educational case) */
FoamFile
{
version 2.0;
format ascii;
class dictionary;
location "system";
object blockMeshDict;
}
scale 1;
vertices
(
(0 0 0) (0.20 0 0) (0.20 0.01 0) (0 0.01 0)
(0 0 0.005) (0.20 0 0.005) (0.20 0.01 0.005) (0 0.01 0.005)
);
blocks ( hex (0 1 2 3 4 5 6 7) (80 16 8) simpleGrading (1 1 1) );
edges ();
boundary
(
inlet
{
type patch;
faces ((0 4 7 3));
}
outlet
{
type patch;
faces ((1 2 6 5));
}
walls
{
type wall;
faces ((0 1 5 4) (3 7 6 2) (0 3 2 1) (4 5 6 7));
}
);
mergePatchPairs ();
Approfondimento · fvSchemes e fvSolution del caso, voce per voce
| Voce | Scelta | Motivazione ingegneristica |
|---|---|---|
ddtSchemes | steadyState | nessun termine temporale: «Time» conta le iterazioni |
div(phi,U) | bounded Gauss linearUpwind grad(U) | secondo ordine upwind; bounded rimuove la parte di ∇·U non nulla durante la convergenza, tipico degli stazionari |
laplacianSchemes | Gauss linear corrected | mesh ortogonale: correzione non-ortogonale inattiva ma pronta |
wallDist meshWave | — | non usato in laminare; necessario con i modelli RAS |
p: GAMG + DIC | tolerance 1e-8, relTol 0.05 | multigrid per l’equazione ellittica della pressione |
U: smoothSolver symGaussSeidel | relTol 0.1 | equazione dominata dalla diagonale, basta un risolutore semplice |
pFinal / UFinal | relTol 0 | impostazioni dell’ultima correzione del ciclo PIMPLE |
momentumPredictor no | — | riduce il costo per iterazione nei casi a basso Reynolds |
relaxationFactors | p 0.3, U 0.7 | valori classici dell’algoritmo SIMPLE per la stabilità |
Le parti 5 e 6 del percorso spiegano in dettaglio schemi e solutori. Per sapere quali voci la propria installazione usa nei casi stazionari, confrontare con il tutorial pitzDailySteady: è il riferimento più sicuro per le parole chiave della versione 14.
Approfondimento · Che cosa servirebbe davvero per passare a un modello RAS
constant/momentumTransport:simulationType RAS;e un sottodizionarioRAS { model kOmegaSST; turbulence on; printCoeffs on; }.- Nuovi campi
0/k,0/omega,0/nut, con condizioni d’ingresso coerenti e funzioni di parete (kqRWallFunction,omegaWallFunction,nutkWallFunctiono equivalenti a bassi Reynolds). - Schemi per
div(phi,k)ediv(phi,omega), solutori e rilassamento perkeomega. - Una mesh di parete coerente con il trattamento scelto (Guida 6, y⁺).
Valori d’ingresso tipici: k = 1,5·(I·U)² con intensità I di qualche percento e ω = √k / (C_μ^¼·ℓ) con C_μ = 0,09 e scala ℓ legata al diametro idraulico (per esempio 0,07·Dₕ). A Re ≈ 133 nessuno di questi è giustificato: un modello di turbolenza in un flusso laminare aggiunge viscosità fittizia e falsa la Δp. Il punto di partenza corretto, quando servirà, è il tutorial RAS della propria installazione.
Approfondimento · Viscosità non newtoniana: dove si imposta e come verificarla
Con fluidi come sangue, polimeri o fanghi la viscosità dipende dalla velocità di deformazione. In Foundation 14 il modello si sceglie in constant/physicalProperties con viscosityModel; i nomi disponibili e i coefficienti richiesti si ricavano dai tutorial installati, non da esempi di altre versioni:
grep -rh --include=physicalProperties "viscosityModel" "$FOAM_TUTORIALS" | sort | uniq -c
grep -rl --include=physicalProperties -E "CrossPowerLaw|BirdCarreau|powerLaw|HerschelBulkley" "$FOAM_TUTORIALS" | head
Con viscosità variabile la soluzione analitica di questa guida non vale più, il profilo si appiattisce (fluidi pseudoplastici) e il numero di Reynolds va definito con una viscosità di riferimento dichiarata. È un passo da fare dopo aver verificato il caso newtoniano.
Approfondimento · Rilassamento e algoritmo: velocità di convergenza contro stabilità
Con i fattori classici p 0,3 e U 0,7 l’algoritmo SIMPLE converge in modo robusto ma lento. La variante «consistente» (SIMPLEC), dove disponibile, permette fattori più alti (per esempio U 0,9 e pressione senza rilassamento) e riduce sensibilmente il numero di iterazioni. Il criterio per cambiare è empirico ma misurabile: confrontare il numero di iterazioni necessario per portare i residui sotto soglia e verificare che la soluzione finale (Δp, Q) non cambi. Le parole chiave della versione 14 si controllano in pitzDailySteady.
7. Preparare la prova da Windows
Il pacchetto contiene:
examples/channel_case/ # template completo
examples/alternatives/ # BC di portata, viscosità alternativa
scripts/run_case.sh # crea nuove copie e avvia OpenFOAM
scripts/preflight.py # controllo statico facoltativo
figures/ # immagini della guida
results/ # viene creata dallo script
Estrai il pacchetto sul PC, modifica i testi con Notepad++ conservando codifica UTF-8 e terminatori Unix LF, poi trasferisci l’intera directory tramite FileZilla SFTP. Apri PowerShell e collega la sessione SSH:
ssh utente@server.example
Nel terminale Linux, dalla cartella che contiene examples/ e scripts/:
source /opt/OpenFOAM-14/etc/bashrc
foamVersion
chmod +x scripts/run_case.sh
./scripts/run_case.sh velocity
Importante: avvia lo script dalla cartella del pacchetto, non da una vecchia directory CFD; lo script calcola comunque il proprio percorso. La cartella results/velocity_..._XXXXXX è creata automaticamente con un nome univoco. I file originali restano intatti. Prima di lanciare casi che durano molto, usa tmux (Guida 1).

Approfondimento · Lo script run_case.sh completo
#!/usr/bin/env bash
# GNU GPL v3 or later - Guide 7. Never changes the shipped template.
set -Eeuo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TEMPLATE="$ROOT/examples/channel_case"
OUTPUT="$ROOT/results"
MODE="${1:-}"
case "$MODE" in
velocity|flowrate|viscosity) ;;
*) echo "Usage: $0 velocity|flowrate|viscosity" >&2; exit 2;;
esac
for executable in blockMesh checkMesh foamRun; do
command -v "$executable" >/dev/null 2>&1 || { echo "Missing $executable: source OpenFOAM-14/etc/bashrc" >&2; exit 127; }
done
mkdir -p "$OUTPUT"
DEST="$(mktemp -d "$OUTPUT/${MODE}_$(date -u +%Y%m%dT%H%M%SZ)_XXXXXX")"
cp -a "$TEMPLATE/." "$DEST/"
mkdir -p "$DEST/logs"
if [[ "$MODE" == flowrate ]]; then
cp "$ROOT/examples/alternatives/U_flowrate" "$DEST/0/U"
elif [[ "$MODE" == viscosity ]]; then
cp "$ROOT/examples/alternatives/physicalProperties_high_nu" "$DEST/constant/physicalProperties"
fi
printf '%s\n' "Mode: $MODE" "Case: $DEST" "UTC: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >"$DEST/logs/RUN_INFO.txt"
CURRENT_STEP=initialisation
onexit() {
status=$?
if (( status == 0 )); then
printf 'OK\n' >"$DEST/logs/STATUS.txt"
else
printf 'FAILED during %s (exit code %s)\n' "$CURRENT_STEP" "$status" >"$DEST/logs/STATUS.txt"
fi
}
trap onexit EXIT
CURRENT_STEP=blockMesh
blockMesh -case "$DEST" >"$DEST/logs/log.blockMesh" 2>&1
CURRENT_STEP=checkMesh
checkMesh -case "$DEST" -allGeometry -allTopology >"$DEST/logs/log.checkMesh" 2>&1
CURRENT_STEP=foamRun
foamRun -case "$DEST" >"$DEST/logs/log.foamRun" 2>&1
printf 'Completed in %s\n' "$DEST"
mktemp -d … _XXXXXXcrea una directory con suffisso casuale: due lanci nello stesso secondo non collidono mai.- La
trap onexit EXITscriveSTATUS.txtin ogni caso, anche quandoset -einterrompe lo script: il file dice in quale fase e con quale codice si è fermato. - Le varianti si ottengono sostituendo un solo file nella copia (
0/Uoconstant/physicalProperties): il template non viene mai toccato. - Codici di uscita: 2 per modalità sconosciuta, 127 se manca un eseguibile OpenFOAM; altrimenti quello dell’utility fallita, riportato in
STATUS.txt.
Approfondimento · Il controllo statico preflight.py
#!/usr/bin/env python3
# GNU GPL v3 or later - static structural checks, no OpenFOAM runtime required.
from pathlib import Path
import re, sys
BASE = Path(__file__).resolve().parents[1]
CASE = BASE/'examples/channel_case'
check = [('0/U','volVectorField','U'),('0/p','volScalarField','p'),('constant/physicalProperties','dictionary','physicalProperties'),('constant/momentumTransport','dictionary','momentumTransport'),('system/blockMeshDict','dictionary','blockMeshDict'),('system/controlDict','dictionary','controlDict'),('system/fvSchemes','dictionary','fvSchemes'),('system/fvSolution','dictionary','fvSolution')]
for relative,kind,name in check:
s=(CASE/relative).read_text()
assert re.search(r'\bclass\s+'+kind+r'\s*;',s),relative
assert re.search(r'\bobject\s+'+name+r'\s*;',s),relative
assert s.count('{') == s.count('}'),relative
assert s.count('(') == s.count(')'),relative
for fname in ('0/U','0/p'):
s=(CASE/fname).read_text()
for patch in ('inlet','outlet','walls'):
assert re.search(r'\b'+patch+r'\s*\{',s),f'{fname} missing patch {patch}'
u=(CASE/'0/U').read_text()
p=(CASE/'0/p').read_text()
assert 'dimensions [0 1 -1 0 0 0 0]' in u
assert 'dimensions [0 2 -2 0 0 0 0]' in p
assert 'solver incompressibleFluid' in (CASE/'system/controlDict').read_text()
A=.01*.005; U=.02; nu=1e-6; D=2*.01*.005/(.01+.005)
print(f'PASS: 8 dictionaries; patches match; 80*16*8=10240 cells; A={A:.8g} m2; Q={A*U:.8g} m3/s; Re_Dh={U*D/nu:.2f}')
print('WARNING: static check only; does not run blockMesh/checkMesh/foamRun.')
Si esegue anche su Windows, prima del trasferimento: verifica classe e nome di ogni dizionario, bilanciamento di parentesi, presenza delle tre patch nei campi, dimensioni di U e p e il solver selezionato, e stampa i valori di riferimento (area, portata, Reynolds). Non sostituisce l’esecuzione: un dizionario sintatticamente corretto può essere fisicamente sbagliato.
python scripts/preflight.py
Approfondimento · Eseguire le tre varianti in sequenza e senza presidio
Le tre esperienze sono indipendenti e brevi: si possono lanciare in sequenza in una sessione tmux, registrando un riepilogo finale. Ogni variante crea la propria directory, quindi un fallimento non ferma le altre se si evita set -e nel ciclo:
tmux new -s guide7
for m in velocity flowrate viscosity; do ./scripts/run_case.sh "$m" || echo "$m failed"; done
for d in results/*/; do printf '%s %s\n' "$d" "$(cat "$d/logs/STATUS.txt")"; done
python3 scripts/collect_fluxes.py results > flux_summary.csv
8. Tre esercitazioni: un parametro alla volta
Esperienza A — ingresso a velocità prescritta. Esegui ./scripts/run_case.sh velocity. Lo script copia il template, esegue blockMesh, poi checkMesh -allGeometry -allTopology, quindi foamRun. I risultati sono salvati nella directory nuova, con logs/log.blockMesh, logs/log.checkMesh, logs/log.foamRun, logs/STATUS.txt e logs/RUN_INFO.txt.
Esperienza B — portata prescritta. Esegui ./scripts/run_case.sh flowrate. Il file 0/U della copia sostituisce l’ingresso con:
inlet
{
type flowRateInletVelocity;
volumetricFlowRate 1e-06;
}
Poiché A = 5×10⁻⁵ m², la velocità media corrispondente è 0,02 m/s. La distribuzione locale sul patch non è necessariamente identica in qualsiasi condizione di flusso, ma il confronto offre un controllo fra due modi di prescrivere lo stesso ingresso nominale. La portata 1e-06 è positiva verso il dominio nella condizione flowRateInletVelocity.
Esperienza C — viscosità raddoppiata. Esegui ./scripts/run_case.sh viscosity: nella copia nu passa a 2×10⁻⁶ m²/s, mentre il campo d’ingresso resta fisso. Il Reynolds scende circa a 66,7. La caduta di pressione può aumentare; la proporzionalità rigorosa con ν appartiene al regime laminare completamente sviluppato e alla stessa geometria, non a qualsiasi campo d’ingresso.

Approfondimento · Le due varianti del pacchetto, complete
/* OpenFOAM Foundation 14 - Guide 7 (original educational case) */
FoamFile
{
version 2.0;
format ascii;
class volVectorField;
location "0";
object U;
}
dimensions [0 1 -1 0 0 0 0];
internalField uniform (0 0 0);
boundaryField
{
inlet
{
type flowRateInletVelocity;
volumetricFlowRate 1e-06; // m3/s (positive into domain)
}
outlet { type zeroGradient; }
walls { type noSlip; }
}
/* OpenFOAM Foundation 14 - Guide 7 (original educational case) */
FoamFile
{
version 2.0;
format ascii;
class dictionary;
location "constant";
object physicalProperties;
}
viscosityModel constant;
nu 2e-06; // m2/s
Con flowRateInletVelocity e profilo uniforme, la velocità imposta è Q/A = 1·10⁻⁶ / 5·10⁻⁵ = 0,02 m/s: identica all’esperienza A. Il confronto A–B è quindi un test di coerenza: le due soluzioni devono coincidere entro la tolleranza dei solutori. Una differenza sensibile indica un errore di area, di segno o di unità.
Approfondimento · Che cosa deve cambiare fra A e C, numeri alla mano
Raddoppiando ν a velocità fissa, nel tratto sviluppato il gradiente di pressione raddoppia (circa 14 → 28 Pa/m), perché f_D = 62,19/Re e Re si dimezza; la lunghezza d’ingresso si dimezza (44 → 22 mm); il profilo sviluppato e quindi la velocità massima restano gli stessi, perché in laminare la forma del profilo non dipende da ν. La Δp totale fra ingresso e uscita raddoppia meno che proporzionalmente, perché il contributo del tratto d’ingresso cresce meno della parte viscosa.
Se il calcolo mostra un rapporto 2,00 fra i gradienti misurati nel tratto sviluppato, il caso è verificato; se mostra un rapporto 2 sulla Δp totale, probabilmente si stanno confrontando grandezze diverse o la soluzione non è convergente.
Approfondimento · Due esperienze in più per completare lo studio
- D — Posizione dell’uscita. Allungare il canale a 0,30 m (vertici e numero di celle in x in proporzione, 120 × 16 × 8) mantenendo tutto il resto: il gradiente misurato fra 0,10 e 0,18 m non deve cambiare. Se cambia, l’uscita influenzava la soluzione.
- E — Raffinamento. Ripetere A con 40 × 8 × 4 e 160 × 32 × 16 celle (r = 2) e applicare la procedura della Guida 4 al gradiente di pressione sviluppato. Il valore estrapolato va confrontato con 14,0 Pa/m: è una verifica completa, numerica e analitica insieme. La mesh più fine ha 81 920 celle, ancora leggera.
9. Controllare la simulazione e i bilanci
L’esito numerico va letto dai log: STATUS.txt = OK significa solo che la procedura è terminata senza errore del processo. Non dimostra la convergenza scientifica. Cerca FOAM FATAL ERROR, i residui finali, gli errori di continuità e la stabilizzazione delle grandezze di interesse. Nei log delle function objects inletFlux e outletFlux, entrambe ottenute con surfaceFieldValue sul campo phi, usa questa convenzione:
Q_inlet < 0 # flusso uscente dalla normale della patch: all'ingresso è negativo
Q_outlet > 0 # alla normale uscente del dominio: all'uscita è positivo
Q_inlet + Q_outlet ≈ 0 # conservazione volumetrica
L’esempio registra misure alle scritture dei campi, tramite system/controlDict. In un sistema incomprimibile phi è un flusso volumetrico (m³/s). Valuta l’errore relativo sul bilancio, ad esempio |Qin + Qout| / max(|Qin|, |Qout|), senza assumere che un numero arbitrario sia una soglia universale. Il campo p può essere mediato su sezioni con ParaView (Guida 3), convertendo successivamente la differenza di pressione in Pa tramite la densità scelta.
Richiamo — convergenza e conservazione. Residui piccoli non assicurano un bilancio corretto né la correttezza delle patch. Al contrario, una piccola differenza di portata non dimostra che il campo di velocità sia sufficientemente accurato. Si verificano entrambe le cose.
Approfondimento · Convergenza di un caso stazionario: criteri automatici e controllo delle grandezze
Il caso esegue 700 iterazioni senza un criterio d’arresto. In un uso serio si aggiunge un controllo sui residui, così che il calcolo si fermi quando i residui iniziali di tutte le equazioni scendono sotto soglie dichiarate. Nella versione 14 la voce si inserisce nel dizionario dell’algoritmo; la sintassi esatta si verifica nel tutorial pitzDailySteady:
grep -n -A6 residualControl "$FOAM_TUTORIALS/incompressibleFluid/pitzDailySteady/system/fvSolution"
Il residuo da solo non basta: si affianca la storia delle grandezze di interesse. Qui la scelta naturale è aggiungere misure di pressione media su due sezioni del tratto sviluppato e verificare che la loro differenza resti costante nelle ultime centinaia di iterazioni. Un esempio di function object da aggiungere a functions nella copia di lavoro:
pSections
{
type surfaces;
libs ("libsampling.so");
writeControl writeTime;
surfaceFormat raw;
fields (p U);
interpolationScheme cellPoint;
surfaces
{
x010 { type cuttingPlane; point (0.10 0 0); normal (1 0 0); interpolate true; }
x018 { type cuttingPlane; point (0.18 0 0); normal (1 0 0); interpolate true; }
}
}
Le parole chiave delle superfici di campionamento sono cambiate fra le versioni: prima di usarlo confrontare con foamInfo surfaces o con i file in $FOAM_ETC/caseDicts/functions. In alternativa le medie di sezione si calcolano in ParaView (Guida 3).
Approfondimento · Errore di continuità nel log: come leggerlo
A ogni iterazione il log riporta una riga «time step continuity errors» con tre numeri: errore locale medio (sum local), globale (global) e cumulativo. Sono errori sulla divergenza del flusso dopo la correzione di pressione, normalizzati: devono diventare molto piccoli e il «global» deve oscillare intorno a zero senza deriva. Una deriva del cumulativo indica una perdita sistematica di massa, tipicamente da tolleranze troppo larghe della pressione.
grep "continuity errors" logs/log.foamRun | tail -n 3
grep -E "^Time =|Solving for (Ux|p)" logs/log.foamRun | tail -n 8
Approfondimento · Confrontare il profilo di velocità con la soluzione esatta
In ParaView, Plot Over Line da (0,15; 0; 0,0025) a (0,15; 0,01; 0,0025) campiona il profilo lungo l’altezza a metà profondità, nel tratto sviluppato. Lo script seguente (solo libreria standard) calcola la soluzione in serie negli stessi 21 punti; l’RMSE fra i due profili, normalizzato su U, misura l’errore di discretizzazione locale (Guida 3).
#!/usr/bin/env python3
"""Analytic fully developed laminar profile in a rectangular duct (series solution).
Prints u/U_mean along the 10 mm side at mid-depth, to compare with a ParaView Plot Over Line CSV."""
import math
H, W, U = 0.010, 0.005, 0.02 # duct height (y), depth (z), mean velocity
a, b = W / 2, H / 2 # half-widths: a short side, b long side
s = sum(math.tanh((2*k+1)*math.pi*b/(2*a)) / (2*k+1)**5 for k in range(200))
fac = 1 - 192*a/(math.pi**5*b)*s
G = 3*U/(a*a*fac) # (-dp/dx)/mu, from the mean velocity
def u(yy, zz, N=60): # yy across the short side, zz across the long side
out = 0.0
for k in range(N):
n = 2*k + 1
arg = n*math.pi*b/(2*a)
r = math.cosh(n*math.pi*zz/(2*a))/math.cosh(arg) if arg < 700 else math.exp(n*math.pi*(abs(zz)-b)/(2*a))
out += (-1)**k * (1 - r) * math.cos(n*math.pi*yy/(2*a)) / n**3
return 16*a*a/math.pi**3 * G * out
print("y_m,u_over_Umean")
for i in range(21):
y = H*i/20 # 0 ... 10 mm, wall to wall
print(f"{y:.4f},{u(0.0, y - b)/U:.4f}")
| y [mm] | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| u/U | 0 | 0,99 | 1,53 | 1,82 | 1,95 | 1,99 | 1,95 | 1,82 | 1,53 | 0,99 | 0 |
Il profilo lungo il lato lungo è più piatto al centro di una parabola, perché a metà profondità il gradiente è dominato dalle pareti vicine (lato corto). Una parabola di Poiseuille fra lastre piane darebbe un massimo di 1,5·U: un risultato vicino a 1,5 indicherebbe un errore di geometria o un caso inavvertitamente bidimensionale.
10. Problemi frequenti e diagnosi mirata
| Sintomo | Ipotesi da controllare | Controllo pratico |
|---|---|---|
| Patch mancante | Nome diverso fra mesh e 0/U o 0/p | Leggere constant/polyMesh/boundary e i due campi |
Errore su physicalProperties | Dizionario di release differente | Confrontare con $FOAM_TUTORIALS/incompressibleFluid/cylinder |
Errore sulle dimensioni di p | Confusione Pa / pressione cinematica | Atteso [0 2 -2 0 0 0 0] |
| Soluzione divergente | Ingresso incoerente, mesh scadente, scheme aggressivo | Leggere il primo errore, rieseguire checkMesh, ridurre la complessità |
| Portata outlet diversa da inlet | Bilancio non chiuso oppure flusso di ritorno | Verificare segni, normali, stato di convergenza |
RAS senza campi k, omega | Modello attivato solo nel dizionario | Ripartire dal tutorial RAS corretto e controllare le wall functions |
| ΔP inattesa | Geometria, sviluppo d’ingresso, unità o regime | Misurare sezioni, controllare densità e localizzazione delle sonde |
Una modifica alle condizioni del contorno altera il problema fisico. Va documentata e confrontata su una copia separata, mai introdotta silenziosamente in un risultato già pubblicato.
Approfondimento · Leggere il primo errore: anatomia di un FOAM FATAL ERROR
Un errore fatale riporta il messaggio, il file e spesso la riga del dizionario coinvolto, e la funzione del codice che l’ha generato. Il messaggio utile è quello che precede «From function»; le righe dello stack che seguono servono agli sviluppatori. Gli errori più frequenti in questa fase: «Cannot find patchField entry for walls» (nome patch diverso), «keyword nu is undefined» (dizionario di un’altra release), «Different dimensions for (p + …)» (unità sbagliate), «Unknown patchField type» (condizione scritta male o non disponibile nella versione).
grep -n -m1 -B2 -A12 "FOAM FATAL" logs/log.foamRun
cat logs/STATUS.txt
Approfondimento · Divergenza nelle prime iterazioni: una procedura in quattro passi
- Leggere in quale equazione esplode il residuo per prima (
Uxop) e a quale iterazione. - Rieseguire
checkMeshe confrontare con la Guida 6: una mesh appena modificata è il primo sospetto. - Ridurre i fattori di rilassamento (per esempio
p 0,2,U 0,5) e passare temporaneamente abounded Gauss upwindperdiv(phi,U): se così converge, il problema è numerico; se diverge comunque, è nelle condizioni al contorno. - Ripristinare le impostazioni originali partendo dalla soluzione convergente (
startFrom latestTime): lo schema del secondo ordine deve essere quello del risultato finale.
11. La consegna scientifica: cosa conservare
Per ciascuna variante archivia: 0/, constant/, system/, nome e versione del solver, logs/, numero di celle, misura della portata, differenza di pressione, convenzione delle unità, criteri di convergenza e stato ParaView .pvsm se utilizzato. FileZilla può trasferire i log e i piccoli CSV sul PC Windows; evita di trasferire inutilmente tutto il volume dei campi se pvserver li legge già sul server.
Checklist finale
- ☐ Ho riconosciuto Foundation 14 e il modulo
incompressibleFluid. - ☐ Le patch della mesh coincidono con quelle dei campi.
- ☐ Ho verificato area, unità e portata teorica.
- ☐ Ho conservato i template senza alterazioni.
- ☐ Ho eseguito mesh, controllo e solver su una copia identificabile.
- ☐ Ho controllato convergenza, bilancio e grandezze fisiche.
- ☐ Ho distinto pressione cinematica e pressione in pascal.
- ☐ Ho documentato quale condizione o parametro differisce fra i tre casi.
Approfondimento · Una scheda di caso pronta da compilare
| Campo | Esperienza A (valori di progetto) |
|---|---|
| versione e solver | OpenFOAM Foundation 14, foamRun / incompressibleFluid |
| mesh | 80×16×8 = 10 240 celle, checkMesh OK |
| fluido | ν = 1e-6 m²/s; ρ = 1000 kg/m³ (solo per il post-processing) |
| ingresso | fixedValue U = 0,02 m/s, Q = 1e-6 m³/s |
| uscita / pareti | p = 0 (fixedValue) / noSlip |
| numerica | steadyState, linearUpwind, PIMPLE, p 0,3 / U 0,7 |
| criterio d’arresto | 700 iterazioni (da sostituire con residualControl) |
| Q_in, Q_out, squilibrio | da collect_fluxes.py |
| gradiente di p nel tratto sviluppato | atteso ≈ 1,40e-2 m²/s² per m (14 Pa/m) |
Approfondimento · Archivio della consegna con impronte
Lo stesso schema della Guida 5 vale qui: un archivio per variante, senza i campi intermedi che si possono rigenerare, con le impronte dei file e la descrizione dell’ambiente.
for d in results/*/; do
( cd "$d" && { date -u; foamVersion; } > logs/environment.txt 2>&1
find 0 constant system logs postProcessing -type f -print0 | sort -z | xargs -0 sha256sum > logs/files.sha256 )
tar czf "$(basename "$d").tar.gz" -C "$d" 0 constant/physicalProperties constant/momentumTransport system logs postProcessing
done
12. Approfondimento operativo: misure su patch e criteri di confronto
Le function objects sono definite all’interno di system/controlDict, nella sezione functions. Il pacchetto ne contiene già due (inletFlux e outletFlux), che registrano la somma di phi su una patch. Questo blocco, per esempio, è parte della configurazione effettivamente fornita:
inletFlux
{
type surfaceFieldValue;
libs ("libfieldFunctionObjects.so");
log true;
writeControl writeTime;
writeFields false;
patch inlet;
operation sum;
fields (phi);
}
Se si osserva il file postProcessing/inletFlux/0/surfaceFieldValue.dat, ogni riga numerica registra un istante di scrittura e il valore integrato. È presente anche l’analogo postProcessing/outletFlux/...: non confrontare la prima riga di un file con l’ultima dell’altro; i tempi devono coincidere. L’utility Python scripts/collect_fluxes.py legge l’ultima misura disponibile di ciascuna funzione per ogni cartella sotto results/ e produce un CSV; lascia vuoti i valori mancanti, senza inventarli. Se si scaricano solo logs/ e postProcessing/ con FileZilla, è sufficiente ricreare localmente la stessa struttura di sottodirectory per utilizzare lo script.
python scripts/collect_fluxes.py results > flux_summary.csv
Esempio di ragionamento sperimentale. Una portata prescritta pari a 1×10⁻⁶ m³/s rappresenta 1 mL/s. Se le grandezze sono coerenti, per il canale ci aspettiamo una portata d’ingresso misurata vicina a −1×10⁻⁶ m³/s e una d’uscita vicina a +1×10⁻⁶ m³/s. Non attribuire a queste cifre una precisione superiore a quella raggiunta dalla simulazione. La pressione si analizza a valle e a monte in sezioni specificate, non prendendo semplicemente valori arbitrari di singole celle.
Un errore progettuale frequente: cambiare contemporaneamente portata, viscosità, mesh e posizione dell’uscita e poi cercare di attribuire le differenze di ΔP a una sola causa. Qui, al contrario, le tre copie isolano una modifica per volta. Quando si passa a studi quantitativi, occorrono poi almeno verifica dell’indipendenza dalla mesh e delle condizioni di ingresso, e una quantificazione della sensibilità numerica.
Richiamo — tempo iterativo. Con ddtSchemes impostato su steadyState, il valore deltaT 1 e la variabile Time sono essenzialmente marcatori di avanzamento iterativo. Non interpretare la curva di una grandezza contro Time come risposta transitoria reale del canale. Per affrontare transitori fisici occorre cambiare discretizzazione, controlli numerici e passo temporale e verificare il Courant number.
Approfondimento · Lo script collect_fluxes.py completo
#!/usr/bin/env python3
# GPL-3.0-or-later. Parse OpenFOAM 14 surfaceFieldValue output on Linux or Windows.
"""Usage: python scripts/collect_fluxes.py results > flux_summary.csv
Read last numeric time row from postProcessing/{inletFlux,outletFlux}/*/surfaceFieldValue.dat.
Missing values remain blank; script never fabricates simulation outputs.
"""
from pathlib import Path
from csv import writer
from sys import argv, stdout, stderr
from math import isfinite
def read_series(case, name):
rows={}
for f in (case/'postProcessing'/name).glob('*/surfaceFieldValue.dat'):
with f.open(encoding='utf-8',errors='replace') as inp:
for line in inp:
if not line.strip() or line.lstrip().startswith('#'):
continue
parts=line.split()
if len(parts)<2:continue
try: x=float(parts[0]);y=float(parts[1])
except ValueError:continue
if isfinite(x) and isfinite(y):
old=rows.get(x)
if old is None or f.stat().st_mtime >= old[0]:
rows[x]=(f.stat().st_mtime,y)
return {t:value for t,(_,value) in rows.items()}
def main():
if len(argv)!=2:
print('usage: collect_fluxes.py <results-directory>',file=stderr);return 2
root=Path(argv[1]);
if not root.is_dir():
print(f'not a directory: {root}',file=stderr);return 2
cw=writer(stdout,lineterminator='\n')
cw.writerow(['case','status','Q_inlet_m3s','Q_outlet_m3s','imbalance_relative'])
for case in sorted(p for p in root.iterdir() if p.is_dir()):
status_file=case/'logs'/'STATUS.txt'
status=status_file.read_text(encoding='utf-8').strip() if status_file.exists() else 'UNKNOWN'
ins=read_series(case,'inletFlux');outs=read_series(case,'outletFlux')
common=ins.keys() & outs.keys()
t=max(common) if common else None
qi=ins.get(t);qo=outs.get(t)
imb=abs(qi+qo)/max(abs(qi),abs(qo)) if qi is not None and qo is not None and max(abs(qi),abs(qo))>0 else None
cw.writerow([case.name,status,qi if qi is not None else '',qo if qo is not None else '',imb if imb is not None else ''])
return 0
if __name__=='__main__':raise SystemExit(main())
- Per ogni istante tiene un solo valore; se lo stesso tempo compare in più file (ripartenze), prevale il file modificato per ultimo.
- Usa l’ultimo istante comune alle due misure, quindi non confronta mai tempi diversi.
- Lo squilibrio è relativo:
|Qin + Qout| / max(|Qin|, |Qout|); se un dato manca la cella resta vuota.
Con il caso nominale ci si aspetta Q_in ≈ −1·10⁻⁶ e Q_out ≈ +1·10⁻⁶ m³/s; lo squilibrio relativo di un caso incomprimibile convergente è dell’ordine della tolleranza della pressione, molto sotto 10⁻⁶.
Approfondimento · Misurare anche lo sforzo a parete e confrontarlo con la Δp
Nel tratto sviluppato il bilancio di forze su un volume di lunghezza L impone Δp·A = τ̄_w·P·L, con P perimetro bagnato (30 mm). È un secondo controllo indipendente: con 14 Pa/m e A/P = Dₕ/4 = 1,667 mm lo sforzo medio atteso è τ̄_w ≈ 0,023 Pa. In OpenFOAM lo sforzo a parete si calcola con il function object wallShearStress (cinematico, da moltiplicare per ρ) e si media sulla patch in ParaView o con surfaceFieldValue.
foamPostProcess -help
foamPostProcess -func wallShearStress -latestTime # sulla copia di lavoro, dopo il calcolo
13. Ripetere le misure e costruire un confronto credibile
Per ogni simulazione, recupera da FileZilla almeno logs/, postProcessing/, 0/, constant/ e system/. Importa i CSV in un foglio di calcolo locale, assegna a ogni colonna unità esplicite, riportando velocità/portata imposta, ν, Re, portata misurata, errore di bilancio e ΔP quando disponibile. Non costruire il grafico di ΔP prima di avere verificato che tutte le misure utilizzino sezioni e definizioni identiche.
| Confronto | Variabile mantenuta costante | Variabile modificata | Interpretazione da verificare |
|---|---|---|---|
| A contro B | Q nominale, ν e mesh | fixedValue contro flowRateInletVelocity | Equivalenza pratica, compatibilmente con lo sviluppo del profilo |
| A contro C | Geometria, U ingresso, mesh | ν da 1×10⁻⁶ a 2×10⁻⁶ | Cambiamento di Re e resistenza viscosa |
| Tutte e tre | Nomi patch, scelta del solver | Solo il parametro dichiarato | Bilancio, residui e stabilità delle misure |
La verifica non si conclude quando lo script restituisce OK. Occorre una seconda lettura dei risultati: cercare variazioni sistematiche non attese, confrontare la velocità media e il gradiente di pressione, valutare effetti d’ingresso e uscita e pianificare infine il raffinamento della mesh. Se si aumenta il numero delle celle, si genera un nuovo caso e si aggiorna la tabella con i criteri descritti nella Guida 4.
Approfondimento · Quanto deve essere piccola la differenza fra A e B
A e B impongono lo stesso campo d’ingresso, quindi la differenza fra le loro soluzioni misura solo il rumore iterativo. Un criterio ingegneristico ragionevole: la differenza sui valori di interesse (Δp fra le due sezioni, Q) deve essere almeno un ordine di grandezza più piccola di qualsiasi effetto che si vuole attribuire alla fisica, per esempio la differenza A–C. Se A e B differiscono per più dell’1 %, la convergenza iterativa è insufficiente e il confronto A–C non è ancora interpretabile.
Approfondimento · Il foglio di confronto: colonne e unità
case,inlet_bc,nu_m2_s,Re_Dh,Q_in_m3_s,Q_out_m3_s,imbalance_rel,dpdx_developed_Pa_m,dp_total_Pa,umax_m_s,iterations,notes
A_velocity,fixedValue 0.02,1e-6,133.3,,,,,,,,
B_flowrate,flowRateInletVelocity 1e-6,1e-6,133.3,,,,,,,,
C_viscosity,fixedValue 0.02,2e-6,66.7,,,,,,,,
Valori attesi per la verifica: dpdx_developed ≈ 14,0 / 14,0 / 28,0 Pa/m; umax ≈ 0,0398 m/s in tutti e tre i casi. Le colonne vuote si riempiono con i dati di collect_fluxes.py e di ParaView; le unità sono nel nome della colonna, così il file resta leggibile anche fuori contesto.
14. Fonti ufficiali e limiti della verifica
- Sorgenti e tutorial OpenFOAM Foundation 14: https://github.com/OpenFOAM/OpenFOAM-14/tree/master/tutorials/incompressibleFluid/cylinder
- Codice del modulo
incompressibleFluidin OpenFOAM 14: https://github.com/OpenFOAM/OpenFOAM-14/blob/master/applications/modules/incompressibleFluid/incompressibleFluid.C - Documentazione API Foundation 14,
flowRateInletVelocity: https://cpp.openfoam.org/v14/classFoam_1_1flowRateInletVelocityFvPatchVectorField.html - Documentazione API Foundation 14,
surfaceFieldValue: https://cpp.openfoam.org/v14/classFoam_1_1functionObjects_1_1fieldValues_1_1surfaceFieldValue.html - Documentazione API Foundation 14,
pressureInletOutletVelocity: https://cpp.openfoam.org/v14/classFoam_1_1pressureInletOutletVelocityFvPatchVectorField.html
Stato dei test: file ed esercitazioni verificati staticamente (struttura, nomi patch, script Bash, unità e calcoli di riferimento). Il primo collaudo sul server deve essere trattato come test di accettazione e i risultati ottenuti vanno sottoposti alle verifiche delle Guide 2–4.
Approfondimento · Letture per il passo successivo
- R. K. Shah, A. L. London, Laminar Flow Forced Convection in Ducts, Academic Press, 1978. Soluzioni e correlazioni per condotti di sezione qualsiasi, incluso f·Re dei rettangoli.
- F. M. White, Viscous Fluid Flow, McGraw-Hill. Moto laminare nei condotti, lunghezza d’ingresso, strato limite.
- S. B. Pope, Turbulent Flows, Cambridge University Press, 2000. Fondamenti dei modelli RAS e LES citati nella sezione 6.
- Tutorial della propria installazione:
incompressibleFluid/pitzDailySteady,incompressibleFluid/cylinder, da confrontare voce per voce con il caso della guida.
Guide collegate: server Linux da Windows, monitoraggio e convergenza, ParaView per l’analisi quantitativa, verifica e validazione, campagne parametriche, generazione e controllo delle mesh e il percorso Imparare OpenFOAM 14.