外部ツールの最適化
本節では、外部のコマンドラインツールを Tunny Dashboard から最適化する方法を紹介します。
Grasshopper に限らず、構造解析ソルバーや自作の計算スクリプトなど、コマンドラインから実行できるツールであれば何でも最適化の評価に使えます。 サンプリングは Dashboard 内蔵の Rust 実装が行うため、最適化の実行のために Python や Optuna をインストールする必要はありません。 必要なのは評価に使うツール自体の実行環境だけです。 たとえば評価対象が Python スクリプトであれば、そのスクリプトを動かす Python 環境だけを用意すれば動きます。 結果は Optuna 互換の journal ファイルに記録され、ライブ更新とすべての分析ウィジェットをほかの Study と同じように使えます。
ツールとの連携には、「テンプレート置換 → 実行 → 出力抽出」というファイルインターフェース方式を採用しています。 ツールごとの専用アダプタを作るのではなく、入出力の形式を宣言するだけで連携できるため、ベンダー固有の知識なしに任意のツールを組み込めます。
以下のような流れで最適化を実行します。
- ツール定義(プロセス定義)を定義ビルダーで作成し、JSON として保存
- 実行設定モーダルで変数の探索範囲、目的の方向、サンプラーを設定
- 最適化を実行し、結果をライブ更新で確認
1 トライアルの評価パイプライン
サンプラーが変数の値を提案するたびに、Dashboard は次の手順で外部ツールを 1 回評価します。
- パラメータ値をツールへ渡す形式に変換(入力ファイルの生成、環境変数の構築など)
- pre-command が設定されていれば実行
- 本体コマンドを実行(タイムアウトとリトライあり)
- post-command が設定されていれば実行
- 標準出力または出力ファイルから、目的関数と制約の値を抽出
pre-command と post-command は、評価の前後に挟む補助コマンドです。 たとえば入力データの準備や、ソルバーの生の出力を抽出しやすい CSV へ変換する処理を挟むことで、本体ツールに手を入れずに連携できます。
パラメータの渡し方
ツールがすでに持っている入力の流儀に合わせて、4 つの方式から選べます。
| 方式 | ツールが受け取るもの |
|---|---|
| Command-line args | コマンドライン引数。--{name}={value} のようなテンプレートをパラメータごとに展開 |
| Environment variables | パラメータと同名の環境変数 |
| JSON on stdin | {"length": 12.5} のような JSON オブジェクトを標準入力へ |
| Input file template | {name} プレースホルダーを置換した入力ファイルを指定パスへ書き出し |
Input file template では、テンプレート中の {length}
のようなプレースホルダーがそのトライアルの値に置き換わります。
波括弧そのものを入力ファイルに書きたい場合は、{{ と }} でエスケープします。
整数値は 3.0 ではなく 3
として書き出されるため、整数を期待してパースするツールにもそのまま渡せます。
出力の抽出
目的関数と制約は 1 つずつ、値の取得元(標準出力または出力ファイル)と抽出方法を指定します。
| 抽出方法 | 仕様 |
|---|---|
| Regex | 正規表現の最初のキャプチャグループ(グループがなければマッチ全体)を数値として読み取る |
| JSON path | results.weight のようなドット区切りパスで JSON をたどる。配列は values.0 のように指定 |
| CSV | 行(インデックスまたは最終行)と列(インデックスまたはヘッダー名)で 1 セルを指定 |
抽出した値は有限の数値でなければなりません。 出力が数値でない場合や NaN、無限大の場合、そのトライアルは静かに間違った値で成功するのではなく、失敗(FAIL)として記録されます。 発散したソルバーの結果が最適化を汚染しないようにするための仕様です。
また、12.5 kg
のような単位付きの文字列は部分的に読み取られず、抽出エラーになります。
数値の部分だけをキャプチャグループで切り出す正規表現を書いてください。
ツール定義の作成
ツールバーの New Tool… をクリックすると、定義ビルダー(Tool Definition モーダル)が開きます。 ビルダーは定義の JSON と 1 対 1 に対応するフォームで、次のセクションを上から順に埋めていきます。
- Parameters:最適化変数の名前。テンプレートや引数から
{name}で参照される - Input:パラメータの渡し方(前述の 4 方式)と、その方式ごとの設定
- Command:実行するプログラム、固定引数、作業ディレクトリ、タイムアウト、リトライ回数
- Objectives / Constraints:名前、値の取得元、抽出方法
- Hooks (optional):pre-command と post-command の有効化
下部の Save to File… で定義を JSON ファイルへ保存でき、Load… で既存の JSON を読み込んで編集できます。 Optimize → を押すと、保存を経由せずにそのまま実行設定へ進めます。
定義は次のような素の JSON のため、ファイルとしてチームで共有したり、テキストエディタで直接編集したりもできます。
{
"param_names": ["length", "thickness"],
"input": { "kind": "args", "arg_template": "--{name}={value}" },
"command": {
"program": "python3",
"args": ["solve.py"],
"timeout_secs": 60,
"retries": 1
},
"objectives": [
{
"name": "mass",
"source": { "kind": "stdout" },
"extractor": { "kind": "regex", "pattern": "mass\\s*=\\s*([0-9.eE+-]+)" }
}
],
"constraints": [
{
"name": "stress",
"source": { "kind": "file", "path": "out.json" },
"extractor": { "kind": "json_path", "path": "results.stress_ratio" }
}
]
}固定引数(Fixed args)はテンプレート展開されず、そのまま渡されます。 そのため、引数に波括弧を含む awk やシェルのワンライナーを指定しても誤って置換されることはありません。
実行設定と実行
保存済みの定義 JSON を開くか、ビルダーの Optimize → を押すと、実行設定モーダル(Tool Optimization)が開きます。 コマンドの内容は読み取り専用で表示され、次の項目を設定します。
- Variables:各パラメータの探索範囲(Low /
High)、小数桁数(Digits)、整数フラグ(Integer)。範囲は
[0, 1]から編集を始める - Objectives:目的ごとに Minimize / Maximize を選択
- Sampler:NSGA-II(Population、デフォルト 16 / Generations、デフォルト 10)または Random(Trials、デフォルト 50)と、乱数シード(デフォルト 42)
- Output:journal ファイルの保存先と Study 名
Low が High 以上の行は赤字で警告され、修正するまで Run ボタンは有効になりません。 制約は定義の一部のためここでは編集できず、すべての制約値が 0 以下のときにトライアルが実行可能(feasible)とみなされます。 制約を満たさないトライアルも記録され、違反量が NSGA-II の探索を実行可能領域へ誘導します。
Run を押すと Study が即座に journal へ作成され、右下の進捗オーバーレイとライブ更新で進み具合を確認できます。 トライアルの評価は並列で行われ、コマンドの失敗やタイムアウトが起きてもそのトライアルが FAIL として記録されるだけで、最適化全体は継続します。 完了すると、成功と失敗のトライアル数がオーバーレイに表示されます。
具体例
2 変数の関数 を、標準出力に結果を印字するだけの小さなツールで最小化する例です。 どの言語で書かれたツールでも構いませんが、ここでは awk を使っています。
{
"param_names": ["x", "y"],
"input": { "kind": "args", "arg_template": "{value}" },
"command": {
"program": "sh",
"args": [
"-c",
"awk \"BEGIN{print \\\"f=\\\" (($1-3)*($1-3) + $2)}\"",
"sh"
],
"timeout_secs": 10,
"retries": 1
},
"objectives": [
{
"name": "f",
"source": { "kind": "stdout" },
"extractor": { "kind": "regex", "pattern": "f=([-0-9.]+)" }
}
]
}実行設定で 、 を最小化方向に設定して Run を押すと、Dashboard がサンプリングした点をコマンドが評価し、通常の Optuna Study として結果が書き込まれます。 最適化エンジンとしての Python や Optuna を用意することなく、最適化の実行から分析までが完結します。
注意点
- シェルを経由しない:コマンドは OS のプロセスとして直接起動される。Windows
のバッチファイルは
cmdを Program にして/Cとスクリプトパスを引数へ、シェルスクリプトはインタープリタを明示する - 作業ディレクトリの基準:Working dir を空欄にすると Dashboard のカレントディレクトリが基準になる。出力ファイルの相対パス解決にも影響するため、絶対パスの指定が確実
- 並列評価とファイル競合:トライアルは並列に評価される。Input file template のように固定パスへ読み書きするツールは、複数トライアルが同じファイルを取り合う可能性があるため、並列実行に安全な作りかを確認する
- Adaptive (surrogate) サンプラーは未対応:現在選べるのは NSGA-II と Random のみ。アダプティブサンプリングは Grasshopper (.ghx) 最適化で先行対応
- 抽出できるのは目的と制約のみ:トライアルごとのユーザー属性の抽出には未対応
まとめ
本節では、ファイルインターフェースによる外部ツールの最適化を紹介しました。
- 任意のツールを統合:テンプレート置換 → 実行 → 出力抽出の宣言だけで、専用アダプタなしに連携
- 定義は共有できる JSON:GUI の定義ビルダーで作成し、ファイルとしてチームで再利用
- 実行時依存なし:最適化エンジンとしての Python や Optuna は不要。必要なのは評価ツール自体の実行環境のみ
- 失敗に強い:非数値出力やタイムアウトは FAIL として記録され、最適化は止まらない
社内の解析ツールや計算スクリプトは、コマンドラインから実行できる形になっていることが少なくありません。 そうしたツールをほぼそのまま最適化ループに乗せられることが、この方式のもっとも大きな価値です。 まずは本節の具体例のような小さな定義から、手元のツールで試してみてください。