【Grasshopper】SciPyの最適化をリアルタイムで動かそう

皆様こんにちは。
AMDlabの齋藤です。
今回はGrasshopperのPython 3を用いてSciPy Optimizationによる最適化を、Rhino Grasshopper側がリアルタイムで動くように実装してみます。
具体的には以下のようなプログラムです。

それでは始めましょう。
今回のデータはこちらに挙げております。

https://github.com/AMDlab/TechBlog-Grasshopper-Realtime-Optimization

SciPyの最適化


それではまず、今回行うSciPyで扱える最適化、SLSQP(逐次二次計画法)について簡単に説明します。
SLSQPではGrasshopperで使える遺伝的アルゴリズムのGalapagosやNSGA2のWallacei、SPEA2等のOctopus、さらにベイズ最適化等のTunnyとはまた違った最適化が行えます。
具体的には、上記は簡単に最適化を構成することができますが、それらに比べてSLSQPではより短時間で厳密な最適解を求めることができ、さらに、制約条件をペナルティ法等を利用せずに設定することができます。
その代わりに、感度解析と呼ばれる目的関数や制約条件の関数を設計変数で偏微分した関数を用意する必要があったり、設計変数は連続的な変数しかできない、さらに、最適解を出したとしても局所最適解へおちいっていることもあったりもします。

実際に建築実務で扱う場合、これらの条件を考えると、SLSQPよりカスタムコンポーネントで実装されているアルゴリズムの方が良かったりもしますが、今回はSLSQPを動かそうと思います。

今回の目的関数と制約条件


今回の最適化で扱う目的関数は、SciPyでチュートリアルで使われているローゼンブロック関数です。
https://docs.scipy.org/doc/scipy/tutorial/optimize.html#sequential-least-squares-programming-slsqp-algorithm-method-slsqp
ローゼンブロック関数はよく最適化のベンチマークとして利用されているようです。
制約条件は2次関数で、境界条件はx0が0~1の範囲、x1が-0.5~2の範囲です。
今回の最適化は、具体的には以下のような最適化となります。

\begin{align*} \min_{x_0,x_1}\; & 100(x_1 - x_0^2)^2 + (1-x_0)^2 \\ \text{s.t.}\; & 1 - x_0^2 - x_1 \ge 0\\ & \begin{array}[t]{r@{\;\le\;}l@{\;\le\;}l} 0 & x_0 & 1 \\ -0.5 & x_1 & 2 \end{array} \end{align*}

x0をx、x1をyとした場合、コンター図に表すと以下のようになります。
コンター図の赤が目的関数の数値が大きく、青が小さいです。
制約条件は赤色で示されている範囲をNGとします。

コンポーネント内でSLSQPを実行


次にPython 3 Scriptのコンポーネント内でSLSQPを動かしてみます。
以下をPython 3 Scriptに入れて実行します。

 

以上を動かすことによって結果が出力されます。

しかし、このコンポーネント内で最適化を行うため、最適化がうまく動いているか、いつ最適化が終わるか、把握が難しいです。
そこで、最適化をリアルタイムで動かして、最適化が動いている様子を見てみようと思います。

リアルタイムに最適化を動かす


次は実際にGrasshopperへ設計変数を流して、最適化を動かすプログラムを作成します。

設計変数
まず、変数をNumber Sliderで作成します。
変数はそれぞれ0~1と-0.5~2の範囲でしたので、そのように設定します。
今回の変数は連続変数となるので、より細かい小数が扱えるようにしたいです。
そのようにする場合は、Number Sliderを数字で作るときに小数点以下のゼロをいくつも入れることで、より細かく刻むことのできるNumber Sliderを作ることができます。

目的関数、制約条件、感度解析
次に目的関数、制約条件、そしてそれぞれの感度解析のコンポーネントを作成します。
中のプログラムは以下へ記載します。
https://github.com/AMDlab/TechBlog-Grasshopper-Realtime-Optimization/tree/main/Method
x0x1 のType hintはfloatとしています。

