記事の順番,難易度分けなどはこちらをご覧下さい.
相変化を取り扱う.本例題が目的としていることは大きく下記の2つ。
- 熱流体の相変化(液相→気相)プロセスを取り扱えるようになる.
- Modelica Standard Library, FluidのExample内に有る,”相変化に対応したvolume”のコンポーネントの使い方を知る.
手書きスケッチと作成対象物の整理
モデリング対象は蒸気発生器,ボイラー.液相の水と熱量を投入して,高温・高圧の蒸気を発生させる装置.発電所や船舶の機関の一部をなすものだ.
下図は”内部がどうなっているか”を示す模式図.と言っても今回ここまで複雑に入り組んだ内部構造をモデリングする訳ではなく,飽くまで,水と熱量供給→蒸気発生のプロセスをモデル化する.
既に模式図が登場したが,作ろうとしているシステムを自分の手で書き下すのは、今回も定型作業として実施しておこう。作成しようとしているものは上の模式図よりも何倍も簡略化されたものだ.
同じく定型作業として、モデル化対象物についての情報を纏める。極力簡潔に、箇条書きを並べるのがお勧め。
- 等容空間内での水の加熱.
- 加熱は熱量を直接任意の値を与える.
- 水の液相から気相への変化.
- 蒸気発生器下流には圧力損失要素と出口大気解放.→蒸気発生器出口圧力は,下流の圧力損失で決まる.
- 液相の水の供給量は,蒸気発生器内の液相容積割合が一定になるように制御する.
- 水(液相)供給温度
- 蒸気発生器投入熱量
- バルブ出口圧力
- 蒸気発生器液体割合目標値(制御入力)
- 蒸気バルブ開度
- 蒸気発生器流入流出質量流量
- 蒸気発生器内の水質量
- 蒸気発生器内の水・蒸気の体積
- 蒸気発生器内の水温度
- 蒸気発生器内の水圧力
内包する物理現象:
境界条件(Input):
Output(注目する出力):
model(とpackage)ファイルの作成
過去記事でも述べたように、必須ではないが、演習例題用のpackageは作成しておこう。過去記事の例で既に作成済みなら、そのpackageにmodelを作成すればOK。
コンポーネントの配置
下図の通りにコンポーネントを配置、接続する。
インスタンス名はコンポーネントを配置する際に自動で命名される(標準設定では名前を尋ねるウィンドウが出てくる)が、筆者は適宜、名前をつけた方が後々判りやすいものには命名している。下図のものだと解りにくければ、解りやすいように名前を変えてくれて構わない。
回路は非常に単純で,1本の流路と2つの要素で構成されているのみ.他の物理的な接続は,evaporatorに渡される熱量流入のみ.
後は,boundaryが供給する質量流量についてのフィードバック制御ループ.Evaporator内の液相体積割合を制御対象とし,PI制御を用いて供給する液相の水の質量流量を決定する.
- Modelica.Fluid.System
- Modelica.Fluid.Sources.MassFlowSource_T
- Modelica.Fluid.Examples.DrumBoiler.BaseClasses.EquilibriumDrumBoiler
- Modelica.Fluid.Valves.ValveLinear
- Modelica.Fluid.Sources.Boundary_pT
- Modelica.Thermal.HeatTransfer.Sources.PrescribedHeatFlow
- Modelica.Fluid.Sensors.Temperature
- Modelica.Fluid.Sensors.Pressure
- Modelica.Fluid.Sensors.MassFlowRate
- Modelica.Blocks.Math.Gain
- Modelica.Blocks.Math.Feedback
- Modelica.Blocks.Continuous.PI
- Modelica.Blocks.Sources.Ramp
使用コンポーネントリスト
必要なコンポーネントを見つけるのは慣れていないと意外と苦労する作業なので、総てのコンポーネントをリストアップしておく。概要については過去記事で記述したものは省略する。
本例題の主役コンポーネント.流体への熱授受と相変化が為される.”相変化に対応したVolume”コンポーネントと呼べるもの.何故かExampleの奥のパッケージに収録されており,Volumeコンポーネントと並列しては収録されていない.
port_aは液相のみ,port_bは気相流体のみが通過する.それぞれの質量流量は蒸気生成量と前後の流路で決まるので,流入と流出の質量流量は必ずしも一致しない.また,コンポーネント内の気と液の体積の和は必ずparameterで定めたもので一定となるように出来ており,気・液いずれかの体積が完全に0を下回り負値になる事も有り得るので運用には注意が必要.
また筐体の熱容量による熱慣性も含むモデルであり,質量・比熱を与えて熱の遅れをシミュレートに含めることも出来る.
アイコン上部のreal-out connectorは液相体積の値を出力するもの.
因みに,本コンポーネントは”Boiler”と名付けられているが,実は液相→気相の状態変化しかシミュレート出来ないわけでなく,流体と熱量の流れを逆にする事で凝縮器(復水器)として機能させる事も出来る.また別記事で使い方を紹介しようと考えている.
parameterの設定
各コンポーネントのparameter設定を示す。コンポーネントを右クリックしてparametersを選択するか、ダブルクリックするとparametersウィンドウが現れるので、必要箇所に値を設定する。
尚、デフォルト設定のままにしておいて済むコンポーネント、parameterは記載を省略する。また、parameterの意味もparameterウィンドウ上の記載から意味が理解可能なものは省略/簡潔に済ます。
- LiquidSupplyPump
- use_m_flow_in: true
- T: 15+273.15 [K]
- evaporator
- Generalタブ
- m_D: 1e-6 [kg]
- cp_D: 500 [J/kg/K]
- V_t: 1.0 [m3]
- V_l.start: true [m3]
- Assumptions
- energyDynamics: Modelica.Fluid.Types.Dynamics.FixedInitial
- massDynamics: Modelica.Fluid.Types.Dynamics.FixedInitial
- Initialization
- p_start: 3*101.325*1000 [Pa]
- V_l_start: 0.5 [m3]
- VaporValve
- m_flow_nominal: 1.0 [kg/s]
- dp_nominal: 100*1000 [Pa]
- AtmosphericBoundary
- p: 1*101.325*1000 [Pa]
- fracLiquid
- k: 1 / evaporator.V_t
- ctrl_pi
- k: 300
- T: 30 [s]
- ramp_r_liquidLevel
- height: 0
- duration: 100 [s]
- offset: 0.4
- startTime: 100 [s]
- ramp_Q_flow_in
- height: 1e6
- duration: 100 [s]
- offset: 1e6
- startTime: 100 [s]
- ramp_valveopen
- height: 0.0
- duration: 10 [s]
- offset: 1
- startTime: 300 [s]
boiler筐体の質量.熱慣性に効く.熱慣性の効果は無い方が挙動が理解しやすいので小さい値を設定しておく.
boiler筐体の熱容量.熱慣性に効く.m_Dと同様に大き過ぎない適当な値を設定しておく.
boiler内部空間容積.
液相容積の初期値.収束性を上げ,静定状態を速く得られるように指定する.
現実的・妥当な値を与えられそうであれば,固定初期値を用いておく.例えば,本例では,水を沸騰させるので,圧力が数気圧程度,温度が100[degC]前後で妥当という事は想像出来る.下手にFreeにしてしまうと,求解の為の余分な処理が生じ,場合に依ってはinitializationで収束失敗になったりする.
boiler内圧力の初期値.
液相体積初期値.
P制御とI制御両方に係るゲイン.これを大きく設定するとP,I両方の制御が強くなる.
I制御の分母に位置する係数.即ちI制御単体のゲインはk/T.時定数という名と,時間の次元を持つが,深く意識する必要は無い.これを大きく設定する程,I制御が弱くなる事を認識出来ていれば使う事が出来る.
evaporatorへの熱量入力指示であり,本例題で,inputとして操作するもの.time=100 – 200 [s] で投入熱量を2倍に増加させる.
ソースコード
上記までを行うとOpenModelicaがソースコードを自動生成してくれている。一部ソースコードを直接書かなければならない箇所が有るのと、エラーが生じた場合の比較のために、本モデルの完成状態のソースコードを示す。尚、ソースコードを直書きする作業が残っているが、その解説はソースコードの後に記載する。
完成状態のソースコード(チェック用)
コンパイルエラーや、計算結果が意図通りのものとならないような場合に比較・チェック用に参照して欲しい。尚、annotation()の部分はGUI上の配置位置や向きで値が変わるものなので、比較する必要は無い。
——————————————————————————-
within WalkingInWorldOfThermoFluid.Normal; model SteamGenerator_ex01 "Complete drum boiler model, including evaporator and supplementary components" extends Modelica.Icons.Example; parameter Boolean use_inputs = false "use external inputs instead of test data contained internally"; Modelica.Fluid.Examples.DrumBoiler.BaseClasses.EquilibriumDrumBoiler evaporator(redeclare package Medium = Modelica.Media.Water.StandardWater , V_l(fixed = true), V_l_start = 0.5, V_t = 1, cp_D = 500, energyDynamics = Modelica.Fluid.Types.Dynamics.FixedInitial, m_D = 1e-6, massDynamics = Modelica.Fluid.Types.Dynamics.FixedInitial, p(fixed = false, start = 101.325 * 1000), p_start = 3 * 101.325 * 1000) annotation( Placement(transformation(extent = {{-46, -30}, {-26, -10}}))); Modelica.Thermal.HeatTransfer.Sources.PrescribedHeatFlow heatSupply annotation( Placement(transformation(origin = {-36, -53}, extent = {{-10, -10}, {10, 10}}, rotation = 90))); Modelica.Fluid.Sensors.MassFlowRate massFlowVapor(redeclare package Medium = Modelica.Media.Water.StandardWater ) annotation( Placement(transformation(origin = {30, -20}, extent = {{10, 10}, {-10, -10}}, rotation = 180))); Modelica.Fluid.Sensors.Temperature T_evaporatorOutlet(redeclare package Medium = Modelica.Media.Water.StandardWater ) annotation( Placement(transformation(origin = {-3, -1}, extent = {{10, 10}, {-10, -10}}, rotation = 180))); Modelica.Fluid.Sensors.Pressure p_evaporatorOutlet(redeclare package Medium = Modelica.Media.Water.StandardWater ) annotation( Placement(transformation(extent = {{10, 18}, {30, 38}}))); Modelica.Fluid.Valves.ValveLinear VaporValve(redeclare package Medium = Modelica.Media.Water.StandardWater , dp_nominal = 100 * 1000, m_flow_nominal = 1) annotation( Placement(transformation(extent = {{50, -10}, {70, -30}}))); inner Modelica.Fluid.System system annotation( Placement(transformation(extent = {{-90, 70}, {-70, 90}}))); Modelica.Blocks.Sources.Ramp ramp_Q_flow_in(duration = 100, height = 1e6, offset = 1e6, startTime = 100) annotation( Placement(visible = true, transformation(origin = {-70, -70}, extent = {{-10, -10}, {10, 10}}, rotation = 0))); Modelica.Blocks.Sources.Ramp ramp_valveopen(duration = 10, height = -0.0, offset = 1, startTime = 300) annotation( Placement(visible = true, transformation(origin = {40, -60}, extent = {{-10, -10}, {10, 10}}, rotation = 0))); Modelica.Fluid.Sources.MassFlowSource_T LiquidSupplyPump(redeclare package Medium = Modelica.Media.Water.StandardWater , T = 15 + 273.15, m_flow = 1, nPorts = 1, use_m_flow_in = true) annotation( Placement(visible = true, transformation(origin = {-70, -20}, extent = {{-10, -10}, {10, 10}}, rotation = 0))); Modelica.Fluid.Sources.Boundary_pT AtmosphericBoundary(redeclare package Medium = Modelica.Media.Water.StandardWater , nPorts = 1, p = 1 * 101.325 * 1000) annotation( Placement(visible = true, transformation(origin = {90, -20}, extent = {{10, -10}, {-10, 10}}, rotation = 0))); Modelica.Blocks.Math.Gain fracLiquid(k = 1 / evaporator.V_t) annotation( Placement(visible = true, transformation(origin = {-32, 15}, extent = {{-5, -5}, {5, 5}}, rotation = 90))); Modelica.Blocks.Sources.Ramp ramp_r_liquidLevel(duration = 100, height = 0, offset = 0.4, startTime = 100) annotation( Placement(visible = true, transformation(origin = {-10, 70}, extent = {{-10, -10}, {10, 10}}, rotation = -90))); Modelica.Blocks.Continuous.PI ctrl_pi(T = 30, k = 300) annotation( Placement(visible = true, transformation(origin = {-70, 40}, extent = {{10, -10}, {-10, 10}}, rotation = 0))); Modelica.Blocks.Math.Feedback feedback annotation( Placement(visible = true, transformation(origin = {-32, 40}, extent = {{10, -10}, {-10, 10}}, rotation = 0))); equation connect(heatSupply.port, evaporator.heatPort) annotation( Line(points = {{-36, -43}, {-36, -30}}, color = {191, 0, 0})); connect(p_evaporatorOutlet.port, massFlowVapor.port_a) annotation( Line(points = {{20, 18}, {20, -20}}, color = {0, 127, 255})); connect(massFlowVapor.port_b, VaporValve.port_a) annotation( Line(points = {{40, -20}, {50, -20}}, color = {0, 127, 255})); connect(evaporator.port_b, massFlowVapor.port_a) annotation( Line(points = {{-26, -20}, {20, -20}}, color = {0, 127, 255})); connect(T_evaporatorOutlet.port, massFlowVapor.port_a) annotation( Line(points = {{-3, -11}, {-3, -20}, {20, -20}}, color = {0, 127, 255})); connect(ramp_Q_flow_in.y, heatSupply.Q_flow) annotation( Line(points = {{-58, -70}, {-36, -70}, {-36, -62}}, color = {0, 0, 127})); connect(ramp_valveopen.y, VaporValve.opening) annotation( Line(points = {{52, -60}, {60, -60}, {60, -28}}, color = {0, 0, 127})); connect(VaporValve.port_b, AtmosphericBoundary.ports[1]) annotation( Line(points = {{70, -20}, {80, -20}}, color = {0, 127, 255})); connect(LiquidSupplyPump.ports[1], evaporator.port_a) annotation( Line(points = {{-60, -20}, {-46, -20}}, color = {0, 127, 255})); connect(evaporator.V, fracLiquid.u) annotation( Line(points = {{-32, -8}, {-32, 10}}, color = {0, 0, 127})); connect(fracLiquid.y, feedback.u2) annotation( Line(points = {{-32, 20}, {-32, 32}}, color = {0, 0, 127})); connect(ramp_r_liquidLevel.y, feedback.u1) annotation( Line(points = {{-10, 60}, {-10, 40}, {-24, 40}}, color = {0, 0, 127})); connect(ctrl_pi.u, feedback.y) annotation( Line(points = {{-58, 40}, {-40, 40}}, color = {0, 0, 127})); connect(ctrl_pi.y, LiquidSupplyPump.m_flow_in) annotation( Line(points = {{-80, 40}, {-96, 40}, {-96, -12}, {-80, -12}}, color = {0, 0, 127})); annotation( Icon(coordinateSystem(preserveAspectRatio = false, extent = {{-100, -100}, {100, 100}}), graphics = {Text(lineColor = {0, 0, 255}, extent = {{-151, 165}, {138, 102}}, textString = "%name"), Text(extent = {{-79, 67}, {67, 21}}, textString = "drum"), Text(extent = {{-90, -14}, {88, -64}}, textString = "boiler")}), experiment(StopTime = 400, StartTime = 0, Tolerance = 1e-06, Interval = 0.01), Documentation(info = " "), Diagram(coordinateSystem(extent = {{-140, -100}, {100, 100}}))); end SteamGenerator_ex01;
——————————————————————————-
mediumのredeclare; 黄色ハイライト部
*OpenModelica ver 1.16.0 以降ではparameterウィンドウから設定出来るようになっている。しかし,その機能を有効化すると動作が非常に遅くなるため,本連載ではmediumのdeclareはコードを直接書いて行うスタイルを推奨する.
OpenModelica の表示を、text viewに切り替える。そして、モデルソースコード内に、前項に示したソースコード中で黄色ハイライトで示されている記述を直接書き足す。これらの記述で、使用する物性モデル(本モデルでは水であること)を指定する。
シミュレーション実行&結果評価
それでは、観たいvariableの値や動きを評価してゆこう。
- evaporator出入口質量流量
- evaporator内の水質量(気液合計)
- evaporator内の気液体積
- evaporator内流体圧力
- evaporator内流体温度
Input
inputsとして与えた(変化させた)値が意図通りvariablesに反映されているか確認。
Output
massFlowVapor.m_flow(赤色):蒸気の流出
evaporator.port_a.m_flow(青色):水の流入
初期状態から約100 [s] で静定に至るまでの過渡状態は触れないでおき,投入熱量増加操作に対する挙動に絞って観る.
熱量増加に従って蒸気流出が増加しする.これは熱量を増したら蒸気発生が増えるので当然の挙動.同時に,水流入量は蒸気流出量増加に対して増加が遅れ,熱量増加を止めてから一度オーバーシュートを経て静定,蒸気流出量と釣り合う.この動きはフィードバック制御を挟んでいるために起きるもので,水流入量が制御で操作される変数である以上自然なものだ.
t=100 [s] 以降の動きを見ると,熱量増加に合わせて減少している.熱量増加中は蒸気流出が水流入を上回るので当然の動き.そして,t=200 [s] 以降の静定後も熱量増加前より質量が小さいままだが,これはevaporator内の平衡温度が上昇して,密度が低下するため.
初期状態からの過渡を除き,ほぼ液相40%を保てている.熱量増加中も40%からの逸脱は殆ど観られない.
熱量増加に伴って,evaporator内の圧力が上昇する.これは同時にevaporator出口から取り出される蒸気の圧力も上昇している事になり,加熱するほど,より高圧の”膨張するポテンシャルが高い”蒸気を生成できる事を意味している.
圧力の増加に伴って沸点が上昇する.これは高山では水が低い温度で沸騰するといった身近な例もあり,感覚的にも理解しやすいだろう.
最後に
今回,液体から気体への相変化に対応したコンポーネントの扱い方を学習した.これにより,水・蒸気が混ざった熱サイクルやシステムのモデルを作れるスキルを得た事になる.今回は流体に水を使ったが,気液両方のデータを持つフロンのMediumモデルが有れば,冷蔵庫や空調機のシステムモデルへと手を拡げられる.
是非,気液混合システムのモデリングに手を伸ばして見て欲しい.
本例題は以上。
例モデルに関する情報
本記事で取り上げたモデルは専用の「WalkingInWorldOfThermoFluid」ライブラリに公開しており、その情報を記しておく。ソースコードは本記事にも記載したが、直接OpenModelicaで読み込みたい方はダウンロードしてGPL3の範囲内で自由に使っていただいて構わない。
- モデルのフルパス:
- githubのライブラリページリンク
以上
コメント