固定版の OpenFOAM ケースを AWS Batch で実行する:ハッシュ照合、2 つのメッシュ、ローカルとの抗力係数比較

OpenFOAM v2512 の各ケースを Batch ジョブで実行し、ハッシュと版を照合後に 4 チェック。候補 Cd はローカル 0.22945、Batch 0.22926。

フロー図:タイトルの下に 1 から 4 の番号付きの塗りつぶした円が左から右へ矢印でつながり、その下の文字は順に、ホストがケースを書き出し case.tar として S3 へ;ケースごとに Batch ジョブ 1 つ、16 vCPU / 32 GiB、再試行なし;result.json と 3 ファイルのハッシュを照合、v2512 を要求;level 3 と level 4 の 2 メッシュで 4 つのチェック。下端に出典の注記があります。
Ahmed 型車体の 1 ケースが、ホストでの梱包、AWS Batch での求解、返送ファイルの照合を経て 4 つのチェックに至る手順。pai-design-workbench の commit f225835 の docs/AERO.md、native/cfd_review.py、native/cfd_job.py に基づいて作図。失敗または期限超過のジョブは報告のみで再投入されないことは図に含めていません。

pai-design-workbench の「车身气动」(車体空力)レーンでは、車体設計の良し悪しをモデルに判断させません。判断は OpenFOAM の RANS 解析に任せます。同じ Ahmed 型車体を 2 つのメッシュでそれぞれ解き、4 つのチェックで結論を出します。AWS Batch のジョブ定義が設定されている場合、各ケースは独立した Batch ジョブとして実行され、結果がホストに戻ると、チェックの前にまずハッシュとソルバーのバージョンが照合されます。本記事では commit f225835 に固定された docs/AERO.md、native/cfd_review.py、native/cfd_job.py と、保存されたレビュー記録 docs/evidence/aero-batch.json をもとに、各ステップが何を渡し何を照合するのか、そして記録の中でローカルと Batch の抗力係数が実際にどれだけ違うのかを説明します。

本文の数値はすべてプロジェクト自身のドキュメント、コード、記録によるものです([O]、プロジェクトの自己報告)。本サイトはこれらのケースを実行しておらず、Batch でのレビューも再現していません。数値はそれぞれのメッシュ、反復回数、乱流モデルの条件のもとでのみ成り立ちます。これは講座 フィジカルAIラボ 0.4:仮想の世界で一万回練習――シミュレーションの 3 つの数字に実験条件を戻す で扱ったのと同じ原則で、ここではファイルを 1 つずつ確かめられる空力ケースに当てはめます。

このレーンは何を計算し、どう判定するか

AERO.md によると、形状は CadQuery 2.8 / OCCT 7.9 で生成するパラメトリックな Ahmed 型車体です。Ahmed 車体は自動車の鈍頭物体空力の標準ベンチマークで(Ahmed、Ramm、Faltin、1984 年)、寸法は 1.044 × 0.389 × 0.288 m、ノーズ半径 0.1 m、後部スラント長 0.222 m、地上高 50 mm です。すべてのレビューで 25° の車体を基準にします。

ソルバーは OpenFOAM v2512 で、公式 OpenCFD イメージ opencfd/openfoam-default:2512 を使い、環境変数 PAI_OPENFOAM_IMAGE でダイジェストを固定します。メッシュは blockMesh、surfaceFeatureExtract、snappyHexMesh の順に作成し、流れは simpleFoam、k-ω SST、壁関数、流速 40 m/s、移動地面、対称面を使ったハーフモデルで解きます。4 つのチェック(pai-cfd-checks-1)は次のとおりです。

チェック定義既定のしきい値
drag-coefficient細かいメッシュ(level 4)の Cd。1200 回の SIMPLE 反復のうち最後の 400 回の平均Cd ≤ 0.24
grid-convergence|Cd(level 4) − Cd(level 3)| / Cd(level 4)。ドキュメントは、これが 2 水準のメッシュ依存性であり GCI ではないと明記しています≤ 12 %
iterative-convergence平均 Cd の定常性:|最後の 400 回の平均 − 最後の 200 回の平均| / 平均。2 つのメッシュのうち悪い方を採用。生の振動幅は cfd.json に別途保存≤ 1 %
mesh-quality両方のメッシュで checkMesh が “Mesh OK” を報告両方とも合格

