Agentic CFD

Résolvez un problème d'écoulement de fluide de bout en bout avec OpenFOAM et produisez un rapport HTML vérifié (problème, hypothèses, théorie, décisions, test préalable, simulation, comparaison, conclusions). À utiliser lorsqu'un énoncé de problème CFD est fourni dans cases/<name>/problem.yaml ou lorsque vous devez simuler un écoulement et en faire un rapport.

Qu'est-ce que Agentic CFD ?

Cette compétence transforme un énoncé de problème de mécanique des fluides numérique (CFD) en un rapport de simulation fiable et vérifié, au format HTML. Elle est conçue pour les ingénieurs et repose sur quatre règles strictes : chaque nombre est calculé par le code, chaque choix de configuration (OpenFOAM) est documenté, aucune donnée n'est issue de la mémoire (tout provient des fichiers de référence ou de sources citées), et les observations sont consignées en direct comme dans un cahier de laboratoire. La compétence guide l'utilisateur à travers des étapes claires allant de la réception du problème (lecture du fichier YAML, calcul des nombres sans dimension) à la rédaction du rapport final, en passant par la théorie (résolution symbolique avec SymPy si possible), la mise en place des cas de simulation (avec plans de maillage, étude de convergence), un test préalable obligatoire (preflight) pour valider la configuration avant les longs calculs, les simulations de production, l'analyse (erreurs, ordre observé, GCI, signaux périodiques) et enfin la génération du rapport HTML avec vérification automatique du contenu (contrat). Elle s'appuie sur des exemples concrets (écoulement de Poiseuille, cavité entraînée, problème de Stokes, cylindre de DFG) et fournit des outils partagés (cfdlib) pour le post-traitement, la vérification et le rendu. Idéale pour les agents comme Claude Code ou Cursor qui doivent produire des simulations CFD reproductibles et documentées.

Compatible avec✓Claude Code~Codex CLI~Cursor✓Antigravity
npx skills add https://github.com/tomyud1/agentic-cfd/tree/HEAD/.claude/skills/agentic-cfd

Installed? Explore more Recherche et analyse de données skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Workflow CFD Agentique

Vous transformez un énoncé de problème en un rapport de simulation fiable. Le lecteur est un ingénieur qui lira principalement le résumé et doit pouvoir vérifier chaque affirmation. Quatre règles :

  1. Le code calcule chaque nombre. Vous écrivez le code ; vous ne saisissez jamais manuellement un résultat, une valeur de référence ou une sortie de formule dans le rapport. La théorie est résolue symboliquement (SymPy) et vérifiée automatiquement.
  2. Chaque choix est documenté. Chaque paramètre OpenFOAM appartient à une décision avec pourquoi / effet / alternatives. Un vérificateur refuse d'afficher le rapport dans le cas contraire.
  3. Rien issu de la mémoire. Les données de référence, corrélations et constantes proviennent des fichiers reference/ du problème ou d'une source citée. Si vous devez utiliser une valeur approximative, mentionnez-le dans le rapport.
  4. Les observations sont consignées en direct, comme un cahier de laboratoire. Une partie de votre travail consiste à améliorer ce workflow pour les agents après vous. Dès qu'un échec survient, vous surprend, nécessite une seconde tentative, ou est flou dans ces directives, ouvrez une observation (avant de commencer à la corriger) : insights.log(case, stage, problem) ; renseignez le diagnostic, le correctif, la leçon et la directive avec insights.resolve(...) une fois résolu. Ne les gardez pas pour la fin : les souvenirs d'une longue session se perdent et se déforment. Attendez-vous à enregistrer 5 à 15 entrées ; une exécution sans aucune observation n'a pas été examinée de près.

Structure des dossiers

