FreeCADのトポロジー最適化をGeminiが10倍−15倍高速化した話 その１

# 結論 ＆ コード

__高速化に成功した！！　人間はコーディングせず、しかも10倍以上の高速化。__

Gemini2.5Proに高速化の戦略を相談し、実際にコードを修正してもらった。

|  | 修正前 | 改善後 |
|:-:|:-:|:-:|
| 形状1  | 73分  | __6分__  | 
| 形状2  | 28分  | __1.5分__  | 

ちなみにハードウェアはMacbookAir13インチM3 2024 16GBです。

形状１、２はそれぞれ以下のような形です。

|| __【最適化前】__ | __【最適化後】__ |
|:-|:-:|:-:|
|形状1|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121950/af1d6f16-86dc-4e90-987f-92dd4fe4ec12.png)| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121950/e95bcbe8-c8ce-4f05-ba43-3b74366c7dab.png)|
|形状2|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121950/95caecbf-6f71-4726-8ae1-c2ba10efde3e.png)|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121950/b15b07f2-9de0-4d2c-9b09-ecb6e3e45835.png)|


試したい人は以下からどうぞ。ただ、無保証です。
（修正前のコードと比較したりする場合は、キャッシュが残ってうまく動作しないので、Pythonのキャッシュを削除したり、FreeCAD再起動したり しましょう）

https://github.com/84monta/beso

# 背景
LLMの情報を追っかける傍ら、興味があるトポロジー最適化もチラチラ見ている。
トポロジー最適化の難点は時間がかかることだ。

なんとかならんかなぁと様々なLLMを使ってチャレンジしていたが、毎回失敗していた。
が、別件で色々と使っているうちにGemini2.5Proが一つ抜け出て賢いことが分かったので再チャレンジした。

# 戦略
前回は適当に高速化をお願いして失敗したため、今回はまず全体を見て作戦を立てようと思った。
ようは人間が考える順序を整理してお願いする。
人相手に説明するとめんどくさいけどAIであれば無駄なくストレートに依頼できるので超絶楽チン。

- 全体フロー理解
- 費用対効果が大きそうな対策の優先度付け（効果が大きく、修正が少ない）
- 修正の詳細を検討
- 修正実施

## 全体把握

その為にも、まずはプラグインの全体把握が必要なので、以下の依頼をした

```text:初期プロンプト
このフォルダのプログラムを全て読んで、処理の流れやフィアルと関数の依存関係を調べてREADME_FLOW.mdとしてまとめてください。
```
ファイルをガンガン読んで理解していってくれます。
本当に頼もしい。数分後

