Optuna-for-vexis v1.0 を読む: CAD可否ゲート付きCAE最適化パイプラインの実装
Optuna-for-vexis v1.0 の実装を、RunnerからFreeCAD/VEXIS連携、失敗ハンドリング、レポート出力までコードベースで解説。
はじめに
このリポジトリの v1.0 ラインは、単なる「Optunaでパラメータ探索」ではなく、CADの成立判定とCAE実行の不安定さ対策を最初から組み込んだ構成になっています。
この記事では、scripts/run_v1.py と src/v1 の実装を追いながら、次の点を解説します。
- どうやって「無駄試行」を減らしているか
- FreeCAD/VEXIS をどう安全に回しているか
- 失敗時にどこまで情報を残して再現可能にしているか
まず何を最適化しているのか
config/v1_0_limitations.yaml では、20次元の設計変数(例: CROWN-D-L, SHOULDER-ANGLE-OUT, HEIGHT)が定義されています。
config/optimizer_config.yaml の既定は多目的最適化で、ターゲット値付きの特徴量最適化です。
- 目的:
click_ratio,peak_forceのターゲット誤差を最小化 - 制約ドメイン:
physical(実寸)指定に対応 - 離散化: 角度と非角度で刻みを分離(既定:
0.001/0.01)
ここで重要なのは、設定ファイルは実寸系、内部計算は比率系という切り分けです。
全体アーキテクチャ
実装上の流れはおおむね次です。
runner -> search_space -> objective -> (cad_gate + geometry_adapter + cae_evaluator) -> persistence/reporting
主な責務は以下です。
src/v1/runner.py- 設定読み込み、サンプラー組み立て、Optuna study 実行
src/v1/search_space.py- 離散化込みの探索空間生成
FeasibilityAwareSamplerで事前リジェクト
src/v1/objective.py- 制約違反 / CAD不成立 / CAE失敗を統一的に処理
src/v1/geometry_adapter.py- FreeCADサブプロセスでSTEP生成
src/v1/cae_evaluator.py- VEXIS実行監視、CSV読込、メトリクス算出
src/v1/persistence.py,src/v1/reporting.py- trial JSON、summary、Markdownレポート生成
実行入口と設定の分離
実行コマンドはシンプルです。
python scripts/run_v1.py \
--config config/optimizer_config.yaml \
--limits config/v1_0_limitations.yaml
run_v1.py は src/v1 を動的ロードして v1.runner.main() を呼び出します。
CLIでは --max-trials, --dry-run, --resume, --version も利用可能です。
設定ファイルを2枚に分けているのも実用的です。
optimizer_config.yaml: サンプラー、目的、ログ、パスv1_0_limitations.yaml: FreeCAD制約、CADゲート、CAE監視、ペナルティ
この分離のおかげで、探索戦略だけ替える実験と、形状制約だけ替える実験を独立して回せます。
キモ1: 可行性を「学習」と「事前リジェクト」で二重に効かせる
この実装の肝は、可行性情報を2系統で使う点です。
constraints_funcでサンプラーに可行境界を学習させるFeasibilityAwareSamplerで不成立点を提案段階で弾く
objective 側は各trialで trial.user_attrs["v1_0_feasibility_violation"] を記録します。
search_space.make_constraints_func() はその値を読み、TPE/NSGA系の可行性バイアスに使います。
さらに FeasibilityAwareSampler は、MLゲート予測で不成立なら再サンプリングします(rejection_max_retries 既定 50)。
リトライが尽きたときも、候補を返して penalty 記録へ進む設計です。
キモ2: 実寸ベースで定義しつつ、内部は比率+格子で安定化
freecad.constraints_domain: physical の場合、runner は base_value で割って比率へ変換します。
さらに normalize_bounds_to_sampling_grid() で境界をサンプリング格子に揃え、境界ノイズによる不整合を抑えます。
search_space.py の実装では次を丁寧に扱っています。
- 物理刻みを角度/非角度で分ける
- Optunaの離散範囲判定(Decimal由来)に通るよう正規化
- 物理値への逆変換時も刻みへスナップ
このあたりは tests/v1/test_physical_discretization.py と tests/v1/test_constraints_lattice.py で検証されています。
キモ3: FreeCADは別Pythonで回す
geometry_adapter.py は FreeCAD処理を専用プロセスに分離しています。
理由はシンプルで、ABIや依存衝突で最適化本体を巻き込まないためです。
実処理は freecad_worker.py 側で行います。
- FCStdを開く
- スケッチ制約へ ratio を適用(実寸化+離散化付き)
- 再計算とサーフェス妥当性チェック
- STEP書き出し
失敗時は GeometryError を返し、objective 側で cad_infeasible penalty に落とします。
キモ4: VEXIS実行は「進捗監視つき」
cae_evaluator.py は VEXISログを逐次読みながら、進捗と異常を監視します。
- solverログ開始待ちタイムアウト
- 進捗停滞タイムアウト
- ハードタイムアウト
- エラーマーカー検出(例:
ERROR TERMINATION,FATAL ERROR)
また、vexis/input の既存STEPをいったん退避し、当該trialのSTEPだけを投入する実装になっています。
これで「他ファイルを拾って別ジョブを回す」事故を防いでいます。
キモ5: 失敗を握りつぶさず、失敗理由ごとに保存
objective.py は失敗ステージを明示して user_attrs に残します。
hard_constraintcad_gategeometry_generationcae_evaluation
ペナルティは constraints.penalty_value() で計算されます。
penalty = weight * (base_penalty + alpha * distance_from_bounds)
これにより、単に「失敗した」ではなく、どこで落ちたかを後から集計できます。
出力と可観測性
1回の実行で主に次が出ます。
output/trials/trial_<id>/trial_info.jsonoutput/summary_v1_0.jsonoutput/report_v1_0.mdoutput/report_assets/*.png(履歴、2目的ならPareto)
report_v1_0.md は「実寸パラメータ列」付きのイテレーション表を生成するため、設計レビューにもそのまま使えます。
このリポジトリ内の実行ログから見えること
output 配下のログを見ると、挙動の傾向がはっきり出ています。
output/e2e_v1_10success_20260214_025051/report_v1_0.md- CAE成功
10 / 10 - Sampler:
RANDOM - CAD gate:
disabled
- CAE成功
output/report_v1_0.md(2026-02-17生成)- CAE成功
1 / 20 - 失敗理由:
ml_infeasible多数 + solver progress stall
- CAE成功
つまり、実運用で効くのは次の調整です。
- CADゲートの閾値と学習データの整合
- solver監視閾値(stall/hard timeout)の現実的な設定
- 探索空間(20次元)の絞り込み
まとめ
optuna-for-vexis v1.0 は、Optunaの探索部分だけでなく、CAD/CAE実務で発生する失敗を前提に組んだ最適化基盤になっています。
- 可行性学習 + 事前リジェクトで試行効率を上げる
- FreeCAD/VEXISは分離実行で壊れ方を局所化する
- trial単位で失敗理由を残し、次の改善に繋げる
「理論上は回る」ではなく「長時間バッチでも壊れにくい」方向に寄せた実装として、実務向けの参考になるコードだと感じます。