最適化コンポーネント
最後に最適化コンポーネントを作成します。
まずPython 3 Scriptの入出力を作成します。
入力は上から順に

  • run , Type Hint: bool, Item Access, 解析を実行する関数
  • x ,  Type Hint: (float), Tree Access, 設計変数(設計変数のNumber Sliderを取得するために繋ぐ)
  • obj , Type Hint: float, Item Access, 目的関数の数値
  • obj_der , Type Hint: float, List Access, 目的関数の感度解析の数値
  • cons , Type Hint: float, Item Access, 制約条件の数値
  • cons_jac , Type Hint: float, List Access, 制約条件の感度解析の数値
  • reset , Type Hint: bool, Item Access, リセット

さらに、 x に関してはNumber Sliderをつなぐ順序があります。上から順に繋いでください。
出力は h_objh_x でobj, xの履歴を保存します。

以上で入出力を作成できました。
次に中身を作成します。

 

それでは説明していきます。

ここではNumPyとSciPyのライブラリをインストールします。
既にインストールした後であれば、必要はありません。

ライブラリの関数を使えるようにします。
NumPyとSciPyに加え、Number Sliderの判別や、最適化を別タスクで動かす、Number Sliderで読み取れる数値へ変換する、遅延させる、2次元配列をTreeへ置き換える等が行えるようにします。

ここでは sticky のkeyを作成します。
sticky はRhino上で保持されるPython辞書です。
ここで変数として作成しておけば、誤字をしたときにエラーの原因が特定しやすくなります。

ここでGrasshopperへ処理を流します。
この処理のアイデアはTunnyから少し頂きました。

ここでは、SciPyのOptimizationから投げられる命令を処理する関数です。
履歴が残るようにもしています。

ここでは最適化を動かします。

初回と reset が押されたときにリセットします。
run が動いているときに reset されると壊れるため、エラーを投げるようにもしています。

run が押されたとき、最適化の状態に応じて処理を実行ます。
解析を回す際は System.Threading.TasksTask.Factory.StartNew で別のタスクとして解析を回せるようにします。

これでプログラムが完成しました。
実際に動かしてみます。

ちゃんと最適解が求まりました。
コンポーネント内でSLSQPを実行した場合と解はある程度一致していそうです。

処理の仕組み


今回作成した処理はリアルタイムで動かすため、かなり煩雑な処理となっています。
最適化中は以下のような処理を行っていました。

設計変数がSciPyのSLSQPで作成され、別タスクで宣言している関数へ流れます。
その後、設計変数はPythonの外側のNumber Sliderへ渡され、処理が行われます。
その処理がPythonへ入り、sticky経由で関数へ返ります。それまではstatusが”processed”に変わるまで、待ちます。
そして結果がSLSQPへ送られます。

注意点


今回はGrasshopper上で最適化を行いましたが、Grasshopperでは取り扱える数値は少し癖があります。
数値をパラメーターからパラメーターへ渡すとき、GH_Gooでの変換が自動的に起こり、数値が少し変わることがあります。
具体的には0に近い数値が0として変換され、処理へ流れることがあります。
そのため、SLSQPのような連続変数最適化は実はGrasshopperで動かすのは向いていなかったりします。
実際に今回のプログラムでは ftol の値を少し上げて、最適解となりやすいようにしています。

まとめ


今回はGrasshopperをリアルタイムで最適化させてみました。
この方法では最適解が厳密にならない等少し甘い部分がありましたが、この方法を利用してより良いものができれば良いなと思っています。
ぜひ皆さんも試してみてください。


AMDlabでは、開発に力を貸していただけるエンジニアさんを大募集しております。少しでもご興味をお持ちいただけましたら、カジュアルにお話するだけでも大丈夫ですのでお気軽にご連絡ください!
中途求人ページ: https://www.amd-lab.com/recruit-list/mid-career

カジュアル面談がエントリーフォームからできるようになりました。
採用種別を「カジュアル面談(オンライン)」にして必要事項を記載の上送信してください!
エントリーフォーム: https://www.amd-lab.com/entry

ARTICLES