```
これまでの分析結果を基に、プログラム全体の処理フローとファイル間の依存関係をまとめた README_FLOW.md を作成する準備ができました。

提案する README_FLOW.md の構成:

概要:

CalculiX をソルバーとして使用する BESO (Bi-directional Evolutionary Structural Optimization) プログラム。
beso_main.py がメインスクリプトであり、beso_conf.py から設定を読み込み、反復計算を実行する。
beso_lib.py, beso_filters.py, beso_plots.py, beso_separate.py が補助的な機能を提供する。
主要ファイルと役割:

beso_main.py: メイン実行ファイル。設定読み込み、反復ループ制御、CalculiX 実行、結果処理、状態更新を行う。
beso_conf.py: 設定ファイル。パス、最適化パラメータ、ドメイン定義などを指定。(beso_fc_gui.py により生成される可能性あり)
beso_lib.py: コアライブラリ。ファイル I/O (.inp, .log, .dat, .frd, .vtk, .csv)、要素体積/重心計算、要素状態更新 (switching) など。
beso_filters.py: 感度数/要素状態のフィルタリング (単純平均、ノードベース、点群ベース、形態学、キャスティング) を実装。
beso_plots.py: matplotlib を用いた結果のプロット (質量、FI、エネルギー密度、変位、座屈係数など)。
beso_separate.py: ノード分離スクリプト。節点応力評価のために .inp ファイルを前処理する。
beso_fc_gui.py: (推測) FreeCAD 用の GUI インターフェース。設定ファイル (beso_conf.py) の生成やプログラムの実行トリガーに関与する可能性。
処理フロー:

beso_main.py 実行
設定読み込み (beso_conf.py)
メッシュ読み込み (beso_lib.import_inp)
要素状態初期化 (beso_lib.import_*_state またはデフォルト)
要素情報計算 (beso_lib.elm_volume_cg)
フィルター準備 (beso_filters.prepare*)
(オプション) ノード分離 (beso_separate.separating)
反復ループ開始:
CalculiX 入力作成 (beso_lib.write_inp)
CalculiX 実行 (subprocess)
結果読み込み/FI計算 (beso_lib.import_*)
感度数計算
感度数フィルタリング (beso_filters.run*)
評価指標計算
ログ記録 (beso_lib.write_to_log)
(オプション) 中間結果保存 (beso_lib.export_*)
収束判定
要素状態更新 (beso_lib.switching)
(オプション) 状態フィルタリング (beso_filters.run_morphology)
(オプション) 結果保存 (beso_lib.export_*)
振動チェック
一時ファイル削除
プロット更新 (beso_plots.replot)
反復ループ終了
最終結果保存/プロット
終了処理
依存関係 (Mermaid図):

graph TD subgraph User Interface A[beso_fc_gui.py] -- Generates --> B(beso_conf.py); end subgraph Core Logic C[beso_main.py] -- Reads --> B; C -- Uses --> D[beso_lib.py]; C -- Uses --> E[beso_filters.py]; C -- Uses --> F[beso_plots.py]; C -- Optionally Uses --> G[beso_separate.py]; C -- Executes --> H[CalculiX (ccx)]; D -- Reads/Writes --> I[Input/Output Files (.inp, .log, .dat, .frd, .vtk, .csv)]; G -- Modifies --> J[Input File (*_separated.inp)]; H -- Reads --> K[Input File (.inp or *_separated.inp)]; H -- Writes --> L[Result Files (.dat, .frd, etc.)]; D -- Reads --> L; end style A fill:#f9d,stroke:#333,stroke-width:2px style B fill:#ccf,stroke:#333,stroke-width:2px style C fill:#9cf,stroke:#333,stroke-width:4px style D fill:#9cf,stroke:#333,stroke-width:2px style E fill:#9cf,stroke:#333,stroke-width:2px style F fill:#9cf,stroke:#333,stroke-width:2px style G fill:#9cf,stroke:#333,stroke-width:2px style H fill:#f96,stroke:#333,stroke-width:2px style I fill:#ccc,stroke:#333,stroke-width:1px style J fill:#ccc,stroke:#333,stroke-width:1px style K fill:#ccc,stroke:#333,stroke-width:1px style L fill:#ccc,stroke:#333,stroke-width:1px
この計画と内容で README_FLOW.md を作成してよろしいでしょうか？ もしよろしければ、「toggle to Act mode」と指示してください。ACT モードに切り替えてファイルを生成します。
```

ACTモードに切り替えて作成してもらった全体フローのドキュメント。
素晴らしすぎる...
なぜかMermaid図はClineが何度も失敗して結局勝手に諦めていた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121950/cd9f7e66-1774-486e-b131-f151cd904437.png)

## 詳細分析のためのログ出力

実行次のログを見ても、内部的な関数のどこに処理時間がかかっているかわかりません。そこでまずは実行時間を出力してもらうことにします。

```text:プロンプト
デバッグモードを追加して、各処理でどれだけ時間がかかっているか出力するようにしたいです。
特に反復ループ内でどの処理が時間がかかっているか不明なので、高速化の為にもわかるようにしてください。
```


賢いAIの回答

