バッチ評価¶
サンプリングによる検討、シナリオ探索、最適化では、1つのモデルを数千回以上評価します。Graphcal は検査済みのモデルをこうした解析に提供できます。モデルのコンパイルは1回だけで、クライアントがパラメータ値のバッチを送ると、行ごとに1つの結果が返ります。計算には graphcal eval と同じ型付き DAG を使います。
手順は次の3つです。
- モデルのインターフェースを1回だけ取得する。
- 変化させるパラメータと読み出す出力を選んで、セッションを開始する。
- リクエストのバッチを送り、結果を読む。
1. モデルを記述する¶
エントリファイルであれば、どのファイルでも使えます。値域(min、max)が公開されるので、サンプラーや最適化ツールは探索範囲を知ることができます。
pub index Mode = { Nominal, Degraded };
param load: Force(min: 0.0 N, max: 10_000.0 N);
param cycles: Int(min: 0, max: 100_000) = 10_000;
param mode: Key<Mode>;
assert load_positive = @load > 0.0 N;
pub node failure: Bool =
@load > 8_000.0 N && @cycles > 50_000 && @mode == Mode#Degraded;
pub node margin: Force = 10_000.0 N - @load;
このコマンドは、すべてのパラメータと public なノードを並べた JSON 文書を出力します。
{
"protocol_version": 2,
"graphcal_version": "0.0.1-alpha.37",
"interface_fingerprint": "sha256:…",
"interface": {
"parameters": [
{
"name": "load",
"graphcal_type": "Force",
"wire": { "kind": "quantity", "dimension": { "Length": 1, "Mass": 1, "Time": -2 }, "unit": "m*kg/s^2" },
"domain": { "kind": "quantity", "min": 0.0, "max": 10000.0 },
"has_default": false
},
…
],
"outputs": [ … ]
}
}
wireは値の受け渡し方です(下の表を参照)。プロトコルで運べない型も一覧には載り、"kind": "unsupported"と理由が付きます。黙って除外されることはありません。unitはそのポートの SI の基準単位です。セッションでそのポートに別の単位を選ばない限り、値はこの単位でやりとりします(後述)。domainも同じ単位で表されます。インデックス付きのパラメータでは、すべての要素を制約します。値域外の値は、その行だけを失敗させます。
| Graphcal の型 | kind |
値 |
|---|---|---|
| 物理量 | quantity |
unit で表した数値 |
Complex<D> |
complex |
unit で表した実部と虚部 |
Int、Bool |
int、bool |
整数または真偽値 |
Key<I> |
key |
ヴァリアント名、0から数えた Fin の位置、または SI の座標 |
Datetime<S> |
datetime |
J2000(2000-01-01T12:00:00)からの秒数。S で読む |
T[I] |
indexed |
I の要素ごとに1つの T。宣言順に並ぶ |
| 構造体型・共用体型 | algebraic |
1つのコンストラクタとそのフィールド |
interface_fingerprintはこのインターフェースを識別します。式を変えても指紋は変わりませんが、ポートの追加・削除・型の変更では変わります。
2. セッションを開始する¶
セッションでは、変化させるパラメータ(--input)と読み出す出力(--output)を指定します。変化させないパラメータは、セッションで固定しない限りデフォルト値のままです(値でパラメータを固定するを参照)。そのため、デフォルト値のないパラメータは変化させるか固定します。
graphcal model serve reliability.gcl \
--input load --input mode --output failure --output margin \
--expect-fingerprint sha256:…
--expect-fingerprint を付けると、モデルが記述時から変わっていた場合にサーバは起動を拒否します。手作業でサーバを起動するときは、代わりに --skip-fingerprint-check を付けます。どちらか一方の指定が必須です。
物理量を別の単位で送受信するには、そのポートに --unit NAME=UNIT を付けます。単位は serve するファイルの中で -> の後に書いた場合と同じように解決される単位式です。ポートの次元を測る単位で、かつ倍率が固定されている必要があります。基本単位、const 単位、プレリュードの単位は使えますが、実行時単位は使えません。セッションの値域は選んだ単位に換算されます。
graphcal model serve reliability.gcl \
--input load --input mode --output failure --output margin \
--unit load=kN --unit margin=kN \
--expect-fingerprint sha256:…
--unit はポート全体の単位を選びます。構造を持つ出力の中の物理量を別の単位で表示するには、graphcal model unit(Rust からは Model::resolve_units)で単位を解決します。単位が測る次元と SI での倍率が得られるので、SI の値を自分で換算します。
サーバは stdin からリクエストのバッチを読み、stdout に結果を Arrow IPC ストリームとして書きます。端末から直接操作するものではなく、クライアントから駆動します。
3. Rust から評価する¶
graphcal-model-client crate は2つのコマンドを実行し、Arrow を内部に隠します。ハンドルには型(Quantity、Complex、Int、Bool、Key、Datetime、Indexed<K>、Algebraic)が付いているので、各列にはそのポートの種類の値しか渡せません。
use graphcal_model_client::{Bool, GraphcalCommand, Key, Model, Quantity};
use graphcal_model_protocol::{RowOutcome, UnitSpelling};
let model = Model::describe(GraphcalCommand::new("reliability.gcl"))?;
let load = model.input::<Quantity>("load")?;
let mode = model.input::<Key>("mode")?;
let failure = model.output::<Bool>("failure")?;
let mut session = model
.session()
.vary_in(&load, UnitSpelling::new("kN")?)
.vary(&mode)
.output(&failure)
.spawn()?;
let degraded = mode.variant("Degraded")?;
let request = session
.batch()
.column(&load, [2.0, 9.0])?
.column(&mode, [degraded.clone(), degraded])?
.build()?;
let result = session.evaluate(&request)?;
for outcome in result.outcomes() {
match outcome {
RowOutcome::Success(row) => println!("failure: {}", row.get(&failure)?),
RowOutcome::Failure(failure) => println!("failed: {}", failure.message()),
}
}
session.shutdown()?;
Tenax はこのクライアントを使って Graphcal モデルをサンプリングし、PRIM によるシナリオ探索を行います。
値でパラメータを固定する¶
変化させないパラメータをデフォルト以外の値で評価したいとき、またはデフォルト値のないパラメータに値を与えたいときは、graphcal eval と同じ形で serve に値を渡します。--param NAME=VALUE、--params-json、--params-json-file のいずれかを使います。stdin はリクエストに使うため、--params-json-file - は使えません。サーバーは起動時に値を検査し、すべての行で使います。固定したパラメータを同時に変化させることはできません。Rust からは SessionBuilder::fix_from_json_file で JSON ファイルを渡します。
graphcal model serve reliability.gcl \
--input load --output failure --output margin \
--params-json-file scenario.json \
--expect-fingerprint sha256:…
ラッパーファイルでパラメータを固定する¶
一部のパラメータを Graphcal で計算した値に固定したいときは、モデルを include する小さなラッパーを書き、そのラッパーを serve します。別のファイルを include するにはパッケージが必要です(複数ファイルのプロジェクトを参照)。ここでは、モデルを plant という名前のパッケージの src/plant/reliability.gcl に置いています。
import plant.reliability::{ index Mode };
param load: Force(min: 0.0 N, max: 10_000.0 N);
include plant.reliability(load: @load, cycles: 80_000, mode: Mode#Degraded)
::{ pub failure, pub margin };
固定値は通常の Graphcal の式なので、単位と型は他の束縛と同じように検査されます。波括弧のリストで pub を付けた include の出力は再エクスポートされ、ラッパー自身で宣言した pub node と同じく出力になります。
行ごとの結果¶
リクエストの各行は、リクエストと同じ順序で必ず1つの結果を返します。
| 結果 | 発生する条件 | 出力 |
|---|---|---|
| 成功 | 行を評価できた | 選んだすべての出力 |
| アサーション不成立 | assert が成り立たなかった。最適化ではその行を実行不可能解として扱える |
なし |
| 値域外の入力 | 値がパラメータの min/max の範囲外 |
なし |
| 評価エラー | ゼロ除算など、それ以外の実行時の失敗 | なし |
ストリームの形式不正、セッションと異なるリクエストのスキーマ、NaN や無限大の値、インデックスにないキーはプロトコルエラーです。このときサーバは行の結果を返さず、終了コード 1 で停止します。
別のクライアントを書く¶
他の言語のクライアントは graphcal model serve と直接やりとりします。
graphcal model describeを実行し、ポートを選ぶ。--input、--output、必要な--unit、--expect-fingerprintを付けてgraphcal model serveを起動する。- stdin に書き込む前に、stdout から結果ストリームのスキーマを読む。スキーマ全体のメタデータの
si_scalesには、選んだ各単位が SI の基準単位でいくつ分かが載っている。リクエストのスキーマにも同じメタデータが入り、その値域は選んだ単位に換算されている。 - stdin に1本のリクエストストリームを書く。リクエストのバッチを1つ送るごとに、結果のバッチを1つ読む。
- リクエストストリームを閉じると、サーバは結果ストリームを終えて終了コード
0で終了する。
スキーマには graphcal というメタデータキーが1つだけあり、その値は JSON です。リクエストストリームには、変化させるパラメータごとに null を許さないフィールドが宣言順に並びます。結果ストリームには2つの struct フィールドがあります。
| フィールド | 子フィールド |
|---|---|
outputs |
読み出す出力ごとに null を許すフィールドが宣言順に並ぶ。失敗した行でだけ null になる |
outcome |
status(UInt8:0 成功、1 アサーション不成立、2 値域外の入力、3 評価エラー)、subject(不成立のアサーション名またはパラメータ名)、message |
物理量と日時は Float64、Int は Int64、Bool は Boolean です。名前付きインデックスのキーは Dictionary<Int32, Utf8> で、辞書のコードではなく辞書の値で解決されます。Fin(N) のキーは UInt32 の位置です。座標インデックスのキーは SI の Float64 座標で、インデックスの座標のどれかと厳密に一致しなければなりません。複素数は re と im の struct、インデックス付きの値は軸の要素ごとに1項目を持つ FixedSizeList です。構造体型はそのフィールドの struct です。共用体は constructor キーと variants struct からなる struct で、variants はコンストラクタごとに null を許す子フィールドを持ち、その行のコンストラクタの子だけが null になりません。