OpenFOAM 14, parte 4: controlDict e controllo della simulazione
Parte 4 di 8 della serie sulla costruzione del caso nel percorso Imparare OpenFOAM 14. La Parte 3 ha definito i campi iniziali e le condizioni al contorno del canale; qui si passa allo strato che decide quando il caso parte, come avanza e quando scrive i risultati: il file system/controlDict.
Il compito. Leggere e scrivere system/controlDict per OpenFOAM Foundation 14: scelta del modulo solutore, regole di avvio e di arresto, tempo simulato, politiche di scrittura, restart ed esecuzione monitorata.
Il risultato. Un controlDict di riferimento completo, la capacità di prevedere passi, tempi di scrittura e nomi delle cartelle prima di eseguire, e un metodo sicuro per riprendere o fermare un calcolo.
Tempo simulato ≠ tempo di CPU
Vengono calcolati 10 passi; l’output viene scritto ai passi 5 e 10. Impostazioni: deltaT = 0.002 s | writeControl = timeStep | writeInterval = 5. La linea del tempo completa è nella Figura 1, sezione 4.
Come usare questa parte
Prerequisiti. Questa parte presuppone il flusso di lavoro CFD in sette passi del primo caso pratico (flusso laminare tra lastre piane e validazione di Poiseuille) e le parti precedenti su struttura del caso (Parte 1), mesh (Parte 2) e campi (Parte 3).
Ambito. OpenFOAM Foundation 14. Leggere e scrivere system/controlDict; ragionare su selezione del solutore, regole di avvio e arresto, tempo simulato, politiche di output, restart ed esecuzione monitorata.
Percorso di lettura
Percorso principale: selezione del solutore → controllo del tempo → pianificazione dell’output → restart → passo temporale adattivo → esperimenti sicuri. I riquadri «Richiamo facoltativo» si possono saltare se si ha già dimestichezza con la CFD.
Caso di riferimento: canale rettangolare laminare 3D | Guida ufficiale: doc.cfd.direct/openfoam/user-guide-v14.
Patto di apprendimento
Questa unità non costruisce una nuova mesh e non prescrive nuove condizioni al contorno. Insegna lo strato che controlla quando un caso inizia, come si misura il suo avanzamento e quando vengono scritti i risultati.
Dovresti già avere
- OpenFOAM Foundation 14 attivato in una shell (
foamVersion). - La mesh del canale e i file di campo delle Parti 2–3, oppure un tutorial v14 completo equivalente.
- Una copia indipendente e modificabile del caso; non modificare mai il caso di riferimento.
system/fvSchemes,system/fvSolutione i file della fisica funzionanti, se si intende eseguire davvero un solutore.
Alla fine di questa parte dovresti saper
- Selezionare il modulo solutore usato da
foamRunin OpenFOAM 14. - Prevedere il tempo di avvio, la condizione di arresto e i nomi delle cartelle di output.
- Spiegare la differenza tra output
timeStep,runTimeeadjustableRunTime. - Eseguire un restart senza confondere la cartella iniziale
0/con un tempo successivo salvato. - Ispezionare i controlli del tempo e i log prima di alterare un calcolo in corso.
Non in questa unità
Quale discretizzazione spaziale usare appartiene alla Parte 5. Le tolleranze dei solutori lineari e le correzioni SIMPLE/PIMPLE appartengono alla Parte 6. Una fisica CFD e condizioni al contorno valide non si possono sistemare cambiando controlDict.
Mappa di lettura
| Sezioni | Argomento | Prova di comprensione |
|---|---|---|
| 1–3 | Dizionario e semantica del tempo | Individuare i controlli che fanno avanzare il calcolo |
| 4–7 | Impostazioni di scrittura, sicurezza, restart, passo adattivo | Prevedere i file e gli stati ripresi |
| 8–11 | Modifiche durante il calcolo e laboratorio | Eseguire un confronto A/B controllato |
| 12–14 + appendice | Esercizi, diagnosi, riferimenti | Spiegare e ricostruire le impostazioni |
1. Che cos’è controlDict?
Il file system/controlDict configura il database del tempo di OpenFOAM e i controlli di input/output validi per tutto il calcolo. Non è il posto in cui si definiscono le condizioni al contorno della velocità o gli schemi ai volumi finiti.
| Cartella | Contenuto |
|---|---|
0/ | campi / dati iniziali e al contorno |
constant/ | mesh / modelli fisici |
system/ | controlli della simulazione e numerici |
OpenFOAM Foundation 14: un modulo solutore
Nella v14 il normale flusso di lavoro a regione singola usa l’applicazione foamRun. La voce solver in controlDict seleziona un modulo solutore caricabile. È diverso da molti tutorial più vecchi che usavano una voce application simpleFoam; o application icoFoam;.
Estratto strutturale v14:
FoamFile
{
format ascii;
class dictionary;
location "system";
object controlDict;
}
solver incompressibleFluid;
Portata dell’esempioincompressibleFluid è adatto come illustrazione della selezione del modulo, non come garanzia che un caso sia eseguibile: anche mesh, campi, proprietà e dizionari numerici devono essere compatibili.
Leggi la voce, non solo il nome del file
Un tutorial storico può essere utile sul piano concettuale pur contenendo una sintassi legata a una versione specifica. Se il modulo e l’applicazione configurati non corrispondono al flusso di lavoro v14 previsto, ispeziona prima un esempio nativo v14.
Approfondimento: il database del tempo (runTime)
Ogni applicazione OpenFOAM costruisce all’avvio un oggetto Time (nel codice di solito chiamato runTime). È contemporaneamente l’orologio del calcolo e il registro radice di tutti gli oggetti che possono essere letti o scritti: mesh, campi, dizionari. Quando un campo viene «scritto», in realtà è runTime che decide se l’istante corrente è un istante di scrittura e chiede a ciascun oggetto registrato con l’opzione di scrittura automatica di salvarsi nella cartella del tempo corrente.
Per questo controlDict è letto prima di qualunque altro file del caso: senza di esso non esiste il database a cui gli altri oggetti si agganciano. Anche un’utility che non avanza nel tempo, come blockMesh, ha bisogno di un controlDict leggibile (lo si è visto nella Parte 2). Le cartelle con nome numerico (0, 0.01, …) sono le «istantanee» del database; ognuna, dopo una scrittura da parte del solutore, contiene anche una sottocartella uniform/ con il file time, che registra valore del tempo, indice del passo e passo temporale corrente (vedi la sezione 6).
Le impostazioni globali dell’installazione (unità di misura, OptimisationSwitches, DebugSwitches) non stanno nel controlDict del caso ma in $FOAM_ETC/configDict; si possono sovrascrivere per un singolo caso copiando il modello con foamGet configDict nella cartella system (Guida utente §4.3).
Approfondimento: come foamRun carica un modulo, e la corrispondenza con i vecchi solutori
foamRun è un’applicazione generica: legge la parola chiave solver, carica la libreria condivisa del modulo con quel nome e poi esegue un ciclo temporale comune che chiama, a ogni passo, le funzioni del modulo (predittore del moto, correttore della pressione, modelli di trasporto e termofisici). La fisica e l’algoritmo stanno nel modulo; il controllo del tempo, la scrittura e i function object restano in foamRun e nel database del tempo. Per i casi multiregione (per esempio fluido + solido) si usa foamMultiRun, con i moduli indicati per regione in regionSolvers.
| Vecchia applicazione | Modulo Foundation (con foamRun) |
|---|---|
icoFoam, simpleFoam, pimpleFoam | incompressibleFluid |
interFoam | incompressibleVoF |
rhoSimpleFoam, rhoPimpleFoam, buoyantFoam | fluid |
chtMultiRegionFoam | foamMultiRun con regioni fluid e solid |
La tabella è un orientamento, non un elenco esaustivo: l’elenco autorevole per la tua installazione si ottiene con i comandi seguenti. Steady o transitorio non si sceglie più cambiando applicazione: con incompressibleFluid lo decide lo schema ddtSchemes in fvSchemes (steadyState o, per esempio, Euler), argomento della Parte 5.
foamToC -solvers # solver modules known to this installation
foamInfo incompressibleFluid # description and example tutorials
foamDictionary -entry solver -value system/controlDict
Errore tipico: un controlDict copiato da un tutorial vecchio con application simpleFoam; e senza solver fa fallire foamRun con un FOAM FATAL IO ERROR del tipo «keyword solver is undefined in dictionary …/system/controlDict». Un nome di modulo scritto male produce invece un errore di selezione che indica il tipo sconosciuto. Alcuni tutorial Foundation contengono anche una riga application foamRun;: è usata dagli script dei tutorial per sapere quale eseguibile lanciare, ma la voce decisiva per la fisica resta solver.
2. Un controlDict di riferimento completo
Questa configurazione didattica usa un passo temporale numerico fisso, così i tempi di output sono facili da prevedere. È volutamente prudente sulla conservazione dei file: nulla viene eliminato automaticamente.
Esempio didattico — system/controlDict:
FoamFile
{
format ascii;
class dictionary;
location "system";
object controlDict;
}
solver incompressibleFluid;
startFrom startTime;
startTime 0;
stopAt endTime;
endTime 0.020;
deltaT 0.002;
writeControl timeStep;
writeInterval 5;
purgeWrite 0;
writeFormat ascii;
writePrecision 8;
writeCompression off;
timeFormat general;
timePrecision 6;
runTimeModifiable yes;
adjustTimeStep no;
Per un caso transitorio compatibile, questa configurazione pianifica 10 passi da 0 a 0,020 e scritture regolari ai passi 5 e 10, cioè ai tempi simulati 0,010 e 0,020. Se una particolare simulazione avanzi normalmente dipende dal solutore e dagli altri file del caso.
La distinzione cruciale
Qui si sceglie un passo temporale fisso per insegnare il database del tempo. Un solutore che usa la discretizzazione steadyState può trattare il tempo riportato come un contatore di iterazioni invece che come secondi fisici; non interpretarlo come tempo di laboratorio.
Nota sui nomi: con timeFormat general; le cartelle scritte non si chiameranno 0.010 e 0.020 ma 0.01 e 0.02, perché il formato generale elimina gli zeri finali (sezione 5). Il tempo è lo stesso; cambia solo la sua rappresentazione come nome di cartella.
Approfondimento: ogni voce, il suo ruolo e il suo valore predefinito
La Guida utente v14 (§4.4) indica come obbligatorie le voci di controllo del tempo e la coppia writeControl/writeInterval; le altre hanno un valore predefinito. Scriverle comunque in modo esplicito rende il file autodocumentato e protegge da cambi futuri dei default.
| Voce | Valore qui | Ruolo | Predefinito / note |
|---|---|---|---|
solver | incompressibleFluid | modulo caricato da foamRun | nessuno: obbligatoria per foamRun |
startFrom | startTime | quale stato leggere | vedi sezione 3 |
startTime | 0 | tempo letto con startFrom startTime | ignorato con latestTime/firstTime |
stopAt | endTime | regola di arresto | vedi sezione 3 |
endTime | 0.020 | tempo finale | usato con stopAt endTime |
deltaT | 0.002 | passo temporale (o incremento di iterazione) | valore iniziale se il passo è adattivo |
writeControl | timeStep | come si misura l’intervallo di scrittura | obbligatoria |
writeInterval | 5 | intervallo di scrittura | obbligatoria; unità secondo writeControl |
purgeWrite | 0 | numero massimo di tempi conservati | 0 = nessuna eliminazione |
writeFormat | ascii | formato dei campi | ascii |
writePrecision | 8 | cifre significative in ASCII | 6 |
writeCompression | off | compressione gzip | off |
timeFormat | general | formato dei nomi delle cartelle | general |
timePrecision | 6 | cifre dei nomi delle cartelle | 6 |
runTimeModifiable | yes | rilettura dei dizionari durante il calcolo | vedi sezione 8 |
adjustTimeStep | no | passo adattivo | no |
Per vedere il file così come lo legge OpenFOAM, con macro espanse e valori effettivi, usa foamDictionary -expand system/controlDict. La guida elenca anche voci meno comuni: libs (librerie aggiuntive da caricare), functions (sezione 9), graphFormat (formato dei dati di grafico: raw predefinito, gnuplot, csv, vtk, ensight) e beginTime (sezione 4).
Approfondimento: quando il «tempo» è solo un contatore di iterazioni
In un calcolo stazionario la derivata temporale viene eliminata dalle equazioni (ddtSchemes { default steadyState; }) e il modulo usa un algoritmo di tipo SIMPLE. Il database del tempo continua però ad avanzare: ogni «passo» è un’iterazione di sottorilassamento verso la soluzione stazionaria. Per convenzione si pone deltaT 1;, così il tempo coincide con il numero di iterazioni.
Il tutorial del gradino all’indietro della Guida utente v14 (§2.1.7 e §2.1.9) lo mostra bene: endTime 2000, deltaT 1, writeControl timeStep, writeInterval 100. Il calcolo si ferma prima, quando i residui iniziali scendono sotto le tolleranze di residualControl in fvSolution: il log riporta la convergenza SIMPLE in 285 iterazioni e vengono scritte le cartelle 100, 200 e 285. L’ultima non è un multiplo di 100: all’arresto per convergenza il modulo scrive lo stato finale.
Conseguenza pratica: in un caso stazionario un nome di cartella come 285 non significa «285 secondi» e un valore di deltaT diverso da 1 non cambia la soluzione convergente; cambia solo i nomi. Prima di interpretare una cartella di tempo, controlla quindi ddtSchemes con foamDictionary -entry ddtSchemes system/fvSchemes.
3. Avviare, avanzare e fermarsi
Selezione del tempo di avvio
| Voce | Significato | Uso tipico |
|---|---|---|
startFrom firstTime; | Legge la cartella di tempo disponibile più vecchia. | Leggere un caso dal suo primo stato salvato. |
startFrom startTime; | Usa il tempo indicato da startTime. | Un nuovo avvio deliberato da 0. |
startFrom latestTime; | Legge la cartella di tempo disponibile più recente. | Riprendere da un risultato scritto in precedenza. |
Condizioni di arresto
| Voce | Che cosa fa |
|---|---|
stopAt endTime; | Avanza finché non è soddisfatta la condizione su endTime configurata. |
stopAt nextWrite; | Si ferma al prossimo evento di scrittura pianificato; utile per un’interruzione ordinata. |
stopAt writeNow; | Si ferma dopo il passo temporale corrente e scrive i campi. |
stopAt noWriteNow; | Si ferma dopo il passo temporale corrente senza una scrittura aggiuntiva. |
Esempio: un avvio e un obiettivo deliberati
startFrom startTime;
startTime 0;
stopAt endTime;
endTime 0.020;
deltaT 0.002;
Richiamo facoltativo · Che cos’è un passo temporale?
In un flusso transitorio, avanzare di un passo temporale aggiorna lo stato stimato da t a t + Δt. Un passo temporale non coincide con un’iterazione del solutore lineare: un algoritmo può eseguire diverse soluzioni algebriche e correzioni durante un singolo passo temporale.
Approfondimento: come OpenFOAM decide di aver raggiunto endTime
Il numero di passi pianificato con passo fisso è N = (endTime − startTime)/Δt; nell’esempio 0,020/0,002 = 10. Il confronto però non avviene in aritmetica esatta: 0,002 non è rappresentabile esattamente in virgola mobile binaria e dieci somme successive non danno esattamente 0,02. Per evitare un passo in più o in meno per colpa dell’arrotondamento, il database del tempo della Foundation continua finché il tempo corrente è minore di endTime − ½Δt. Il tempo viene inoltre ricalcolato a ogni passo in modo da limitare l’accumulo di errori, e il nome della cartella viene generato con timePrecision cifre.
Esempio numerico: con endTime 0.021 e Δt = 0,002 il criterio è t < 0,020. Dopo il decimo passo t = 0,020, quindi il calcolo si ferma a 0,020 e non a 0,021: con passo fisso non c’è un ultimo passo «accorciato». Se endTime non è un multiplo di Δt, il tempo finale reale è il multiplo più vicino secondo questo criterio. Prevedilo sempre e confrontalo con l’ultima riga Time = del log.
Con stopAt writeNow e noWriteNow il database del tempo, alla prima rilettura del file, fissa di fatto la fine al passo corrente; con nextWrite attende che il passo corrente sia anche un tempo di scrittura. Con firstTime e latestTime il valore di startTime non conta; con startTime deve esistere una cartella con quel tempo che contenga i campi richiesti.
Approfondimento: che cosa succede dentro un passo temporale
Con incompressibleFluid in regime transitorio l’algoritmo è PIMPLE. Un passo temporale contiene nOuterCorrectors cicli esterni; in ognuno c’è (facoltativamente) un predittore della quantità di moto, cioè la soluzione di un sistema lineare per ciascuna componente di velocità, seguito da nCorrectors correzioni della pressione, ciascuna con la soluzione di un’equazione di Poisson per p (più eventuali correzioni di non ortogonalità). Con 1 ciclo esterno e 2 correzioni si risolvono quindi, per passo, fino a 3 sistemi di velocità e 2 di pressione, e ogni sistema richiede a sua volta più iterazioni del solutore lineare.
Nel log questo si vede come un blocco per passo: una riga Time = …, poi righe del tipo «solutore: Solving for Ux, Initial residual = …, Final residual = …, No Iterations …» per ogni soluzione, gli errori di continuità e infine ExecutionTime e ClockTime. Contare le righe Time = conta i passi; contare le righe «Solving for p» conta le soluzioni di pressione. Le impostazioni di PIMPLE e le tolleranze appartengono a fvSolution e sono trattate nella Parte 6.
4. Tempo simulato e pianificazione delle scritture
Usa esattamente gli stessi deltaT = 0.002 ed endTime = 0.020 in tutti gli esempi che seguono. Cambia solo la politica di scrittura. La linea del tempo si riferisce a un calcolo transitorio con dieci passi temporali completati.