反復収束のチェックは、ある不合格をきっかけに書き直されました。ドキュメントの記録では、level 4 で 12.5° 車体の定常 RANS が準周期的に振動し、Cd は 0.2255–0.2313、周期は約 300 反復、100 反復での幅は 2.6 % でした。最初のエンドツーエンド実行はこの幅をチェックに使い、車体を不合格にしました。現在は 400 反復で平均を取り、その平均が定常かどうかを判定します。結果は EvalArc の JUnit 形式で、ほかのレーンと同じフィードバック、再チェック、リリースの流れに入ります(EvalArc の紹介は EvalArc:スコアが上がったとき、エージェントはどこで退行したのか)。

1 つのケースが Batch へ行き、ホストへ戻るまで

cfd_review.py には 2 種類のランナーがあります。ローカルの docker(--network none、ホストユーザーで実行、ケースディレクトリだけをマウント)と batch です。Batch の経路は次の順に進みます。

  1. ホストが cfd_case.py でケース(辞書ファイルと body.stl)を書き出し、case.tar にまとめて S3 の jobs/<run>/<name>-l<level>/ プレフィックスへアップロードします。アーカイブから run.sh は除外します。
  2. Batch ジョブを 1 つ投入します。Dockerfile.cfd は同じダイジェスト固定の OpenCFD イメージをベースに、Python と boto3 だけを追加したものです。各ジョブは 16 vCPU、32 GiB で、再試行はしません。
  3. ジョブ側の cfd_job.py は、プレフィックスが jobs/ で始まり .. を含まないことを要求します。展開前には通常ファイルとディレクトリだけを受け付け、絶対パスと .. を含むパスを拒否します。リンクは通常ファイルではないため拒否されます。コードのコメントにはメンバーが case/ 以下にあるべきだと書かれていますが、コードはこれを検査していません。展開には filter="data" を使い、展開後に case/system/controlDict の存在を求めます。実行する run.sh は入力からではなく、イメージ自身のものを使います。
  4. ジョブは coefficient.dat、run.json、log.checkMesh と result.json(スキーマ pai-cfd-job-1。各ファイルの SHA-256、OpenFOAM のバージョン、イメージタグ、所要時間、CPU 数を含む)を書き出し、ChecksumSHA256 を付けてアップロードします。
  5. ホストは 10 秒ごとにジョブの状態を問い合わせ、既定の期限は 7200 秒です。失敗または期限超過のジョブは報告するだけで、再投入しません。成功時は status が measured、openfoam が v2512 であることを確認し、3 つのファイルを 1 つずつダウンロードして result.json のダイジェストと照合します。一致しなければエラーです。
  6. Cd はホストが coefficient.dat から計算し、4 つのチェックもホストで判定します。その後ケースディレクトリは削除され、要約した証拠だけが残り、約 100 MB の流れ場は残しません。

ハッシュ照合の範囲には注意が必要です。ホストは 3 つのファイルと同じ S3 プレフィックスから result.json をダウンロードするので、一致によって分かるのは、ダウンロードしたファイルがダウンロードした result.json のダイジェストと合っていることだけです。ファイルの破損や、result.json を更新せずにファイルが変更された場合は検出できますが、そのプレフィックスに書き込める人が両方を差し替えた場合は検出できません。ジョブのアップロード時に付ける ChecksumSHA256 は、そのアップロード中の完全性を守るものです。また、照合は解析そのものが正しいことを独立に証明するものでもありません。解析を信頼できるかどうかは、メッシュと反復の 2 つのチェック、そして後述の制約に左右されます。

保存されたレビュー記録:2 つの車体、2 つのメッシュ、4 つのジョブ

aero-batch.json には、プロジェクトのホスト型サイトで完了した 12.5° 車体のレビューが記録されています(checkedAt 2026-10-03T21:50:00Z、判定 accepted-aero-body、ブロッキング項目 0、所要 9 分)。基準は 25° 車体、候補は 12.5° 車体で、車体ごとに 2 つのメッシュ、合計 4 つの Batch ジョブです。各ジョブは CPU 数 16 を報告しています。

車体メッシュセル数Cdホスト計測(秒)ジョブの自己報告(秒)
基準 25°level 361,3100.25751141.961
基準 25°level 4219,6220.23406352.5269.1
候補 12.5°level 361,5730.2536615261.6
候補 12.5°level 4221,3540.22926262.1169.2

4 つのチェックは両方の車体で合格と記録されています。基準はメッシュ変化 0.1002、反復ドリフト 0、候補はメッシュ変化 0.1064、ドリフト 0.00012 で、どちらも mesh-quality は 2 メッシュとも合格です。記録にはこのレビューで使ったしきい値が書かれていません。ドキュメントの既定値で見ると、10.0 % と 10.6 % のメッシュ変化は上限 12 % に近い値です。ホスト計測は投入前から結果の解析後までなので、ジョブの自己報告を上回る分にはアップロード、キュー待ち、ポーリング、ダウンロードが含まれます。記録はその内訳を示していません。