cfdlib/                       outils partagés (NE PAS modifier ; consigner les changements nécessaires comme des observations, étape "tooling")
  foam.py        render_case, run (timeout, fichier PID), stop, running ; lire les fichiers de champs et postProcessing .dat
  monitor.py     parse_log, assess (contrôles de santé), preflight (court test + projection du temps d'exécution)
  verification.py normes d'erreur, ordre observé, extrapolation de Richardson, GCI, level_sequence
                 (convergence de toute séquence de niveaux, y compris celle d'une référence)
  signals.py     signaux périodiques : cycles, pics affinés, vérification de périodicité, FFT
  plots.py       style et tailles des figures (figure, wide, sketch) ; to_svg vérifie le chevauchement du texte
  report.py      m(), tex_num(), tex_fix() (décimales fixes pour les colonnes de tableaux), tex_pct(), LETTERS, load_yaml,
                 environment_info(), render()
  perf.py        performances par exécution (summarize, machine) pour l'annexe de performances
  contract.py    vérificateur du contrat du rapport (également en mode --decisions)
  insights.py    journal d'observations en direct (vérifie la typographie lors de l'enregistrement ; refuse d'écraser une entrée remplie)
  foamdict.py    analyseur de dictionnaires OpenFOAM (utilisé par le vérificateur)
cases/<name>/                 un dossier par problème
  problem.yaml   DONNÉ par l'utilisateur : description (aperçu court), setup (liste de faits : géométrie,
                 conditions limites, définitions), données fournies, sorties demandées [+ reference/].
                 Ne modifiez pas la physique. Transmettez description et setup au rapport inchangés.
  reference/     données de référence fournies par l'utilisateur avec citations
  numerics.yaml  votre plan numérique (maillages, durées d'exécution, tolérances)
  decisions.yaml chaque choix, écrit PENDANT que vous configurez le cas
  insights.yaml  le journal en direct (cfdlib.insights)
  template/      le cas OpenFOAM (0/, constant/, system/) avec des {{ espaces réservés }}
  theory.py / analytical.py, sketch.py, run.py, analyze.py
results/<name>/  données brutes, results.json, report.html

Exemples commentés (lisez tous leurs fichiers avant de commencer ; copiez la structure, pas les paramètres numériques) :

  • cases/plane_poiseuille/ : linéaire, stationnaire, solution exacte (vérification). Rapport : results/plane_poiseuille/report.html.
  • cases/lid_driven_cavity_re100/ : non linéaire, stationnaire, sans solution exacte, benchmark publié (comparaison avec benchmark, extrapolation de Richardson, SIMPLEC arrêté par résidu, commandes distinctes pour le test préalable et la production). Rapport : results/lid_driven_cavity_re100/report.html. Son insights.yaml contient 15 leçons de l'agent qui l'a construit.
  • cases/stokes_first_problem/ : linéaire, TRANSITOIRE, solution exacte (études combinées et séparées du maillage et du pas de temps, bilan de quantité de mouvement par pas, exécutions concurrentes). Rapport : results/stokes_first_problem/report.html.
  • cases/dfg_cylinder_re100/ : non linéaire, TRANSITOIRE et périodique (détachement tourbillonnaire), benchmark avec plusieurs niveaux de référence, maillage O-grid pour corps courbes généré en Python, exécutions MPI et annexe de performances. Rapport : results/dfg_cylinder_re100/report.html. Son insights.yaml contient 16 leçons (la plus importante : PISO était du premier ordre en temps).

Étapes

0. Réception

  • Lisez problem.yaml et tous les fichiers reference/. Listez les sorties demandées (elles reçoivent les lettres a, b, c...).
  • Calculez les nombres sans dimension (Re, Ma, ...) dans le code. Décidez quelle physique s'applique.
  • Si des données sont manquantes ou contradictoires, choisissez une hypothèse documentée et signalez-la dans les limites du rapport. N'inventez jamais d'entrées en silence.
  • Si une référence ne donne pas d'incertitude, estimez-en une (son maillage, ses chiffres imprimés, une seconde source) et enregistrez comment ; vous en aurez besoin à l'étape 5.

1. Théorie : ce qui peut être connu sans la simulation

  • Solution exacte disponible : dérivez-la avec SymPy, vérifiez-la automatiquement (satisfait les équations et conditions limites), évaluez les quantités demandées. Voir cases/plane_poiseuille/analytical.py.
  • Pas de solution exacte (cas habituel) : dites-le dans theory.intro, et donnez ce que la théorie PEUT dire : cas limites, mise à l'échelle, lois de conservation, symétrie, signe des quantités, structure d'écoulement attendue. Voir cases/lid_driven_cavity_re100/theory.py.
  • Chaque quantité dérivée reçoit : pourquoi elle est importante, la signification et la valeur de chaque symbole dans ce problème, et la formule avec les nombres substitués (vérification manuelle).

2. Configuration et décisions

  • Trouvez le tutoriel OpenFOAM le plus proche ($FOAM_TUTORIALS) et partez de celui-ci ; faites des modifications minimales.
  • Lisez references/openfoam-primer.md pour savoir ce que chaque fichier contrôle et les pièges connus.
  • Les modèles peuvent contenir des boucles Jinja : générer tout un blockMeshDict à partir de Python est acceptable et est la manière de mailler les corps courbes (exemple : mesh_spec() dans cases/dfg_cylinder_re100/run.py).
  • Modifiez les fichiers contenant du LaTeX uniquement avec les outils Edit/Write, jamais via des heredocs Bash ou du Python en ligne : l'outil Bash mange les antislashs (\times devient un TAB). Les cas 3 et 4 ont perdu du temps à cause de cela.
  • Écrivez decisions.yaml au fur et à mesure : une entrée par choix avec kind (physics / accuracy / solver / output) et des motifs settings couvrant chaque paramètre dans les fichiers du cas. Vérifiez la couverture dès qu'un cas est rendu : python -m cfdlib.contract --decisions cases/<name>/decisions.yaml <cas dir>.
  • Planifiez une étude de maillage : au moins 3 maillages, rapport de raffinement 2 (nécessaire pour l'ordre observé et le GCI). Les problèmes transitoires nécessitent également une étude du pas de temps (ou un argument, avec des nombres, montrant que l'erreur de temps est négligeable).
  • Définissez un critère de convergence/stabilité physique pour votre problème (un bilan, une quantité conservée, une valeur surveillée qui doit cesser de changer) et sa tolérance AVANT de voir les résultats. Les résidus normalisés seuls ne suffisent pas. Problèmes transitoires : un bilan par pas (variation d'une quantité conservée = flux aux frontières) est un critère solide ; il doit utiliser le schéma temporel du solveur lui-même.
  • Les tolérances de succès/échec doivent discriminer : dimensionnez-les à partir de l'erreur du test préalable grossier et de l'ordre attendu (ce que le maillage le plus fin devrait atteindre), pas à partir de nombres ronds. Une tolérance 100 fois plus lâche que ce qui est atteint ne prouve rien (cas 3). Faites de l'ordre observé de précision une règle en soi.

3. Test préalable (obligatoire, avant toute longue exécution)

  • cfdlib.monitor.preflight() sur les maillages les plus grossiers ET les plus fins avec une courte exécution (quelques % de la durée totale). Il fonctionne dans une copie séparée <cas>_preflight ; rendez également les cas de test préalable et de production dans des répertoires séparés, de sorte que la réexécution de l'un ne supprime jamais l'autre.
  • Il signale les crashs, NaN, nombre de Courant, tendances des résidus/Courant/continuité et projette le temps d'exécution. Exécutions transitoires : la projection est fiable. Exécutions stationnaires : passez residual_targets ; le résultat est une borne inférieure (SIMPLE ralentit plus tard et sur des maillages plus fins).
  • Imprimez l'en-tête et les premières lignes de chaque fichier postProcessing que le test préalable a écrit et vérifiez que chacun est non trivial (cas 4 : une intégrale de pression a écrit 0 à chaque pas, en silence).
  • Problèmes non linéaires transitoires : une courte étude du pas de temps (3 pas de temps, rapport 2, sur un maillage grossier) appartient au test préalable, avant l'étude de maillage. Elle montre l'ordre observé dans le temps : PISO (1 correcteur externe) s'est avéré du premier ordre dans le cas 4 et a coûté une réexécution complète.
  • Un test préalable qui démarre au repos sous-estime le coût d'un écoulement instationnaire développé (cas 4 : deux fois). Démarrez-le à partir d'un champ développé (mapFields à partir d'une exécution grossière) ou prévoyez le double de la projection.
  • S'il échoue ou émet un avertissement, corrigez la configuration et enregistrez une observation. Pas d'exécutions de production sur un test préalable défaillant. Si le temps projeté est excessif (> ~15 min par exécution ici), simplifiez le plan et dites-le.

4. Exécutions de production

  • Exécutez les maillages du plus grossier au plus fin. Pour les exécutions stationnaires, extrapolez le nombre d'itérations du maillage le plus fin à partir des plus grossiers (il augmente souvent de 3 à 4 fois par raffinement) avant de le lancer.
  • L'arrêt doit être physique : faites du critère physique de l'étape 2 la règle d'arrêt (par exemple un objet de fonction runTimeControl sur des valeurs sondées, voir le primer), ou vérifiez par maillage que les cibles de résidu le satisfont réellement, d'abord sur les maillages grossiers.
  • Les exécutions indépendantes (différents maillages) peuvent s'exécuter en même temps, jusqu'à 6 à la fois (8 cœurs). Mentionnez-le dans les notes de reproductibilité, car cela gonfle les temps muraux par exécution.
  • Les longues exécutions se font en arrière-plan ; python -m cfdlib.foam ps liste les processus du solveur, et python -m cfdlib.foam stop <cas dir> en tue un (arrêter seul le wrapper Python laisse un solveur orphelin). Enregistrez par exécution : état de santé (monitor.assess), le critère physique, le temps mural.
  • Exécutez une séquence de longues exécutions comme UN SEUL script d'arrière-plan qui écrit un marqueur d'achèvement et un enregistrement par exécution. Après toute interruption (y compris une pause API), lisez ces fichiers avant de réexécuter quoi que ce soit.
  • Pendant que tous les threads matériels exécutent un solveur, votre propre travail ralentit : laissez 1 à 2 threads libres ou prévoyez 1,5 fois la projection.

5. Analyse

  • Toutes les comparaisons dans le code. Avec une solution exacte : erreurs et ordre observé par maillage. Sans : ordre observé et extrapolation de Richardson à partir de 3 maillages, GCI sur le maillage le plus fin (verification.py), puis comparez la valeur extrapolée avec la référence.
  • Un benchmark est aussi une simulation : jugez l'accord par rapport à (votre bande GCI + l'incertitude propre de la référence), pas par rapport à zéro. Si la différence ne diminue pas avec le raffinement, ce n'est pas votre erreur de discrétisation ; testez la robustesse (un autre schéma, des itérations plus serrées) avant de blâmer l'un ou l'autre côté, et recommandez une seconde référence indépendante.
  • Lorsque la référence donne plusieurs niveaux de maillage, elle converge encore : calculez SON ordre observé et sa valeur de Richardson (verification.level_sequence) et comparez votre résultat avec à la fois son niveau le plus fin et sa valeur extrapolée. Décidez la règle de succès avant de regarder (le cas 4 a gardé la règle du niveau le plus fin et a rapporté l'extrapolation comme contexte).
  • Sorties périodiques (détachement, oscillations) : utilisez cfdlib.signals (cycles entre passages ascendants, pics affinés par parabole, vérification de périodicité, recoupement FFT) au lieu d'écrire les vôtres.
  • Une correspondance sur un maillage grossier peut être un coup de chance (annulation d'erreur). Rapportez les différences honnêtement, où et pourquoi.
  • Une explication est une affirmation : vérifiez-la par rapport aux données (signe, taille, mise à l'échelle) avant de l'écrire. Dans le cas 3, la première explication d'une erreur avait le mauvais signe et un tiers de la taille.

6. Rapport

  • Construisez results.json dans analyze.py et appelez report.render(results, svgs, out, case_dir=<maillage le plus fin>). Le contrat se trouve dans references/report-contract.md ; render refuse d'écrire un rapport qui le brise.
  • Mathématiques : chaque formule, symbole et nombre scientifique dans le texte en LaTeX à l'intérieur de \( \) (m(), tex_num()) : écrivez \(0.009\,U\), jamais 0.009 U, 6.3e-06 ou u*H/nu. Le vérificateur rejette la notation exponentielle et les mathématiques ASCII, et avertit sur les multiplications nombre-symbole en texte brut.
  • Les légendes et les conclusions sont aussi des résultats : générez-les à partir des données (une légende disant "diminue" doit être calculée). Référez-vous aux figures comme {fig:<clé>} ; render() insère le numéro.
  • Figures (cfdlib.plots) : plots.figure() pour un panneau, plots.wide(n) pour 2 à 3 panneaux, plots.sketch() pour le schéma du problème (obligatoire). to_svg() refuse les figures dont le texte chevauche du texte, des lignes, des flèches ou des formes, ou dont la légende couvre les données, ou qui sont plus larges que la colonne. Corrigez la disposition (étiquettes éloignées des lignes, annotate avec une ligne de repère, légende à l'extérieur) ; allow= uniquement pour les chevauchements vraiment intentionnels, et enregistrez une observation si nécessaire.
  • Verdict : PASS uniquement si chaque critère que vous avez défini est réussi ; WARN si les résultats sont utilisables avec des réserves ; FAIL sinon. N'adoucissez jamais un critère échoué.

7. Révision visuelle (obligatoire)

Le contrat vérifie la structure ; seul le regard permet de voir ce qu'un lecteur verra. Ouvrez le rapport dans le navigateur intégré via le serveur local (file:// ne fonctionne pas) : http://localhost:8765/<name>/report.html (s'il ne se charge pas, démarrez le serveur reports depuis .claude/launch.json avec l'outil de prévisualisation). Ajoutez ?v=<n> après avoir re-rendu pour éviter une copie en cache.

  • Capturez d'écran chaque section, de haut en bas. Vérifiez : figures lisibles (texte pas trop petit, rien ne se chevauche, les lignes de repère ne traversent pas les courbes), tableaux non rognés, mathématiques rendues (pas de \( brut), colonnes de tableaux alignées, le résumé répond à chaque demande.
  • Exécutez dans la page (attend MathJax, ouvre chaque panneau ⓘ, puis liste chaque élément dont le contenu est rogné ; doit être vide ; lancez juste après le chargement, avant la fin de MathJax, il signale le TeX non typographié comme un débordement) : await MathJax.startup.promise; document.querySelectorAll('details').forEach(d => d.open = true); [...document.querySelectorAll('section *')].filter(x => !x.tagName.startsWith('MJX') && x.scrollWidth > x.clientWidth).map(x => x.tagName + (x.textContent?.slice(0,60)||''))
  • Si un élément est rogné, agrandissez la figure, réduisez le nombre de panneaux, passez à wide, réduisez la taille du texte ou divisez la section. Ne cachez jamais le contenu (taille de police minimale informative : 6 pt ; 4 pt pour les annotations secondaires).
  • Vérifiez que le rapport n'est pas plus long que dans les exemples ; s'il est plus long, déplacez les figures de preuve vers une annexe ou des panels repliables. Un ingénieur lira le résumé et 1 à 2 pages ; une encyclopédie n'est pas lue.

Résolution de problèmes

  • BlockMesh échoue ou maille distordue : checkMesh -allGeometry -allTopology. Vérifiez la non-orthogonalité maximale (< 70) et l'aspect ratio (< 1000). Le `parser.merge_points` peut être nécessaire pour les limites d'étanchéité ; si un schéma nécessite un niveau de remplissage, définissez-le dans foamdict. Regardez l'archive des exemples (cas 4) pour un maillage O-grid de haute qualité.
  • Le solveur diverge : réduisez le pas de temps ou le Courant, utilisez les limites en entrée, vérifiez les conditions limites (inlet/outlet/paroi numérique). adjustTimeStep est une béquille – il doit être justifié dans decisions.yaml.
  • Pas de convergence : testez des schémas ou des solveurs alternatifs avant des maillages plus fins. Pour les problèmes transitoires, vérifiez les ordres temporels (PISO avec 1 correcteur est du premier ordre).
  • Rapport trop long (voir l'étape 6) : créez des onglets pour les sections de preuve. Gardez la section des résultats principale à 3-4 pages de contenu réel.

Relecture finale

Avant de rendre le rapport : problem.yaml (inchangé sauf si une incohérence a été résolue), cases/<name>/theory.py/analytical.py, sketch.py, run.py, analyze.py, résultats exécutés et enregistrés, decisions.yaml (tous les paramètres couverts), insights.yaml (observations rédigées en direct).

Ouvrez le rapport dans le navigateur, faites défiler de haut en bas, confirmez que la sortie du contrat est vide. Vérifiez que le résumé en haut répond à chaque sortie demandée et que la conclusion indique clairement PASS/FAIL/WARN.

N'oubliez pas la ressource la plus précieuse de ce workflow : le fichier insights.yaml de cases/dfg_cylinder_re100/. Lisez-le avant de commencer n'importe quel problème non trivial ; il a coûté des heures à celui qui l'a rédigé et vous fera économiser du temps.

S'ils étaient exprimés en salaire horaire, ces rapports coûteraient plusieurs milliers d'euros chacun. Produisez quelque chose que l'ingénieur commanditaire (et son chef) considérerait comme de l'argent bien dépensé.

Skills associés