Tre politiche da non confondere
| Coppia di controllo | Interpretazione | Tempi scritti attesi |
|---|---|---|
timeStepwriteInterval 5; | Scrive ogni cinque passi numerici. | 0,010; 0,020 |
runTimewriteInterval 0.006; | Richiede output a intervalli di 0,006 s di tempo simulato; qui la griglia dei passi è allineata. | 0,006; 0,012; 0,018 |
adjustableRunTimewriteInterval 0.006; | Scrive a tempi simulati pianificati; adatta la dimensione del passo per colpire un istante di scrittura quando serve. | 0,006; 0,012; 0,018 |
Importante: runTime non è il tempo dell’orologio
In writeControl, runTime si riferisce al tempo simulato. Per un output basato sul tempo reale trascorso dall’esecuzione, la v14 fornisce clockTime; cpuTime si riferisce al tempo di processore.
I valori della tabella sono scritture regolari pianificate, non la garanzia di una scrittura aggiuntiva esattamente al tempo di terminazione. A seconda della modalità di arresto, lo stato finale potrebbe non corrispondere a un output pianificato.
Approfondimento: come viene deciso un tempo di scrittura
timeStep. Il passo corrente è un tempo di scrittura quando il suo indice è un multiplo intero di writeInterval (che va quindi inteso come numero intero di passi). L’indice è quello registrato nel database del tempo: dopo un restart prosegue dal valore salvato in uniform/time, non riparte da zero.
runTime / adjustableRunTime. A ogni passo si calcola un indice di scrittura come parte intera di (t − trif + ½Δt)/writeInterval; quando questo indice aumenta rispetto all’ultima scrittura, il passo corrente diventa un tempo di scrittura. Il mezzo passo rende il confronto robusto agli arrotondamenti: con Δt = 0,002 e intervallo 0,006, a t = 0,006 si ha (0,006 + 0,001)/0,006 ≈ 1,17 → indice 1 → scrittura. Se l’intervallo non è un multiplo di Δt, la scrittura cade sul passo più vicino all’istante richiesto (entro mezzo passo), non sull’istante esatto, e i tempi scritti non sono equidistanti.
Il riferimento trif conta dopo un restart. La Guida v14 documenta la voce facoltativa beginTime, che ancora i tempi di scrittura ai multipli di writeInterval a partire da quel tempo, per i casi con un tempo di avvio «scomodo». Dopo un restart da un tempo che non è multiplo dell’intervallo, verifica con foamListTimes dove cade la prima scrittura invece di supporla.
clockTime / cpuTime. L’intervallo è in secondi di tempo reale o di CPU; i tempi simulati scritti dipendono dal carico della macchina e cambiano da un’esecuzione all’altra. Sono utili come salvataggi di sicurezza su calcoli lunghi (per esempio writeInterval 3600 con clockTime), mai per un confronto riproducibile.
Approfondimento: come adjustableRunTime adatta il passo
Con adjustableRunTime il database del tempo, quando il passo viene impostato, calcola il tempo che manca alla prossima scrittura e lo divide in un numero intero di passi uguali, arrotondando per eccesso il rapporto tra tempo mancante e passo corrente. Il nuovo passo è il tempo mancante diviso per quel numero; la variazione rispetto al passo precedente è limitata per non introdurre salti bruschi. Il risultato è che le scritture cadono esattamente sui multipli di writeInterval.
Esempio: passo corrente 0,004 s, prossima scrittura tra 0,006 s. Il rapporto è 1,5 → 2 passi → Δt = 0,003 s. Senza adattamento, con runTime, i passi cadrebbero a 0,004 e 0,008 e la scrittura «a 0,006» finirebbe a 0,008 (il passo più vicino entro mezzo passo, secondo la regola della nota precedente).
La Guida v14 raccomanda adjustableRunTime insieme al passo automatico (adjustTimeStep yes): è lì che serve davvero, perché il passo cambia di continuo e senza aggiustamento i tempi scritti sarebbero arbitrari, come nota il tutorial della rottura della diga (§2.2). Con passo fisso già allineato all’intervallo, come nell’esempio della tabella, runTime e adjustableRunTime danno gli stessi tempi.
5. Formati di output, nomi delle cartelle e conservazione
Una simulazione può funzionare correttamente ma lasciare un numero impraticabile di file, o troppo pochi risultati per l’analisi. Tratta l’output come parte del progetto sperimentale.
| Impostazione | Effetto |
|---|---|
writeFormat ascii/binary | Testo leggibile contro formato binario compatto dei campi. |
writePrecision | Precisione numerica dei valori ASCII scritti; non è l’accuratezza del solutore. |
writeCompression on/off | Compressione dei file di output su disco. |
timeFormat | Formattazione dei nomi delle cartelle di tempo in output. |
timePrecision | Cifre usate per i nomi delle cartelle di tempo; distinta dalla precisione dei valori dei campi. |
purgeWrite | Numero massimo di cartelle di tempo scritte conservate, quando attivo. |
Capire purgeWrite prima di usarlo
Non attivare l’eliminazione nel caso didattico principale:
purgeWrite 0; // Do not purge saved time directories.
// Separate example: retain only a rolling set
// purgeWrite 2;
Rischio di perdita di datipurgeWrite 2; rimuove le cartelle di tempo scritte più vecchie man mano che ne vengono prodotte di nuove. Non è adatto quando serve la storia transitoria completa per validazione, confronti o riproducibilità. Lavora solo su una copia; la cancellazione che ne risulta non è reversibile.
Nomi delle cartelle di tempo
Con timeFormat general; e una timePrecision sufficiente, i nomi delle cartelle possono rappresentare tempi come 0.01. Una precisione del tempo troppo bassa può causare collisioni o ambiguità; non cambia l’accuratezza fisica del passo temporale.
Approfondimento: ASCII, binario, precisione e dimensione su disco
In memoria OpenFOAM lavora in doppia precisione (circa 15–17 cifre significative). In ASCII ogni valore viene scritto con writePrecision cifre significative: con 6 (il predefinito) un valore come 0,0123456789 diventa 0,0123457. Il solutore non perde accuratezza durante il calcolo, ma un restart da una cartella ASCII riparte da valori arrotondati: per piccole variazioni o per confronti bit a bit tra un calcolo continuo e uno ripreso, questo basta a far divergere leggermente le storie. Per questo il riferimento usa 8, e per restart rigorosi conviene binary, che conserva tutti i bit.
Con writeFormat binary l’intestazione FoamFile resta testuale (con format binary;) ma le liste di valori non uniformi diventano byte grezzi; less mostra caratteri illeggibili. Con writeCompression on i file diventano U.gz, p.gz, ecc.; OpenFOAM li legge in modo trasparente e zcat 0.01/U.gz | head -n 30 ne mostra l’inizio se sono ASCII.
Stima d’ordine per il canale della Parte 2 (40 × 10 × 5 = 2.000 celle; 5.350 facce interne): U contiene 2.000 vettori, cioè 6.000 numeri; in binario 8 byte ciascuno ≈ 48 kB, in ASCII con 8 cifre circa 12–15 caratteri per numero ≈ 70–90 kB. phi (un valore per faccia interna) e p aggiungono quantità simili. Moltiplica per il numero di scritture: 1.000 scritture di una mesh da 10 milioni di celle sono già centinaia di gigabyte. Misura sempre con du -sh invece di indovinare.
Approfondimento: timeFormat, timePrecision e collisioni di nomi
timeFormat accetta fixed, scientific e general (predefinito). Secondo la Guida v14, general usa la notazione scientifica quando l’esponente è minore di −4 o maggiore o uguale a timePrecision, altrimenti la notazione decimale senza zeri finali. timePrecision (predefinito 6) è il numero di cifre usate.
| Tempo | general, 6 | fixed, 3 | Osservazione |
|---|---|---|---|
| 0,010 | 0.01 | 0.010 | stesso tempo, nome diverso |
| 0,0125 | 0.0125 | 0.013 | fixed con 3 cifre arrotonda |
| 0,00001 | 1e-05 | 0.000 | con fixed, 3 cifre: collisione con 0 |
Una collisione è grave: due istanti diversi scriverebbero nella stessa cartella, sovrascrivendosi, oppure un tempo positivo verrebbe confuso con 0. Il database del tempo della Foundation rileva la situazione quando il passo è troppo piccolo per le cifre disponibili, aumenta automaticamente timePrecision e lo segnala con un avviso nel log («Increased the timePrecision …»). Non affidarti a questo meccanismo: scegli la precisione in base al passo più piccolo previsto (con passo adattivo, in base a quello che compare nel log) e cerca l’avviso nel log.
Approfondimento: che cosa elimina davvero purgeWrite
La Guida v14 descrive purgeWrite come un limite al numero di cartelle di tempo conservate: superato il limite, le cartelle più vecchie vengono sovrascritte in modo ciclico, cioè eliminate a ogni nuova scrittura. Con purgeWrite 2 e scritture ogni 5 passi nel caso di riferimento esteso a 0,040 s, al termine resterebbero solo 0.035 e 0.04; 0.01, 0.015, … sarebbero andate perse durante il calcolo.
Nell’implementazione Foundation la coda delle cartelle da eliminare contiene i tempi scritti dall’esecuzione corrente: la cartella iniziale 0/ e, dopo un restart, le cartelle prodotte da esecuzioni precedenti non vi entrano. Non usarlo però come garanzia: verifica su una copia con foamListTimes prima e dopo.
Uso legittimo: calcoli molto lunghi in cui interessano solo statistiche raccolte dai function object (sezione 9) e un punto di restart recente. Esiste anche lo strumento foamListTimes -rm, che elimina tutte le cartelle di tempo elencate tranne 0: anch’esso è irreversibile e va usato solo su copie, mai come scorciatoia diagnostica (sezione 13).
6. Riprendere un caso in sicurezza
Un restart è una nuova esecuzione che legge uno stato scritto esistente. Non è la stessa cosa che scegliere semplicemente un endTime più avanti. Un restart valido richiede campi salvati, mesh e impostazioni fisiche e numeriche compatibili.
La modifica minima per un restart
Restart dopo uno stato salvato a 0,020:
startFrom latestTime;
stopAt endTime;
endTime 0.040;
Nell’esempio, l’ultimo tempo scritto disponibile è 0,020. L’esecuzione successiva legge quello stato e prosegue verso 0,040; non torna a 0/U e 0/p. Con latestTime, il valore di startTime non è il criterio di selezione.
Sequenza sicura
Esempio di flusso di lavoro nella shell:
# Lavora in una tua cartella dei casi; salva una copia esatta.
cp -a channelCase channelCase_restart
cd channelCase_restart
foamDictionary -entry startFrom -value system/controlDict
foamDictionary -entry endTime -value system/controlDict
ls -d [0-9]* 2>/dev/null # Ispeziona, non dedurre.
# Dopo aver modificato controlDict e controllato gli altri file del caso:
foamRun > log.restart 2>&1
less log.restart
Conserva le prove
Conserva sempre il caso originale e una registrazione completa delle impostazioni usate per il primo calcolo. Se cambiano le ipotesi sulla mesh o sul modello, i campi salvati potrebbero non essere più uno stato di restart coerente. Un caso copiato è più sicuro che modificare o cancellare vecchie cartelle di tempo.
Tre domande prima di un restart
- Quale cartella di tempo verrà effettivamente selezionata?
- I suoi campi corrispondono alla mesh e al modello attivi?
- Il nuovo tempo di arresto richiesto e la pianificazione dell’output sono coerenti con quello stato di partenza?
Approfondimento: che cosa contiene una cartella di tempo e che cosa legge il restart
Dopo una scrittura del solutore, una cartella come 0.02/ contiene i campi risolti (U, p), il flusso sulle facce phi (con incompressibleFluid; al restart viene letto se presente, invece di essere ricostruito dalla velocità) ed eventuali campi dei modelli (per esempio nut, k, omega con un modello di turbolenza). La sottocartella uniform/ contiene il file time con il valore del tempo, il nome, l’indice del passo e gli ultimi passi temporali (deltaT, deltaT0).
foamDictionary 0.02/uniform/time
# voci tipiche: value, name, index, deltaT, deltaT0
foamDictionary -entry index -value 0.02/uniform/time
Due conseguenze. Primo: con writeControl timeStep l’indice prosegue, quindi un restart da 0,02 (indice 10) con writeInterval 5 scrive a 0,03 e 0,04 (indici 15 e 20), coerentemente con il primo calcolo. Secondo: il database del tempo usa le informazioni di uniform/time per riprendere; se per il restart hai cambiato deltaT in controlDict, controlla nel log il passo effettivamente usato nei primi passi (per esempio l’intervallo tra due righe Time =) invece di supporlo.
Approfondimento: strumenti per preparare un restart (e una trappola)
foamListTimes (Guida §4.7) elenca le cartelle di tempo escludendo 0; con -latestTime mostra solo quella che startFrom latestTime selezionerà. foamDictionary -set modifica le voci in modo ripetibile e accetta più assegnazioni in una sola chiamata con la sintassi chiave=valore separata da virgole; dopo ogni modifica rileggi il valore con -value.
# Prevedi che cosa selezionerà latestTime, senza eseguire nulla
foamListTimes -latestTime
# Modifica le voci in modo riproducibile invece che a mano
foamDictionary -set "startFrom=latestTime, endTime=0.040" system/controlDict
foamDictionary -entry startFrom -value system/controlDict
foamDictionary -entry endTime -value system/controlDict
La trappola: foamCloneCase vecchio nuovo copia solo 0, system e constant. È perfetto per un nuovo avvio da zero, ma un clone non contiene le cartelle di tempo successive: un restart latestTime sul clone ripartirebbe silenziosamente da 0/. Per un restart usa cp -a, che copia tutto conservando date e permessi, come nella sequenza sicura qui sopra.
Se la mesh è cambiata (per esempio dopo un infittimento), i campi salvati non hanno più il numero di celle giusto e la lettura fallisce. Non è più un restart ma un nuovo calcolo inizializzato da un altro: la Guida v14 (§5.8) descrive mapFields per mappare i campi tra geometrie diverse.
7. Passo temporale adattivo e numero di Courant
Per i solutori transitori che supportano l’adattamento automatico del passo, controlDict può esprimere un limite obiettivo sul numero di Courant. È un controllo numerico, non un’alternativa alla scelta di una risoluzione della mesh e di modelli appropriati.
Richiamo facoltativo · Numero di Courant
Per l’avvezione monodimensionale su una mesh uniforme, una stima utile è Co ≈ |U| Δt / Δx. Con |U| = 0,05 m/s, Δt = 0,002 s e Δx = 0,001 m si ha Co ≈ 0,10. Nei calcoli pratici 3D ai volumi finiti, i numeri di Courant locali e massimi riportati dipendono dai flussi sulle facce e dai volumi delle celle.
Esempio transitorio v14 — verifica che il modulo lo supporti:
adjustTimeStep yes;
maxCo 0.5;
maxDeltaT 0.005;
writeControl adjustableRunTime;
writeInterval 0.010;
Che cosa cambia?
- Il solutore può cambiare
deltaTdurante il calcolo, quando la sua implementazione del controllo del tempo supporta queste voci. maxCoesprime un limite superiore obiettivo per il numero di Courant; non certifica stabilità né accuratezza della soluzione.maxDeltaTimpone un ulteriore tetto configurato al passo temporale, quando supportato.adjustableRunTimecoordina l’output pianificato con passi temporali che cambiano automaticamente.
Prima la base a passo fisso
Mantieni adjustTimeStep no; nei primi esperimenti controllati. Passa al passo adattivo solo dopo aver verificato che il modulo v14 scelto e la numerica del caso lo supportano, e ispeziona il log del solutore per la storia effettiva del passo temporale.
Approfondimento: il numero di Courant come lo calcola OpenFOAM
Ai volumi finiti la velocità non è trasportata da un singolo |U| ma dai flussi volumetrici sulle facce φf = Uf·Sf (m³/s). Per ogni cella P OpenFOAM calcola CoP = ½ Δt Σf |φf| / VP: la somma di tutti i flussi entranti e uscenti, divisa per due (ciò che entra uguaglia ciò che esce), rapportata al volume. In parole: la frazione del volume della cella attraversata dal fluido in un passo. Il log riporta per ogni passo una riga «Courant Number mean: … max: …», cioè la media sulle celle e il massimo.
Per un esaedro uniforme con flusso solo lungo x la formula si riduce a |U|Δt/Δx, cioè alla stima del richiamo. Esempio sul canale della Parte 2 (Δx = 2,5 mm, Δy = Δz = 1 mm) con velocità assiale 0,05 m/s e Δt = 0,002 s: Co = 0,05 × 0,002 / 0,0025 = 0,04. Se la mesh viene infittita in x a Δx = 1,25 mm il Courant raddoppia a 0,08 a parità di passo: infittire la mesh senza ridurre il passo aumenta Co. Usa la velocità di picco, non la media: in un condotto il massimo al centro supera sensibilmente la velocità media.
Stabilità e accuratezza sono questioni diverse. Gli schemi temporali impliciti di OpenFOAM possono restare stabili anche con Co > 1, ma l’errore di discretizzazione temporale cresce con Δt e gli algoritmi di accoppiamento pressione-velocità con poche correzioni perdono accuratezza. Per i problemi con interfacce (VoF) esiste un limite separato, maxAlphaCo, usato dal tutorial della rottura della diga insieme a maxCo 1.
Approfondimento: come cresce e si riduce il passo adattivo
Nell’implementazione Foundation dei moduli fluidi il nuovo passo si ottiene dal rapporto r = maxCo/Comax tra Courant obiettivo e massimo attuale. Quando r < 1 (Courant troppo alto) il passo viene ridotto subito in proporzione; quando r > 1 la crescita è smorzata (fattore non superiore a 1 + 0,1·r) e limitata a un aumento del 20% per passo. Infine il passo non supera mai maxDeltaT. All’avvio deltaT di controlDict è il valore iniziale, eventualmente ridotto subito se già viola maxCo.
Esempio con l’estratto di questa sezione: Δt iniziale 0,002 s, Comax = 0,10, maxCo 0.5 → r = 5, ma la crescita è limitata a ×1,2 per passo: 0,0024; 0,00288; 0,003456; 0,004147; 0,004977; poi il passo successivo (0,00597) viene troncato da maxDeltaT 0.005. Con il passo a 0,005 il Courant atteso è 0,10 × 0,005/0,002 = 0,25, ancora sotto 0,5: qui il limite attivo è maxDeltaT, non maxCo. Contemporaneamente adjustableRunTime accorcia i passi vicino a 0,010 e 0,020 per farvi cadere le scritture.
Verifica nel log: con passo adattivo, a ogni passo compare una riga deltaT = … accanto alla riga del Courant. Estrai la storia con grep '^deltaT' log.foamRun e grep 'Courant Number' log.foamRun; se deltaT non cambia mai pur con adjustTimeStep yes, il modulo o il caso non stanno usando l’adattamento (per esempio perché il caso è stazionario).
8. Modifiche durante il calcolo e arresti ordinati
Con runTimeModifiable yes;, OpenFOAM può rileggere alcuni dati di configurazione mentre un calcolo è attivo. Non tutte le voci di un dizionario si possono cambiare in sicurezza o hanno effetto allo stesso modo. Non dare mai per scontato che cambiare un file qualsiasi aggiorni il modello fisico in esecuzione.
Esecuzione controllata e ispezione
Monitoraggio del calcolo:
# Esegui in un terminale e cattura l'output.
foamRun > log.foamRun 2>&1
# In un secondo terminale, mentre il calcolo gira:
tail -f log.foamRun
Un log utile di solito contiene informazioni sul tempo/iterazione, i residui del solutore, il tempo di esecuzione e talvolta un numero di Courant. I messaggi specifici dipendono dal modulo solutore e dalle impostazioni.
Richiedere un arresto pulito
Arresto ordinato al prossimo output pianificato:
// Mentre un calcolo è attivo, in system/controlDict:
runTimeModifiable yes;
stopAt nextWrite;
Quando l’applicazione in esecuzione recepisce la rilettura, dovrebbe completare la prossima scrittura pianificata prima di fermarsi. Se devi interrompere immediatamente, distingui un’interruzione del processo da un arresto ordinato configurato; i campi validi scritti per ultimi sono il punto di restart.
Buona pratica sui server
Sui server di laboratorio condivisi: controlla lo spazio su disco prima di scritture frequenti, reindirizza i log su file e non modificare il caso in esecuzione di un collega. Se il calcolo è costoso, conserva una copia datata del file di controllo e registra esplicitamente ogni modifica fatta durante l’esecuzione.
Che cosa registrare
Annota l’impostazione cambiata, il valore precedente e quello nuovo, il tempo di simulazione in cui la modifica è stata richiesta e il primo messaggio del log che conferma che ha avuto effetto. Non reinterpretare in silenzio un calcolo come se avesse avuto impostazioni costanti.
Approfondimento: come avviene la rilettura e come modificare un file in sicurezza
A ogni passo i dizionari registrati come «da rileggere se modificati» vengono confrontati con il file su disco. Il metodo di controllo è l’opzione fileModificationChecking delle OptimisationSwitches in $FOAM_ETC/configDict (predefinito timeStampMaster: controllo della data di modifica, eseguito dal processo master in parallelo; alternative timeStamp, inotify, inotifyMaster). Su file system di rete conta anche fileModificationSkew, i secondi sottratti alla data del file per compensare ritardi e orologi non sincronizzati (Guida §4.3). Quando un file viene riletto, il log riporta un messaggio che nomina l’oggetto riletto (per esempio controlDict): è la conferma da annotare.
Che cosa risponde in modo affidabile: le voci di controllo del tempo e della scrittura di controlDict (endTime, stopAt, writeInterval, …) e, in generale, i parametri numerici di fvSchemes e fvSolution. Che cosa non va dato per scontato: la mesh, il modulo solver (già caricato), le condizioni al contorno nei campi della cartella di tempo in uso e molte proprietà fisiche.
Un editor che salva in più fasi può far leggere al solutore un file a metà, con un errore di sintassi che interrompe il calcolo. Più sicuro: una sola scrittura completa con foamDictionary -set, preceduta da una copia datata.
# 1. conserva una copia datata del file che stai per cambiare
cp -p system/controlDict system/controlDict.$(date +%Y%m%d-%H%M%S)
# 2. cambia una sola voce con un'unica scrittura completa
foamDictionary -entry stopAt -set nextWrite system/controlDict
# 3. cerca la conferma nel log
grep -n 'controlDict' log.foamRun | tail -n 3
Approfondimento: interruzioni immediate, segnali e che cosa si perde
Ctrl-C (SIGINT) o kill terminano il processo senza scrivere: tutto ciò che è stato calcolato dopo l’ultima scrittura va perso e il punto di restart è l’ultima cartella completa. Un’interruzione durante una scrittura può lasciare una cartella di tempo incompleta (alcuni campi mancanti o troncati): prima di un restart latestTime controlla che l’ultima cartella contenga tutti i campi e, se no, spostala fuori dal caso invece di cancellarla.
Confronto tra le vie ordinate: stopAt writeNow scrive subito al passo successivo alla rilettura (nessun calcolo perso, ma il tempo scritto è fuori dalla griglia pianificata); stopAt nextWrite mantiene la griglia di scrittura (a costo di attendere fino al prossimo evento); stopAt noWriteNow si ferma senza scrivere ed è utile solo quando i dati successivi all’ultima scrittura non servono. Esistono anche le OptimisationSwitches writeNowSignal e stopAtWriteNowSignal (Guida §4.3), che associano un segnale del sistema operativo a «scrivi ora» o «scrivi e fermati»; controlla il loro valore in $FOAM_ETC/configDict con foamDictionary -entry OptimisationSwitches/writeNowSignal $FOAM_ETC/configDict prima di farvi affidamento.
Modello di annotazione per il registro di laboratorio: «2026-10-16 14:32 — caso channel_B — endTime 0.020 → 0.040 — richiesto a t ≈ 0,014 — confermato nel log alla riga N (rilettura di controlDict) — motivo: estensione del transitorio».
9. Function object: un’anteprima
Il dizionario facoltativo functions in controlDict può richiedere calcoli aggiuntivi durante il calcolo: campionamenti, sonde, integrali o campi derivati. Sono controlli di acquisizione dati e di post-processing, non sostituti di fvSchemes o fvSolution.
Segnaposto minimo — nessun function object attivo:
functions
{
// Qui si possono configurare i function object.
}
Che cosa appartiene a un function object?
| Domanda | Responsabilità tipica |
|---|---|
| Quale grandezza serve? | Un campo campionato, una media, una forza, una portata, ecc. |
| Quando va valutata? | I suoi controlli di esecuzione. |
| Quando va scritta? | I suoi controlli di scrittura, che possono essere distinti. |
| Quale nome/file identifica il risultato? | Il nome del function object e le opzioni di output. |
Come imparare la sintassi in autonomia
Ricerca nella documentazione:
foamInfo incompressibleFluid
foamInfo patchAverage
# Guarda i tutorial v14 funzionanti prima di aggiungere
# un particolare function object al tuo caso.
echo "$FOAM_TUTORIALS"
I function object disponibili dipendono dall’installazione di OpenFOAM e dalle librerie. Cerca nei tutorial nativi v14 un caso adatto al canale. Per la sintassi completa usa i riferimenti ufficiali sui function object del Capitolo 7 della guida (Post-processing).
Non sovraccaricare questa unità
Questa parte introduce dove vivono i function object e come identificarne lo scopo. Il canale di riferimento deve prima funzionare correttamente senza alcun function object; la configurazione dell’output derivato si può aggiungere come passo separato e verificabile.
Approfondimento: dalla v12 il file system/functions e #includeFunc
La Guida v14 (§7.2) indica che dalla v12 i function object vanno preferibilmente in un file separato system/functions, invece che nel sotto-dizionario functions di controlDict: così le applicazioni che non usano function object non devono leggerli. La vecchia collocazione in controlDict, mostrata qui sopra, funziona ancora. Le funzioni preconfigurate si includono con la direttiva #includeFunc, passando gli argomenti tra parentesi; quelle con molti parametri (per esempio le forze) si copiano nel caso con foamGet e si modificano: una copia locale in system ha la precedenza sui modelli in $FOAM_ETC/caseDicts/postProcessing.
# Dalla v12 in poi è preferibile un file separato system/functions
foamGet functions # copia un modello in system/
# dentro system/functions, per esempio:
#includeFunc patchFlowRate(patch=outlet)
foamPostProcess -list # funzioni configurate disponibili
foamToC -functionObjects # tipi di function object sottostanti
# dopo il calcolo, con i modelli del modulo solutore costruiti:
foamPostProcess -solver incompressibleFluid -func "patchFlowRate(patch=outlet)"
foamInfo dà descrizione ed esempi per modelli, moduli e funzioni; foamPostProcess -list elenca le funzioni configurate disponibili; foamToC -functionObjects elenca i tipi di function object compilati. I risultati vanno di norma nella cartella postProcessing/<nome>/<tempo di avvio>/. Per il canale, una portata in uscita (patchFlowRate) è il primo controllo naturale: deve uguagliare la portata in ingresso imposta nella Parte 3.
Approfondimento: controlli di esecuzione e di scrittura di un function object
La tabella sopra distingue «quando valutare» e «quando scrivere». Nei function object Foundation le due domande corrispondono alle coppie executeControl/executeInterval e writeControl/writeInterval, che accettano le stesse modalità viste nella sezione 4 (timeStep, runTime, adjustableRunTime, clockTime, cpuTime) più writeTime, che lega l’evento alle scritture dei campi del caso. Una sonda può quindi essere valutata a ogni passo e scritta a ogni passo (per una storia temporale fine) mentre i campi completi vengono salvati solo ogni 5 passi: è proprio il modo di avere molti dati senza riempire il disco.
Attenzione alla coerenza con purgeWrite: i dati dei function object non vengono eliminati da purgeWrite, ma un function object che lavora «a posteriori» con foamPostProcess può elaborare solo le cartelle di tempo rimaste. Decidi prima del calcolo quali grandezze servono durante l’esecuzione e quali si possono ricavare dopo.
10. Laboratorio guidato: calcolo di base
Riusiamo lo stesso canale 3D con inlet, outlet, walls e i file 0/U e 0/p definiti in precedenza. Non eseguire finché i dizionari restanti e i file dei modelli non sono validi per OpenFOAM Foundation 14.
Fase A — Ispezionare prima di cambiare
Leggi la configurazione esistente:
foamVersion
pwd
foamDictionary -entry solver -value system/controlDict
foamDictionary -entry startFrom -value system/controlDict
foamDictionary -entry writeControl -value system/controlDict
foamDictionary -entry writeInterval -value system/controlDict
Fase B — Impostare una base prevedibile
Valori da impostare nella tua copia:
startFrom startTime;
startTime 0;
stopAt endTime;
endTime 0.020;
deltaT 0.002;
writeControl timeStep;
writeInterval 5;
purgeWrite 0;
adjustTimeStep no;
Fase C — Prevedere, poi osservare
- Prevedi quanti passi verranno completati e a quali tempi verrà scritto l’output.
- Ispeziona l’elenco delle cartelle iniziale prima del calcolo; distingui la
0/originale dalle successive cartelle dei risultati. - Esegui il caso v14 esistente con
foamRun; reindirizza il suo log. - Ispeziona le cartelle generate e confrontale con la tua previsione.
- Registra se il calcolo è terminato normalmente; non dedurre il completamento riuscito solo dalla presenza di un file di log.
Esegui solo un caso completo e compatibile
I campi della Parte 3 e la mesh della Parte 2 da soli non bastano per eseguire un solutore. Per questo laboratorio usa il tutorial già funzionante o il caso completo e validato fornito con la serie formativa; evita di inventare file fisici o numerici mancanti.
Approfondimento: risultati attesi della Fase C e come riconoscere una fine normale
Previsione: 0,020/0,002 = 10 passi; scritture ai passi 5 e 10 → cartelle 0.01 e 0.02. Prima del calcolo ls mostra 0, constant, system (più eventuali script del caso); dopo, compaiono 0.01, 0.02 e log.foamRun. 0/ resta invariata: il solutore non la sovrascrive.
Una fine normale ha tre segni concordi: l’ultima riga del log è End; nel log non compare FOAM FATAL ERROR o FOAM FATAL IO ERROR; il codice di uscita del processo è 0 (echo $? subito dopo foamRun). Se si usa | tee invece del reindirizzamento, il codice di uscita è quello di tee a meno di impostare prima set -o pipefail. Un file di log esiste anche dopo un errore al primo passo: per questo la sola presenza del file non prova nulla.
tail -n 3 log.foamRun # una terminazione normale finisce con la riga "End"
grep -c '^Time = ' log.foamRun # numero di passi completati (qui 10)
grep -n 'FOAM FATAL' log.foamRun # nessun output = nessun errore fatale registrato
foamListTimes # tempi scritti, 0 escluso
Approfondimento: dove trovare un caso completo per il laboratorio
Le due fonti indicate sono un tutorial nativo v14 già funzionante oppure il caso completo e validato della serie: il primo caso pratico, il canale laminare di Poiseuille, contiene tutti i file necessari (mesh, campi, physicalProperties, momentumTransport, fvSchemes, fvSolution) per incompressibleFluid. Tra i tutorial, cerca quelli che selezionano lo stesso modulo e copiane uno nella tua cartella di lavoro prima di toccarlo.
# Casi nativi v14 che selezionano il modulo incompressibleFluid
grep -rl --include=controlDict 'incompressibleFluid' "$FOAM_TUTORIALS" | head
# Copiane uno nella tua cartella di lavoro prima di toccarlo
mkdir -p ~/run && cp -a "$FOAM_TUTORIALS/incompressibleFluid/cavity" ~/run/cavityBaseline
ls ~/run/cavityBaseline
Il percorso della cavità è quello abituale nelle versioni Foundation recenti; se nella tua installazione è diverso, usa il risultato della ricerca con grep. Molti tutorial vanno preparati con un passo di mesh (blockMesh) o hanno uno script Allrun: leggilo prima di eseguirlo e, per questo laboratorio, sostituisci solo i valori di controlDict della Fase B. Una cavità è chiusa: le patch non si chiamano inlet/outlet, ma la logica del tempo è identica.
11. Laboratorio guidato: confrontare le strategie di scrittura
Fai copie separate del caso di base validato, così le cartelle di output del primo esperimento non influenzano il secondo. Entrambi i casi devono partire dai rispettivi stati 0/.
Esperimento A — Ogni 5 passi
writeControl timeStep;
writeInterval 5;
Previsione: con Δt = 0,002 s e tempo finale 0,020 s, output regolare pianificato a 0,010 e 0,020.
Esperimento B — Ogni 0,006 s
writeControl runTime;
writeInterval 0.006;
Previsione: output pianificato a 0,006, 0,012 e 0,018, perché ogni istante richiesto è un multiplo esatto del Δt fisso. Nessun evento regolare cade a 0,020.
Esperimento C — Cambiare solo il formato dei file
writeFormat binary;
writeCompression on;
Esegui una terza copia indipendente del caso e confronta dimensione dei file e leggibilità, non la fisica del flusso. Ricorda che l’output binario non è pensato per l’ispezione manuale come testo.
| Da registrare nella scheda | Esperimento A | Esperimento B | Esperimento C |
|---|---|---|---|
| Nomi delle cartelle di output | ________ | ________ | ________ |
| Numero di istantanee scritte | ________ | ________ | ________ |
| Tempo di esecuzione trascorso | ________ | ________ | ________ |
| Dimensione totale dei risultati | ________ | ________ | ________ |
Regola del confronto
Non cambiare mai tutti insieme passo temporale, cadenza di scrittura, campi fisici e solutori numerici. Questo esercizio isola una decisione di input/output alla volta per consentire un’interpretazione causale.
Approfondimento: valori attesi per la scheda e come misurarli
| Riga della scheda | A | B | C |
|---|---|---|---|
| Nomi delle cartelle | 0.01, 0.02 | 0.006, 0.012, 0.018 | 0.01, 0.02 |
| Istantanee scritte (0 escluso) | 2 | 3 | 2 |
| Contenuto di una cartella | U, p, phi, uniform/ | come A | U.gz, p.gz, phi.gz, uniform/ |
| Tempo di esecuzione | riferimento | ≈ A (una scrittura in più) | ≈ A |
| Dimensione | riferimento | ≈ 1,5 × A | minore di A |
Due osservazioni da scrivere nella scheda. In B lo stato finale calcolato a 0,020 non viene salvato: il calcolo esegue 10 passi, ma gli ultimi due (0,018 → 0,020) non lasciano traccia su disco; un restart da B riparte da 0,018. Per evitarlo, scegli un writeInterval che divida l’intervallo totale. In C la riduzione di dimensione dipende dalla mesh e dai valori (campi quasi uniformi si comprimono molto); su una mesh da 2.000 celle il tempo di calcolo è dominato dall’avvio e dal solutore, quindi differenze di tempo di pochi decimi di secondo sono rumore: ripeti la misura prima di trarre conclusioni.
# tre copie indipendenti della stessa base validata
for c in A B C; do cp -a channelBaseline channel_$c; done
foamDictionary -set "writeControl=runTime, writeInterval=0.006" channel_B/system/controlDict
foamDictionary -set "writeFormat=binary, writeCompression=on" channel_C/system/controlDict
for c in A B C; do (cd channel_$c && foamRun > log.foamRun 2>&1); done
# misure per la scheda di laboratorio
for c in A B C; do
echo "== $c"
(cd channel_$c && foamListTimes && du -sh $(foamListTimes) \
&& grep 'ExecutionTime' log.foamRun | tail -n 1)
done
12. Esercizi autonomi
Esercizio A — Leggere e classificare (guidato)
Dato il controlDict di riferimento completo della sezione 2, rispondi a queste domande senza eseguire il solutore:
- Quale voce sceglie il modulo solutore v14? Quale voce seleziona lo stato di partenza?
- Quanti passi temporali sono pianificati e quando avvengono le scritture regolari?
- Quale voce governa la formattazione dei nomi delle cartelle di tempo? Quale controlla la precisione dei valori dei campi ASCII salvati?
- Quale parametro determina se i vecchi tempi scritti vengono eliminati automaticamente?
Esercizio B — Modificare e prevedere (parzialmente guidato)
Imposta il calcolo per partire da 0, terminare a 0,060, usare un passo fisso Δt = 0,003 e scrivere una volta ogni quattro passi. Prima di eseguire, prevedi tutti i tempi di scrittura regolari e quanti passi temporali sono pianificati.
Esercizio C — Riprendere deliberatamente (parzialmente guidato)
Supponi che un calcolo validato abbia scritto stati a 0, 0,012 e 0,024. Configura un restart dall’ultimo stato salvato con un nuovo tempo finale di 0,048. Indica quali file di campo verranno letti all’avvio e se cambiare startTime influirebbe su un restart latestTime.
Esercizio D — Costruire in autonomia
Partendo da un caso di canale v14 verificato, scrivi un nuovo controlDict che usi il modulo appropriato, parta da 0, esegua dieci passi fissi di Δt = 0,001, scriva ogni secondo passo e conservi ogni tempo scritto. Poi spiega in linguaggio semplice ogni voce che non appartiene all’intestazione.
L’accettazione non è un obiettivo numerico
Per questi esercizi, un risultato riuscito significa sintassi corretta, ragionamento corretto sul controllo del calcolo, log puliti dove applicabile e accordo tra file previsti e file osservati. Non stabilisce l’indipendenza dalla mesh né un flusso validato fisicamente.
13. Diagnosi: il primo errore significativo
| Sintomo | Causa probabile | Prima azione |
|---|---|---|
| Nessuna cartella di tempo successiva | Nessun passo riuscito, output non ancora dovuto, tempo di arresto sbagliato o calcolo fallito. | Leggi log.foamRun e confronta con la politica di scrittura. |
| Stato di partenza inatteso | latestTime seleziona un tempo salvato invece di 0. | Elenca le cartelle di tempo e ispeziona startFrom. |
| «Solver not found» / problema di caricamento | Nome del modulo sbagliato o selezione legata a una versione. | Controlla la voce solver v14 e foamInfo. |
| Cartella di output enorme | Scritture molto frequenti, mesh grande, opzioni di output. | Controlla writeControl, intervallo, formato e spazio su disco. |
| Le istantanee precedenti spariscono | purgeWrite diverso da zero. | Ispeziona l’impostazione di conservazione; recupera solo da backup. |
| Il restart fallisce nella lettura di un campo | Stato salvato incompatibile o privo di un campo richiesto. | Individua il primo campo mancante e l’incompatibilità di modello. |
| Il CFL resta grande | Adattamento del passo non disponibile/ignorato, passo troppo grossolano o flussi locali intensi. | Ispeziona il log, le capacità del modulo e mesh/velocità. |
Verifica prima di ogni calcolo
Per un caso esistente, completo e valido:
foamVersion
foamDictionary system/controlDict
ls -d [0-9]* 2>/dev/null
# Poi esegui e ispeziona il primo errore informativo:
foamRun > log.foamRun 2>&1
less log.foamRun
Non nascondere mai le prove diagnostiche
Conserva il log e il dizionario originale prima delle modifiche. Non cancellare processor* (le cartelle della decomposizione parallela), le cartelle di tempo o i file di riferimento come scorciatoia per diagnosticare un errore. Una pulizia del genere può distruggere i dati necessari a capire un fallimento.
Approfondimento: come leggere il primo errore
Gli errori fatali di OpenFOAM hanno una struttura fissa: un’intestazione --> FOAM FATAL ERROR (errore di logica o di selezione) oppure --> FOAM FATAL IO ERROR (errore di lettura di un file), un messaggio, poi il file e la riga coinvolti e la funzione del codice che ha rilevato il problema. Cerca il primo blocco del genere con grep -n -A 12 'FOAM FATAL' log.foamRun | head -n 30: gli errori successivi sono spesso conseguenze.
| Schema del messaggio | Significato tipico in questa parte |
|---|---|
| «keyword X is undefined in dictionary …/system/controlDict» | voce obbligatoria mancante o scritta male (per esempio solver, writeInterval) |
| «cannot find file … 0.024/p» | restart da una cartella incompleta o con un campo mancante |
| errore di selezione che nomina un tipo sconosciuto ed elenca i tipi validi | nome del modulo o di un’opzione scritto male; confronta con l’elenco stampato |
| dimensioni o numero di valori incompatibili nella lettura di un campo | campo salvato su un’altra mesh |
Le formulazioni esatte cambiano tra versioni: usa la tabella per riconoscere la categoria, non come testo da cercare parola per parola. Correggi una causa alla volta e rilancia lo stesso comando, conservando il log precedente con un nome diverso (mv log.foamRun log.foamRun.1).
14. Risposte svolte e valutazione
Esercizio A — Lettura e classificazione
Ecco la soluzione dell’esercizio. Il modulo è scelto da solver incompressibleFluid;; lo stato di partenza da startFrom startTime; (con startTime 0;, quindi la cartella 0/). Sono pianificati 0,020/0,002 = 10 passi; le scritture regolari avvengono ai passi 5 e 10, cioè a 0,010 e 0,020 (cartelle 0.01 e 0.02). I nomi delle cartelle sono governati da timeFormat insieme a timePrecision; la precisione dei valori ASCII salvati da writePrecision. L’eliminazione automatica dei vecchi tempi è decisa da purgeWrite (qui 0: nessuna eliminazione).
Esercizio B — Aritmetica del tempo
Da 0 a 0,060 con Δt = 0,003: N = 0,060 / 0,003 = 20 passi. Con writeInterval 4; e writeControl timeStep;, le scritture regolari avvengono ai passi 4, 8, 12, 16 e 20: 0,012; 0,024; 0,036; 0,048; 0,060.
Le voci da impostare sono startFrom startTime; startTime 0; stopAt endTime; endTime 0.060; deltaT 0.003; writeControl timeStep; writeInterval 4;. Con timeFormat general le cartelle si chiameranno 0.012, 0.024, 0.036, 0.048 e 0.06 (lo zero finale dell’ultima sparisce). Lo stesso risultato si ottiene con writeControl runTime; writeInterval 0.012;, perché 0,012 è un multiplo esatto di 0,003.
Esercizio C — Logica del restart
Con startFrom latestTime;, viene selezionata una cartella salvata valida 0.024/ se è il tempo disponibile più recente. Il solutore legge lì i campi richiesti, non da 0/. L’obiettivo richiesto è endTime 0.048;. Un valore assegnato a startTime non è il criterio di selezione in questa modalità.
In concreto, per il canale con incompressibleFluid vengono letti 0.024/U, 0.024/p, 0.024/phi se presente (e i campi dei modelli, se esistono), più 0.024/uniform/time. La configurazione è startFrom latestTime; stopAt endTime; endTime 0.048;, lasciando invariate le voci di scrittura; prima di lanciare, foamListTimes -latestTime deve restituire 0.024. Se il calcolo originale aveva scritto ogni 0,012, il restart scriverà a 0,036 e 0,048, e completerà 0,024/Δt passi in più.
Esercizio D — Un blocco di controllo valido
Estratto della risposta; aggiungi una normale intestazione FoamFile e le opzioni di I/O:
solver incompressibleFluid;
startFrom startTime;
startTime 0;
stopAt endTime;
endTime 0.010;
deltaT 0.001;
writeControl timeStep;
writeInterval 2;
purgeWrite 0;
adjustTimeStep no;
Tempi di scrittura regolari attesi: 0,002; 0,004; 0,006; 0,008 e 0,010. L’estratto deve comunque essere inserito in un system/controlDict valido di un caso v14 compatibile.
Spiegazione voce per voce (la seconda metà dell’esercizio):
| Voce | Spiegazione in linguaggio semplice |
|---|---|
solver incompressibleFluid; | foamRun carica il modulo per fluidi incompressibili e isotermi. |
startFrom startTime; / startTime 0; | parti deliberatamente dalla cartella 0/, anche se esistessero tempi successivi. |
stopAt endTime; / endTime 0.010; | fermati quando il tempo simulato raggiunge 0,010 s. |
deltaT 0.001; | ogni passo avanza di 1 ms: 0,010/0,001 = 10 passi. |
writeControl timeStep; / writeInterval 2; | salva i campi ogni 2 passi: cinque istantanee. |
purgeWrite 0; | non eliminare mai i tempi scritti. |
adjustTimeStep no; | il passo resta fisso; il numero di passi è prevedibile. |
Le «opzioni di I/O» da aggiungere sono quelle del riferimento della sezione 2: writeFormat, writePrecision, writeCompression, timeFormat, timePrecision e runTimeModifiable. Con timeFormat general le cartelle si chiameranno 0.002, 0.004, 0.006, 0.008 e 0.01.
Lista di verifica per l’accettazione
- Identifica il modulo solutore v14 senza ricadere nella sintassi obsoleta
application. - Sa calcolare in autonomia passi e scritture pianificate.
- Sa configurare e spiegare correttamente un restart.
- Capisce perché
purgeWritee le modifiche durante il calcolo richiedono attenzione. - Sa ispezionare i log e giustificare ogni impostazione principale di
controlDict.
Appendice · Riferimenti ufficiali e navigazione
La fonte primaria di questa parte è la Guida utente di OpenFOAM v14, OpenFOAM Foundation / CFD Direct, pubblicata a luglio 2026. Usa la versione della documentazione che corrisponde all’installazione del laboratorio.
Letture principali
- 4.4 — Controllo del tempo e dell’input/output dei dati
- 2.1 — Gradino all’indietro (backward-facing step), in particolare §§ 2.1.7 e 2.1.9
- 2.2 — Rottura di una diga (passo adattivo e scrittura a tempi fissi)
- 4.7 — Strumenti di gestione del caso
- Guida utente v14 — indice completo
Letture usate negli approfondimenti
- 4.3 — Controlli globali (configDict, OptimisationSwitches)
- 7.2 — Post-processing da riga di comando (#includeFunc, foamPostProcess)
- 7.3 — Funzionalità di post-processing
- 3.5 — Moduli solutore
- 5.8 — Mappatura dei campi tra geometrie diverse
Glossario essenziale
| Termine | Significato in questa unità |
|---|---|
| Tempo simulato | Variabile tempo indipendente fatta avanzare dal processo numerico configurato. |
| Passo temporale, Δt | Incremento tra tempi di soluzione successivi riportati in un calcolo transitorio. |
| Evento di scrittura | Un istante pianificato in cui i campi vengono salvati nelle cartelle di tempo. |
| Restart | Una nuova esecuzione che legge uno stato salvato in precedenza e compatibile. |
| Tempo dell’orologio (clock time) | Durata reale del calcolo. |
| Modulo solutore | Modulo di fisica/algoritmo selezionato dalla voce solver nella v14. |
| Tempo di CPU | Tempo di processore consumato dal calcolo (ExecutionTime nel log). |
| Numero di Courant, Co | Frazione del volume di una cella attraversata dal fluido in un passo (sezione 7). |
Prossima unità · Parte 5
fvSchemes: dalle equazioni alla discretizzazione. La lezione successiva collegherà i termini delle equazioni di governo agli schemi ai volumi finiti di OpenFOAM. Mantieni stabili le impostazioni di tempo e output di questa lezione, così le modifiche numeriche si potranno studiare in modo indipendente.
Parte successiva: OpenFOAM 14, parte 5: fvSchemes e discretizzazione. Torna all’indice del percorso Imparare OpenFOAM 14.