ローカルと Batch:抗力係数と所要時間の読み方

記録の localComparison では、基準車体の Cd はローカルでも Batch でも 0.23406 です。候補車体はローカル 0.22945、Batch 0.22926 です。AERO.md は両者を「等しい」としていますが、候補の値は実際には 0.00019、ローカル値の約 0.08 % 異なります。記録はこの差の原因を説明しておらず、本記事も推測はしません。

所要時間は、記録ではローカル 8 コアで 94 分、Batch で 9 分です。これは保存された 1 回の開発時の実行です。ローカルは 8 コア、Batch は 16 vCPU のジョブ 4 つで、ハードウェア、コア数、スケジューリングがすべて異なり、費用も記録されていません。一般に Batch が 10 倍速いことを示すものではなく、コスト比較でもありません。

ローカルで再現する

AERO.md に記載されたローカル実行のコマンドは次のとおりです。commit f225835 のリポジトリのルートで実行します。

docker pull opencfd/openfoam-default:2512
echo "PAI_OPENFOAM_IMAGE=$(docker image inspect opencfd/openfoam-default:2512 --format '{{index .RepoDigests 0}}')" >> .state/demo.env
npm run test:aero   # 35° body vs the Cd target → feedback → 12.5° body re-solved on two meshes → feedback closed

コメントはこのテストの流れを示しています。35° 車体を Cd の目標と照らし合わせてフィードバックを出し、12.5° 車体を 2 つのメッシュで解き直してフィードバックを閉じます。2 行目は自分のマシンが取得したイメージのダイジェストを環境ファイルに書き込むので、固定されるのは自分が取得したダイジェストであり、記録されたレビューで使われたものと同じとは限りません(記録では sha256:33fb575a… と省略表示)。提供されたドキュメントには、リポジトリのクローンや npm 依存関係、CadQuery 環境のインストール手順がありません。コミットをチェックアウトしても固定されるのはリポジトリ内のファイルだけで、これらの依存関係のバージョンは固定されません。

API から実行する場合は POST /api/projects/:id/aero で、リクエスト本文に requestId、projectRevision、parameters(slantAngleDeg、noseRadius、length、height)を含め、requirements は任意です。AI ツール aero-body は型付きのパラメーターを提案するだけで、人による確認が必要です。Batch を使うのは PAI_SOLVER_CFD_JOB_DEFINITION が設定されている場合だけです。提供された資料にはジョブキュー、ロール、ネットワークの定義が含まれないため、本記事ではデプロイ手順を示しません。自前で構築する場合は、ジョブロールに使用するバケットの jobs/ プレフィックスの読み書きだけを許可し、AccessDenied が出たら権限を広げるのではなくプレフィックスを確認してください。

制約と注意点

  • メッシュ依存性はまだ大きく、level 3 から level 4 で Cd は 9–10 % 変わります。ドキュメント自身も、絶対値を求めるにはさらに細かいメッシュが必要だとしています。現在はプリズム層なしで実行しており、ドキュメントは層の追加と細かいレベルを AWS Batch での次のステップとしています。
  • 抗力は過小評価されます。ローカルの level 4 で 25° 車体は Cd 0.234、実験値は 0.285、level 3 で 30° 車体は 0.263、実験値は 0.378 です。ドキュメントはこれを壁関数を使った定常 RANS の既知の弱点とし、30° での高抗力状態は非定常な C ピラー渦とスラント上の部分的な剥離に依存すると説明しています。
  • 順位づけが成り立つのは低抗力側だけです。12.5° 車体は実験でも計算でも最も抗力が小さく、level 4 の 0.230 は実験値 0.230 と一致します。しかし同じ表で、level 4 の計算は 35° 車体(0.249)を 25° 車体(0.234)より高くしており、実験では 25°(0.285)が 35°(0.260)より高く、順序が逆です。ほかの形状範囲の比較に使う前には、別途検証が必要です。
  • すべての記録に physicalValidation: false が付いています。これは設計比較のためのツールであり、風洞、走行、その他の物理的な安全性の検証ではありません。
  • 保存された Batch 記録が示すのは、ホスト型サイトでの 1 回のレビューが完了したことです。独立した再現ではなく、本サイトも再実行していません。
  • cfd_review.py 冒頭のモジュール説明は、いまも「最後の 100 反復の平均」と (max − min) / mean の幅を記述しており、同じファイルのコードや AERO.md の 400/200 の定常性チェックと食い違っています。本記事はコードと AERO.md に従って説明しています。

リンク