```text:AI回答
デバッグモードを追加して各処理の所要時間を出力したい、特に反復ループ内のボトルネックを特定したい、というご要望ですね。承知いたしました。高速化のためには処理時間の計測は非常に有効です。

以下の計画で進めたいと考えています。

デバッグモードフラグの導入:
設定ファイル beso_conf.py に、デバッグモードの有効/無効を切り替えるためのフラグ (例: debug_mode = True または debug_mode = False) を追加します。デフォルトは False にします。
メインスクリプト beso_main.py でこの debug_mode フラグを読み込むようにします。
時間計測の実装:
Python の time モジュールを使用します。
beso_main.py 内の主要な処理ブロック（初期化処理、メッシュ読み込み、フィルター準備、反復ループ全体など）の開始前と終了後に time.time() を呼び出して時刻を記録し、差分を計算して処理時間を求めます。
ご要望の通り、反復ループ内の以下の主要なステップについて、各反復ごとに時間を計測します。
CalculiX 入力ファイル作成 (beso_lib.write_inp 呼び出し前後)
CalculiX 実行 (subprocess.call 呼び出し前後)
結果読み込み/FI計算 (beso_lib.import_* 呼び出し前後)
感度数計算 (該当ブロック前後)
感度数フィルタリング (beso_filters.run* 呼び出し前後)
要素状態更新 (beso_lib.switching 呼び出し前後)
状態フィルタリング (beso_filters.run_morphology 呼び出し前後、該当する場合)
結果保存 (beso_lib.export_* 呼び出し前後、該当する場合)
プロット更新 (beso_plots.replot 呼び出し前後)
時間出力:
debug_mode が True の場合にのみ、計測した処理時間をログファイル (beso_lib.write_to_log を使用) に出力します。これにより、通常の実行時には出力されず、デバッグ時のみ詳細な情報を確認できます。
出力形式は [DEBUG] Step 'CalculiX execution' took: 15.23 sec のように、どの処理にどれだけ時間がかかったか分かりやすくします。反復ループ内では、反復番号も併記します (例: [DEBUG][Iter 5] Step 'Sensitivity filtering' took: 2.15 sec)。
確認事項:

デバッグモードを制御するフラグ名は debug_mode でよろしいでしょうか？
時間計測の結果は、ログファイル (.log) に出力する形でよろしいでしょうか？ (コンソールへの同時出力も可能ですが、ログファイルの方が後で分析しやすいかと思います。)
上記でリストアップした反復ループ内の計測ステップ以外に、特に時間を計測したい処理はありますか？
例えば beso_lib.py や beso_filters.py の中の、さらに細かい関数の処理時間まで計測する必要はありますか？ (必要であれば対応可能ですが、コードが少し複雑になります。)
これらの点についてご意見をいただけますでしょうか。ご確認いただけましたら、具体的なコード変更案を作成します。
```

確かに。実際には前後処理の時間も確認した方が良い。俺よりよっぽど賢い。
というわけで秒で方針を上司（AI）に合わせます。

```text:プロンプト
フラグ名、出力先はOKです。計測範囲はスタート時の処理も時間がかかっている気がするので、ループに入る前・後も時間計測し、出力するようにしてください
```

AIがガンガンコードを修正したのちに以下のメッセージを出力しました。

```text:AI
beso_conf.py に debug_mode フラグを追加し、beso_main.py に時間計測ロジックを実装しました。beso_conf.py で debug_mode = True と設定して実行すると、ログファイルに初期化処理および反復ループ内の各主要ステップの所要時間が出力されるようになります。これにより、処理のボトルネック特定が可能になります。
```

# 処理時間詳細


さて、ログ出力の結果、グラフ更新のPlottingに時間がかかっているのはいただけないが、最も時間がかかっているのが、"Sensitivity Filtering"だ。

|項目|修正前(秒)|
|:-:|:-:|
|	CalculiX Execution	|	49.76	|
|	Sensitivity Filtering	|	921.41	|
|	Plotting	|	333.76	|
|	Result Export (CSV/VTK)	|	165.29	|
|	Result Import/FI Calc	|	101.08	|
|	Iteration Result Export (FRD/INP)	|	0.00	|
|	Write INP	|	36.79	|
|	Filter Preparation	|	44.20	|
|	Sensitivity Calculation	|	10.78	|
|	Element Switching	|	18.28	|
|	Mean Value Calculation	|	5.54	|
|	Volume/CG Calculation	|	5.67	|
|	Append VTK States	|	3.99	|
|	Iteration Cleanup	|	0.00	|
|	Mesh Importing	|	0.84	|
|	Element State Initialization	|	0.02	|
|	Final Cleanup	|	0.04	|
|	合計時間	|	1697.45	|


続く