JuliaパッケージModelingToolkit.jlでタンクモデルを作成する
久しぶりの投稿です。
今回は非因果モデリングのためのJuliaパッケージModelingToolkit.jl
を使用してタンクモデルを作成してみます。
ModelingToolkit.jlについて
ModelingToolkitはJuliaネイティブのモデリングツールで、方程式ベースでモデルを記述できます。方程式ベースのため入力と出力を区別しない非因果的なモデリングが可能です。その他の非因果モデリングツールはmodelica
や同じくJuliaベースのmodia.jl
が有名ですが、ModelingToolkitとそれらの相違点は以下の記事に詳しく書かれています。
また、ModelingToolkitのドキュメントは以下になります。
今回の題材
今回の題材は以下のようなものになります。
ポンプから一定流量qin
でタンクに水が流れ込みます。タンクの下部には開度R
のバルブが取り付けられており、水位h
に応じて水が流出します。
これをModelingToolkitでモデリングしてみます。
モデリングの考え
以下のようにモデリングしてみます。
なお、タンクのこれらの関係式は以下のようになります。
実装
では、この簡単なモデルを実装してみましょう。
使用パッケージの宣言
以下のように使用するパッケージを宣言します。
using ModelingToolkit using DifferentialEquations, Plots
モデリングに必要なModelingToolkit
と、微分方程式を解くのに必要なDifferentialEquations
、そして結果の描画に必要なPlots
ですね。
タンクのモデリング
タンクは以下のようにモデリングできます。
function tank(;name) @parameters C R @variables t h(t) qin(t) qout(t) D = Differential(t) eqs = [ D(h) ~ (qin - qout)/C, qout ~ R * h] ODESystem(eqs,t,[h,qin,qout],[C,R];name=name) end
@parameters
でパラメーターを宣言しています。ここでパラメーターとはシミュレーション中に値が変化しないもの(前後では変化する)としておきます(明確な定義はありません。しかしmodelicaはそのようなイメージでパラメーターを扱っています)。また、@variables
は変数で、シミュレーション中に値が変化する可能性があるものとします。
D = Differential(t)
は時間tの微分を定義しています。続くeqs
で方程式を定義します。qout ~ R * h
のようにModelingToolkitでは等号は~
で表現します。
ここでいくつか注意事項です。
ModelingToolkitではパラメーターと変数の違いが不明確で、パラメーターもシミュレーション中に値が変化しても実は問題ありません。ただし、パラメーターの経時変化はシミュレーション結果に記録されません。
D(h) ~ (qin - qout)/C
のように、微分項は必ず数式の左辺に単独で配置する必要があります。たとえばこれをC*D(h) ~ (qin - qout)
とする場合、微分項に係数があるためエラーになります。
ポンプのモデリング
続いてポンプのモデリングは以下のようになります。
function pump(;name) @variables t q(t) eqs = q ~ 1 ODESystem(eqs,t,[q],[];name=name) end
注意点はタンクのモデリングの際と特に違いありません。
システムのモデリングと簡略化
これまではタンクとポンプという各コンポーネントのモデリングでしたが、次にこれらを繋ぎ合わせてシステムとしてモデリングしていきます。
モデルの呼び出し
タンクとポンプはまだ関数を作成しただけですので、それらを呼び出してみます。
@named tankA = tank() @named pumpA = pump()
それぞれtankA
、pumpA
と名付けました。
モデルの接続
タンクとポンプを接続します。モデル図を見てわかりますように、ポンプの変数qとタンクの変数qinは等しいです。
eqs = tankA.qin ~ pumpA.q @named connected = compose(ODESystem(eqs),tankA,pumpA)
これはモデルconnected
がtankA
とpumpA
をサブシステムとして内包していると解釈できます。
モデルの簡略化
ここでモデルconnected
の数式を見てみましょう。
equations(connected)
で確認できます。
4-element Vector{Equation}: tankA₊qin(t) ~ pumpA₊q(t) Differential(t)(tankA₊h(t)) ~ (tankA₊C^-1)*(tankA₊qin(t) - tankA₊qout(t)) tankA₊qout(t) ~ tankA₊R*tankA₊h(t) pumpA₊q(t) ~ 1
この式にはtankA₊qin(t)
とpumpA₊q(t)
が両方存在していますが、両者には等号の関係があるのでどちらかは削除できるはずです。他にもpumpA.q(t)
やtankA.qout
も削除できそうです。このように変数を消去して簡略化する命令がstructural_simplify
になります。
simplified_sys = structural_simplify(connected)
ちゃんと簡略化されたか確認してみましょう。
equations(simplified_sys)
1-element Vector{Equation}: Differential(t)(tankA₊h(t)) ~ (tankA₊C^-1)*(1.0 - (tankA₊R*tankA₊h(t)))
4つあった数式が1つにまで簡略化されました。
シミュレーション
最後に、このモデルを微分方程式ソルバーで解いていきます。
x0 = [tankA.h => 1] ##初期値 p = [tankA.C => 1, tankA.R => 1/5] ##パラメーターの値 prob = ODEProblem(simplified_sys, x0,(0.0, 20.0),p) sol = solve(prob,Tsit5()) plot(sol) ## 結果の描画
無事、結果の描画までたどり着くことができました。
さいごに、今回のコードを置いておきます。
using ModelingToolkit using DifferentialEquations, Plots function tank(;name) @parameters C R @variables t h(t) qin(t) qout(t) D = Differential(t) eqs = [ D(h) ~ (qin - qout)/C, qout ~ R * h] ODESystem(eqs,t,[h,qin,qout],[C,R];name=name) end function pump(;name) @variables t q(t) eqs = q ~ 1 ODESystem(eqs,t,[q],[];name=name) end @named tankA = tank() @named pumpA = pump() eqs = tankA.qin ~ pumpA.q @named connected = compose(ODESystem(eqs),tankA,pumpA) simplified_sys = structural_simplify(connected) x0 = [tankA.h => 1] ##初期値 p = [tankA.C => 1, tankA.R => 1/5] ##パラメーターの値 prob = ODEProblem(simplified_sys, x0,(0.0, 20.0),p) sol = solve(prob,Tsit5()) plot(sol) ## 結果の描画
今回はひとまずここまでにします。