Agentic CFD Workflow
Du verwandelst eine Problemstellung in einen vertrauenswürdigen Simulationsbericht. Der Leser ist ein Ingenieur, der meist die Zusammenfassung liest und in der Lage sein muss, jede Behauptung zu überprüfen. Vier Regeln:
- Code berechnet jede Zahl. Du schreibst den Code; Du gibst niemals manuell ein Ergebnis, einen Referenzwert oder eine Formelausgabe in den Bericht ein. Die Theorie wird symbolisch (SymPy) gelöst und selbstgeprüft.
- Jede Entscheidung wird dokumentiert. Jede OpenFOAM-Einstellung gehört zu einer Entscheidung mit Begründung / Auswirkung / Alternativen. Ein Prüfer weigert sich andernfalls, den Bericht zu rendern.
- Nichts aus dem Gedächtnis. Referenzdaten, Korrelationen und Konstanten stammen aus den
reference/-Dateien des Problems oder einer zitierten Quelle. Wenn Du einen Näherungswert verwenden musst, gib dies im Bericht an. - Live Insights führen, wie ein Laborheft. Ein Teil Deiner Aufgabe ist es, diesen Workflow für die nachfolgenden Agenten zu verbessern. Sobald etwas fehlschlägt, Dich überrascht, einen zweiten Versuch erfordert oder in diesen Richtlinien unklar ist, öffne einen Insight (bevor Du mit der Behebung beginnst):
insights.log(case, stage, problem); fülle die Diagnose, den Fix, die Lektion und die Richtlinie mitinsights.resolve(...), sobald das Problem gelöst ist. Spare sie nicht für das Ende auf: Erinnerungen an eine lange Sitzung gehen verloren und werden verzerrt. Erwarte 5-15 Einträge; ein Durchlauf ohne Einträge wurde nicht genau betrachtet.
Ordnerstruktur
cfdlib/ gemeinsam genutzte Werkzeuge (NICHT ändern; notwendige Änderungen als Insights mit stage "tooling" protokollieren)
foam.py render_case, run (Timeout, PID-Datei), stop, running; Felder und postProcessing .dat-Dateien lesen
monitor.py parse_log, assess (Gesundheitschecks), preflight (kurzer Testlauf + Laufzeitprojektion)
verification.py Fehlernormen, beobachtete Ordnung, Richardson-Extrapolation, GCI, level_sequence
(Konvergenz jeder Sequenz von Niveaus, einschließlich der eigenen eines Referenzfalls)
signals.py periodische Signale: Zyklen, verfeinerte Peaks, Periodizitätsprüfung, FFT-Frequenz
plots.py Figurenstil und -größen (figure, wide, sketch); to_svg prüft auf überlappenden Text
report.py m(), tex_num(), tex_fix() (feste Dezimalstellen für Tabellenspalten), tex_pct(), LETTERS, load_yaml,
environment_info(), render()
perf.py Leistungszahlen pro Lauf (summarize, machine) für den Leistungsanhang
contract.py der Berichtsvertragsprüfer (auch im --decisions-Modus)
insights.py das Live-Insight-Logbuch (prüft die Formatierung beim Loggen; weigert sich, einen ausgefüllten Eintrag zu überschreiben)
foamdict.py OpenFOAM-Wörterbuchparser (wird vom Prüfer verwendet)
cases/<name>/ ein Ordner pro Problem
problem.yaml VOM BENUTZER GEGEBEN: description (kurzer Überblick), setup (Liste der Fakten: Geometrie,
Randbedingungen, Definitionen), gegebene Daten, angeforderte Ausgaben [+ reference/].
Ändere die Physik nicht. Übergebe description und setup unverändert an den Bericht.
reference/ vom Benutzer bereitgestellte Referenzdaten mit Zitaten
numerics.yaml Dein numerischer Plan (Gitter, Lauflängen, Toleranzen)
decisions.yaml jede Entscheidung, geschrieben WÄHREND Du den Fall einrichtest
insights.yaml das Live-Logbuch (cfdlib.insights)
template/ der OpenFOAM-Fall (0/, constant/, system/) mit {{ Platzhaltern }}
theory.py / analytical.py, sketch.py, run.py, analyze.py
results/<name>/ Rohdaten, results.json, report.html
Ausgearbeitete Beispiele (lies alle ihre Dateien, bevor Du beginnst; kopiere die Struktur, nicht die numerischen Einstellungen):
cases/plane_poiseuille/: linear, stationär, exakte Lösung (Verifizierung). Bericht:results/plane_poiseuille/report.html.cases/lid_driven_cavity_re100/: nichtlinear, stationär, keine exakte Lösung, veröffentlichter Benchmark (Benchmark-Vergleich, Richardson-Extrapolation, residual-gestopptes SIMPLEC, separater Vorab- und Produktionsbefehl). Bericht:results/lid_driven_cavity_re100/report.html. Seine insights.yaml enthält 15 Lektionen von dem Agenten, der es erstellt hat.cases/stokes_first_problem/: linear, INSTATIONÄR, exakte Lösung (kombinierte und separate Gitter- und Zeitschrittstudien, Impulsbilanz pro Schritt, gleichzeitige Läufe). Bericht:results/stokes_first_problem/report.html.cases/dfg_cylinder_re100/: nichtlinear, INSTATIONÄR und periodisch (Wirbelablösung), Benchmark mit mehreren Referenzniveaus, O-Gitter für gekrümmte Körper in Python generiert, MPI-Läufe und ein Leistungsanhang. Bericht:results/dfg_cylinder_re100/report.html. Seine insights.yaml enthält 16 Lektionen (die wichtigste: PISO war erster Ordnung in der Zeit).
Stufen
0. Aufnahme
- Lies
problem.yamlund allereference/-Dateien. Liste die angeforderten Ausgaben auf (sie erhalten Buchstaben a, b, c...). - Berechne die maßgeblichen dimensionslosen Kennzahlen (Re, Ma, ...) im Code. Entscheide, welche Physik zutrifft.
- Wenn Daten fehlen oder widersprüchlich sind, wähle eine dokumentierte Annahme und markiere sie in den Einschränkungen des Berichts. Erfinde niemals stillschweigend Eingaben.
- Wenn eine Referenz keine Unsicherheit angibt, schätze eine (ihr Gitter, ihre gedruckten Ziffern, eine zweite Quelle) und zeichne auf, wie; Du wirst sie in Stufe 5 benötigen.
1. Theorie: Was ohne die Simulation bekannt sein kann
- Exakte Lösung verfügbar: leite sie mit SymPy her, überprüfe sie selbst (erfüllt Gleichungen und Randbedingungen),
und werte die angeforderten Größen aus. Siehe
cases/plane_poiseuille/analytical.py. - Keine exakte Lösung (üblicher Fall): sage dies in
theory.intround gib an, was die Theorie SAGEN kann: Grenzfälle, Skalierung, Erhaltungssätze, Symmetrie, Vorzeichen von Größen, erwartete Strömungsstruktur. Siehecases/lid_driven_cavity_re100/theory.py. - Jede abgeleitete Größe erhält: warum sie wichtig ist, die Bedeutung und den Wert jedes Symbols in diesem Problem, und die Formel mit den eingesetzten Zahlen (zur Überprüfung per Hand).
2. Einrichtung und Entscheidungen
- Finde das nächstgelegene OpenFOAM-Tutorial (
$FOAM_TUTORIALS) und beginne damit; nimm minimale Änderungen vor. - Lies
references/openfoam-primer.md, um zu erfahren, was jede Datei steuert und welche bekannten Fallstricke es gibt. - Vorlagen können Jinja-Schleifen enthalten: das Generieren eines gesamten
blockMeshDictaus Python ist in Ordnung und ist der Weg, um gekrümmte Körper zu vernetzen (Beispiel:mesh_spec()incases/dfg_cylinder_re100/run.py). - Bearbeite Dateien, die LaTeX enthalten, nur mit den Bearbeitungs-/Schreibwerkzeugen, niemals über Bash-Heredocs oder
Inline-Python: Das Bash-Werkzeug frisst Backslashes (
\timeswird zu einem TAB). Die Fälle 3 und 4 haben beide Zeit dadurch verloren. - Schreibe
decisions.yamlwährend der Arbeit: ein Eintrag pro Wahl mitkind(physics / accuracy / solver / output) undsettings-Mustern, die jede Einstellung in den Falldateien abdecken. Überprüfe die Abdeckung, sobald ein Fall gerendert ist:python -m cfdlib.contract --decisions cases/<name>/decisions.yaml <case dir>. - Plane eine Gitterstudie: mindestens 3 Gitter, Verfeinerungsverhältnis 2 (erforderlich für beobachtete Ordnung und GCI). Instationäre Probleme benötigen auch eine Zeitschrittstudie (oder ein Argument mit Zahlen, dass der Zeitfehler vernachlässigbar ist).
- Definiere einen physikalischen Konvergenz-/Stationaritätscheck für Dein Problem (eine Bilanz, eine Erhaltungsgröße, ein überwachter Wert, der sich nicht mehr ändern darf) und seine Toleranz, BEVOR Du Ergebnisse siehst. Normalisierte Residuen allein sind nicht ausreichend. Instationäre Probleme: eine Bilanz pro Schritt (Änderung einer Erhaltungsgröße = Randflüsse) ist ein starker Check; er muss das eigene Zeitschema des Lösers verwenden.
- Bestanden/Nicht bestanden-Toleranzen müssen unterscheiden können: dimensioniere sie aus dem groben Vorabfehler und der erwarteten Ordnung (was das feinste Gitter erreichen sollte), nicht aus runden Zahlen. Eine Toleranz, die 100x lockerer ist als das Erreichte, beweist nichts (Fall 3). Mache die beobachtete Ordnung der Genauigkeit zu einer eigenen Regel.
3. Vorabtest (obligatorisch, vor jedem langen Lauf)
cfdlib.monitor.preflight()auf dem gröbsten UND feinsten Gitter mit einem kurzen Lauf (einige % der vollen Länge). Es arbeitet in einer separaten<case>_preflight-Kopie; rendere Vorab- und Produktionsfälle ebenfalls in separate Verzeichnisse, sodass ein erneuter Lauf niemals den anderen löscht.- Es berichtet über Abstürze, NaN, Courant-Zahl, Residuen-/Courant-/Kontinuitätstrends und projiziert die Laufzeit.
Instationäre Läufe: die Projektion ist zuverlässig. Stationäre Läufe: übergib
residual_targets; das Ergebnis ist eine untere Schranke (SIMPLE verlangsamt sich später und auf feineren Gittern). - Drucke die Kopfzeile und die ersten Zeilen jeder
postProcessing-Datei, die der Vorabtest geschrieben hat, und überprüfe jede auf Nicht-Trivialität (Fall 4: ein Druckintegral schrieb bei jedem Schritt 0, stillschweigend). - Instationäre nichtlineare Probleme: eine kurze Zeitschrittstudie (3 Zeitschritte, Verhältnis 2, auf einem groben Gitter) gehört in den Vorabtest, vor der Gitterstudie. Sie zeigt die beobachtete Ordnung in der Zeit: PISO (1 äußerer Korrektor) erwies sich in Fall 4 als erster Ordnung und kostete eine vollständige Wiederholung.
- Ein Vorabtest, der aus der Ruhe startet, unterschätzt die Kosten einer entwickelten instationären Strömung (Fall 4:
doppelt). Starte ihn von einem entwickelten Feld (
mapFieldsvon einem groben Lauf) oder budgetiere das Doppelte der Projektion. - Wenn er fehlschlägt oder warnt, korrigiere die Einrichtung und protokolliere einen Insight. Keine Produktionsläufe bei fehlgeschlagenem Vorabtest. Wenn die projizierte Zeit übermäßig ist (> ~15 min pro Lauf hier), vergröbere den Plan und sage dies.
4. Produktionsläufe
- Führe die Gitter vom groben zum feinen aus. Für stationäre Läufe extrapoliere die Iterationsanzahl des feinsten Gitters aus den gröberen (sie wächst oft um das 3-4-fache pro Verfeinerung), bevor Du es startest.
- Das Stoppen muss physikalisch sein: mache den physikalischen Check aus Stufe 2 zur Stoppregel (z. B. ein
runTimeControl-Funktionsobjekt auf abgetasteten Werten, siehe das Primer), oder überprüfe pro Gitter, dass die Residuenziele ihn tatsächlich erfüllen, zuerst auf den groben Gittern. - Unabhängige Läufe (verschiedene Gitter) können gleichzeitig ausgeführt werden, bis zu 6 auf einmal (8 Kerne). Sage dies in den Reproduzierbarkeitshinweisen, da es die Wandzeiten pro Lauf erhöht.
- Lange Läufe laufen im Hintergrund;
python -m cfdlib.foam pslistet Löserprozesse auf, undpython -m cfdlib.foam stop <case dir>tötet einen (das Stoppen des Python-Wrappers allein hinterlässt einen verwaisten Löser). Zeichne pro Lauf auf: Gesundheit (monitor.assess), den physikalischen Check, die Wandzeit. - Führe eine Sequenz langer Läufe als EINEN Hintergrundskript aus, der einen Abschlussmarker und einen Datensatz pro Lauf schreibt. Lies nach jeder Unterbrechung (auch einer API-Pause) diese Dateien, bevor Du etwas erneut ausführst.
- Während alle Hardware-Threads einen Löser ausführen, verlangsamt dies Deine eigene Arbeit: lasse 1-2 Threads frei oder budgetiere das 1,5-fache der Projektion.
5. Analyse
- Alle Vergleiche im Code. Mit einer exakten Lösung: Fehler und beobachtete Ordnung pro Gitter.
Ohne: beobachtete Ordnung und Richardson-Extrapolation aus 3 Gittern, GCI auf dem feinsten Gitter
(
verification.py), dann vergleiche den extrapolierten Wert mit der Referenz. - Ein Benchmark ist auch eine Simulation: beurteile die Übereinstimmung gegen (Deine GCI-Band + die eigene Unsicherheit der Referenz), nicht gegen Null. Wenn die Differenz mit der Verfeinerung nicht schrumpft, ist es nicht Dein Diskretisierungsfehler; teste die Robustheit (ein anderes Schema, engere Iteration), bevor Du eine Seite beschuldigst, und empfehle eine zweite unabhängige Referenz.
- Wenn die Referenz mehrere Gitterebenen angibt, konvergiert sie immer noch: berechne IHRE beobachtete Ordnung
und den Richardson-Wert (
verification.level_sequence) und vergleiche Dein Ergebnis sowohl mit ihrer feinsten Ebene als auch mit ihrem extrapolierten Wert. Lege die Bestehensregel fest, bevor Du hinsiehst (Fall 4 behielt die Regel für die feinste Ebene bei und berichtete die Extrapolation als Kontext). - Periodische Ausgaben (Ablösung, Schwingungen): verwende
cfdlib.signals(Zyklen zwischen Aufwärtskreuzungen, Parabel-verfeinerte Peaks, Periodizitätsprüfung, FFT-Gegenprüfung) anstatt Deine eigenen zu schreiben. - Eine Übereinstimmung auf grobem Gitter kann Glück sein (Fehleraufhebung). Berichte Abweichungen ehrlich, wo und warum.
- Eine Erklärung ist eine Behauptung: überprüfe sie an den Daten (Vorzeichen, Größe, Skalierung), bevor Du sie schreibst. In Fall 3 hatte die erste Erklärung eines Fehlers das falsche Vorzeichen und ein Drittel der Größe.
6. Bericht
- Erstelle
results.jsoninanalyze.pyund rufereport.render(results, svgs, out, case_dir=<feinster Fall>)auf. Der Vertrag ist inreferences/report-contract.md;renderweigert sich, einen Bericht zu schreiben, der ihn bricht. - Mathematik: jede Formel, jedes Symbol und jede wissenschaftliche Zahl im Text als LaTeX in
\( \)(m(),tex_num()): schreibe\(0.009\,U\), niemals0.009 U,6.3e-06oderu*H/nu. Der Prüfer lehnt e-Notation und ASCII-Mathematik ab und warnt bei Zahl-mal-Symbol im Klartext. - Bildunterschriften und Ergebnisse sind ebenfalls Ergebnisse: generiere sie aus den Daten (eine Bildunterschrift, die
sagt "schrumpft", muss berechnet werden). Verweise auf Abbildungen als
{fig:<key>}; render() fügt die Nummer ein. - Abbildungen (
cfdlib.plots):plots.figure()für ein Panel,plots.wide(n)für 2-3 Panels,plots.sketch()für die Problemskizze (erforderlich).to_svg()weigert sich bei Abbildungen, deren Text sich mit Text, Linien, Pfeilen oder Formen überschneidet, oder deren Legende Daten verdeckt, oder die breiter als die Spalte sind. Korrigiere das Layout (Beschriftungen weg von Linien,annotatemit einer Führungslinie, Legende außerhalb);allow=nur für Überschneidungen, die wirklich beabsichtigt sind, und protokolliere einen Insight, falls Du es benötigst. - Befund: BESTANDEN nur, wenn jeder von Dir definierte Check bestanden ist; WARNUNG, wenn die Ergebnisse mit Einschränkungen verwendbar sind; ANDERNFALLS NICHT BESTANDEN. Mache niemals einen fehlgeschlagenen Check weicher.
7. Visuelle Überprüfung (obligatorisch)
Der Vertrag prüft die Struktur; nur das Hinsehen erfasst, was ein Leser sehen wird. Öffne den Bericht im
integrierten Browser über den lokalen Server (file:// funktioniert nicht):
http://localhost:8765/<name>/report.html (wenn er nicht lädt, starte den reports-Server aus
.claude/launch.json mit dem Vorschauwerkzeug). Füge ?v=<n> nach einem erneuten Rendern hinzu, um eine zwischengespeicherte Kopie zu vermeiden.
- Mache einen Screenshot von jedem Abschnitt, von oben nach unten. Überprüfe: Abbildungen lesbar (Text nicht winzig, nichts
überlappend, Führungslinien kreuzen keine Kurven), Tabellen nicht abgeschnitten, Mathematik gerendert (kein rohes
\(), Tabellenspalten ausgerichtet, die Zusammenfassung beantwortet jede Anfrage. - Führe auf der Seite aus (wartet auf MathJax, öffnet jedes ⓘ-Panel, listet dann jedes Element auf, dessen Inhalt
abgeschnitten ist; sollte leer sein; wenn direkt nach dem Laden ausgeführt, bevor MathJax fertig ist, wird ungesetzter
TeX als Überlauf gemeldet):
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(e => e.tagName + (e.id ? '#'+e.id : e.textContent?.slice(0,60).trim()))(wenn nicht leer, korrigiere das Layout durch Ändern der Python-Vorlage oder des Plots, nicht durch Inline-CSS). - Überprüfe, dass kein Inhalt überläuft. Wenn doch, korrigiere das Layout.
Richtlinien vs. Cursor-Regeln (wiederholte Fehler, die Tage gekostet haben)
- Anweisungen mit zwei Zeilen oder am Ende eines Dokuments – Der Agent befolgt die erste Zeile, vergisst den Rest, dann kostet das nächste Manöver Zeit. Wiederhole wichtige Anweisungen dort, wo sie gebraucht werden, nicht nur in einer Master-Regel.
- Checkliste vs. Vorschrift – „Denke daran, eine Entscheidung zu protokollieren, wenn Du die Einstellung
änderst“ ist eine Checkliste, nicht durchgesetzt. Wenn eine Regel durchgesetzt werden soll, mache sie
zu einem Code-Test oder einem Template-Text.
contract.pyist gut. Ein Pfeffern von.claude/rules.mdmit guten Absichten reicht nicht. - „Mache es wie Fall N“ – Der Agent öffnet die Dateien eines ausgearbeiteten Beispiels, liest den Quelltext, kopiert dann die Denkweise (nicht den Text). Das ist der beste Weg, alle Einheiten zusammenzuführen. Durchsuche die ausgearbeiteten Beispiele nach jeder relevanten Technik (Gitter für gekrümmte Körper, Signaldetektion, Zeitschrittstudie).
Betriebshinweise
Schreibwerkzeuge
Die Edit- und Write-Werkzeuge, die vom Terminal getrennt sind, sind die EINZIGE Möglichkeit, LaTeX-haltige
Dateien zu schreiben. Ein Bash-cat > file <<'EOF' beschädigt sie leise. cases/dfg_cylinder_re100 enthält
für beide Fälle auf diese Weise verlorene Zeiteinträge.
Diagrammerstellung
- Eine asymmetrische Linie (z. B. wo eine gestrichelte Linie auf eine durchgezogene Linie trifft) mit
annotatewird oft falsch dargestellt: die Führungslinie und die Markierung an der Nahtstelle haben unterschiedliche Farben. Zeichne sie als zwei völlig getrennte annotate-Aufrufe mit derselben Farbe. to_svg()übersetzt nicht – wenn ein Text auf Deutsch geschrieben ist, bekommst du eine Fehlermeldung. Schreibekind="ends"- oderkind="start"-Texte auf Englisch.- Der
arrowprops-WertshrinkBist ein Teil des Pfeils, der auf den Text zeigt; er muss möglicherweise auf -3 gesetzt werden, um den Abstand zu vergrößern.
Fehlerbehebungen
- rm: Argumentliste zu lang – Nach vielen Zwischenfällen kann
results/zu viele Dateien enthalten. Lösche sie mitfind results/ -delete. - Pandas 2.2 deprecated
DataFrame.append– es heißt jetztpd.concat. - Vorlage-Erweiterungen – Jinja
.j2muss mitrender_caseverwendet werden; diese Dateien haben keine LaTeX-Probleme, aber sie benötigentemplate/mit{{ placeholders }}fürrender_case. stopschreiben – Es gibt einen fallbezogenen Timer. Innerhalb eines Hintergrundskripts: Wenn der Überverseen-Agent unterbrochen wird (API-Pause), können lange Läufe weiterlaufen. Ein neuer Agent wird sie nicht kennen.stopalle Läufe zu Beginn jedes neuen Agenten-Aufrufs; dann ist die Zeitverschwendung auf 1-2x die maximale Laufzeit begrenzt, anstatt dass ein ganzer Batch an einem Tag neu gestartet wird. Wann immer ein neuer Aufruf beginnt,tools.bash('python -m cfdlib.foam stop results/case_dir'); sorge dafür, dass der Fall geladen ist, bevor Du die PID-Datei löschst.
Skript-Ausführung
Die Ausgabe von Python kann auf 4096 Zeichen begrenzt sein. Wenn die Ausgabe abgeschnitten ist, verwende
python -c "print(open('...').read())", um die vollständige Datei zu lesen. Insbesondere
results/<name>/results.json muss vollständig gelesen werden.
Zeilenumbrüche in Python-help()
help(cfdlib.foam) wird zu einem langen String. Die native Python-Anzeige enthält \n; sie
wird mit neuen Zeilen gerendert, wenn man sie druckt: python -c "import cfdlib.foam; help(cfdlib.foam)" >
eine Datei und lese die Datei.
SSH und Git
- SSH-Schlüssel: Der Agent kann nicht nach einem Passwort fragen. Clone über HTTPS, wenn Du keinen SSH-Agenten im Container hast.
- Push: Der SSH-Schlüssel des Systembenutzers ist möglicherweise nicht hinterlegt. Erstelle einen persönlichen Zugriffstoken (Repository-Zugriff) und verwende ihn als Passwort.