プロセス・パラメータの動的変更
- Visual Studio Code バージョン: 1.104.2
- 拡張機能:Jupyter バージョン 2025.8.0
- Python 3.12.10
- biosteam 2.52.13
- graphviz-14.0.2
プロセス・パラメータの動的変更
仕様を満たすためにプロセスのパラメータを動的に調整することは、生産プロセスを設計する上で極めて重要であり、特に不確実な条件がある場合ではなおさら重要です。 BioSTEAM はプロセス仕様を、分析的仕様と数値解析的仕様の 2 つのカテゴリーに分類しています。 分析的仕様とはシステムの単一ループ内で解くことが出来るもの、 一方、数値解析的仕様は、機器ユニットを繰り返し実行したり、リサイクル系を収束させて解く必要があるものとしています。 以下の実例によって、この点が詳しく説明されます。
分析的に求める仕様
バイオ・エタノール製造プラントでの変性剤投入工程
バイオエタノールの流量に応じて添加する変性剤の量を、出来上がり時に変性剤の比率が質量比で2%になるように調整します。
- 仕様を満たすための関数(specification function)
- 仕様リスト 全ての仕様(specifications)は、機器ユニットの仕様リストを見ることで確認できます。
- 仕様追加関数に引数を渡す
impacted_units
で影響を受ける機器を指示- 熱交換器H1の流量
impacted_units
なし- 熱交換器H1の流量
- 貯蔵タンク T1に仕様追加関数を設定
- 数値解析的仕様変更(add_bounded_numerical_specification)
from biosteam import settings, Chemical, Stream, units, main_flowsheet
import biosteam as bst; bst.nbtutorial()
# フローシートに名前を付けます
main_flowsheet.set_flowsheet('mix_ethanol_with_denaturant')
# 使用する成分の熱力学特性を設定します
# 実際のプロセスではもっと多くの物質を扱うと思いますが、
# ここでは例として少ない種類で試行します。
settings.set_thermo(['Water', 'Ethanol', 'Octane'])
# 330日の操業で年間 40 ミリオン ガロンのエタノール生産されているとします。
dehydrated_ethanol = Stream('dehydrated_ethanol', T=340,
Water=0.1, Ethanol=99.9, units='kg/hr')
operating_days_per_year = 330
dehydrated_ethanol.set_total_flow(40e6 / operating_days_per_year, 'gal/d')
denaturant = Stream('denaturant', Octane=1)
M1 = units.Mixer('M1', ins=(dehydrated_ethanol, denaturant), outs='denatured_ethanol')
# 仕様を満たすための関数(specification function)を設定します。
# 変性剤が質量比で2%になるように設定します。
@M1.add_specification(run=True) # 適用後に物質収支およびエネルギー収支を計算します。
def adjust_denaturant_flow():
denaturant_over_ethanol_flow = 0.02 / 0.98 # A mass ratio
denaturant.imass['Octane'] = denaturant_over_ethanol_flow * dehydrated_ethanol.F_mass
# 実行と結果の確認
M1.simulate()
M1.show(composition=True, flow='kg/hr')
Mixer: M1
ins...
[0] dehydrated_ethanol
phase: 'l', T: 340 K, P: 101325 Pa
composition (%): Water 0.1
Ethanol 99.9
------- 1.43e+04 kg/hr
[1] denaturant
phase: 'l', T: 298.15 K, P: 101325 Pa
composition (%): Octane 100
------ 292 kg/hr
outs...
[0] denatured_ethanol
phase: 'l', T: 339.31 K, P: 101325 Pa
composition (%): Water 0.098
Ethanol 97.9
Octane 2
------- 1.46e+04 kg/hr
outs[0]のOctaneの比率が質量比で2%になっていることが確認できます。混合後の体積流量は、
M1.outs[0].get_total_flow('gal/d') * operating_days_per_year / 1e6
40.88351940635338年間 約40.9 ミリオンガロンになりました。
ちなみに、この@~
はPythonのデコレータ構文[1]
というもので、直後に書いた関数を元の関数の引数として実行してくれるもの(?)だそうです。
M1.specifications
[ProcessSpecification(f=adjust_denaturant_flow(), args=(), impacted_units=())]
argsと
impacted_initsについては次の
corn slurryの節で説明します。
従来型乾式粉砕工程におけるコーンスラリーの作成
従来型の乾式粉砕トウモロコシをエタノール製造プラントに供給する際のコーンスラリーの固形分濃度は、通常約 32 wt.% である。粉砕トウモロコシに混合する水の流量を調整し、スラリーの固形分濃度が 32 wt.% となるようにせよ。
# 新しいフローシートに名前を付ける
main_flowsheet.set_flowsheet('corn_slurry_example')
# コーンの代表的な化学成分を設定
Starch = Chemical.blank('Starch', phase='s') # でんぷん(固体)
Fiber = Chemical.blank('Fiber', phase='s') # 繊維成分(固体)
Oil = Chemical('Oil', search_ID='Oleic_acid') # オレイン酸
Water = Chemical('Water')
# 実際の特性は今回は不要なので、デフォルトの物性値を設定(25℃、1atm)
Starch.default()
Fiber.default()
# 熱力学特性もセット。
# 実際の工程ではもっと多くの成分が関与するが、今回は代表的なもののみとする
settings.set_thermo([Starch, Oil, Fiber, Water])
# 代表的な乾式粉砕法では年間年間 4,000万ガロン(40,000,000 gal)のエタノールを生産し、
# トウモロコシ 1 ブッシェル当たり 2.7 ガロンのエタノール収率が得られる
corn_flow_per_year = 40e6 / 2.7 # ブッシェル/年
days_per_year = 365
operating_days_per_year = 330
corn_flow_per_day = corn_flow_per_year * days_per_year / operating_days_per_year
# トウモロコシの粒の成分は、でんぷん(62%)、タンパク質と繊維(19%)、水分(15%)、
# 油分(4%)とします
corn_feed = Stream('corn_feed',
Starch=62, Fiber=19, Water=15, Oil=4,
total_flow=corn_flow_per_day,
units='bu/day', # bushel per day
)
T1 = bst.StorageTank('T1', corn_feed)
# 粉砕したトウモロコシの粉をスラリーにするために加える水(希釈水)を設定
dilution_water = Stream('dilution_water', Water=1)
M1 = units.Mixer('M1', ins=(dilution_water, T1-0), outs='slurry')
@M1.add_specification(
run=True, # 仕様追加関数の実行後に物質収支およびエネルギー収支を計算
args=[0.32], # `adjust_water_flow`関数に引数を渡す
)
def adjust_water_flow(solids_content):
F_mass_moisture = corn_feed.imass['Water']
F_mass_solids = corn_feed.F_mass - F_mass_moisture
water_solids_ratio = (1 - solids_content) / solids_content
dilution_water.imass['Water'] = F_mass_solids * water_solids_ratio - F_mass_moisture
# 実行して結果を確認
corn_slurry_sys = main_flowsheet.create_system('corn_slurry_sys')
corn_slurry_sys.simulate()
M1.show(flow='kg/hr', composition=True)
Mixer: M1
ins...
[0] dilution_water
phase: 'l', T: 298.15 K, P: 101325 Pa
composition (%): Water 100
----- 3.97e+07 kg/hr
[1] s6 from StorageTank-T1
phase: 'l', T: 298.15 K, P: 101325 Pa
composition (%): Starch 62.2
Oil 3.72
Fiber 19.1
Water 15
------ 2.4e+07 kg/hr
outs...
[0] slurry
phase: 'l', T: 298.15 K, P: 101325 Pa
composition (%): Starch 23.4
Oil 1.4
Fiber 7.18
Water 68
------ 6.37e+07 kg/hr
outs[0] slurry の水分が68%、つまり残りの水分以外の比率が32%となっていることが確認できます。add_specification()関数を使うことで、dilution_waterの流量を動的に変化させていますが、引数も渡すことで、units.Mixer()を変更することなく、比率も状況に応じて変えることが出来ました。
希釈水を混合する前に加熱したいと思います。希釈水の流量が混合器で指定されると、その上流の熱交換器やポンプにも影響が及びます。流量決定後に直接影響を受けるこれらの機器が再計算されるようにimpacted_units
で指示することが出来ます。
# ポンプと熱交換器を追加します。
P1 = units.Pump('P1', dilution_water, P=5 * 101325)
H1 = units.HXutility('H1', P1-0, T=350)
M1.ins.append(H1-0)
M1.specifications.clear() # 一旦、仕様追加関数を削除
# "impacted units"を追加した、仕様追加関数を作成
M1.add_specification(
adjust_water_flow, # 仕様追加関数
args=[0.32], # 関数に渡す引数
run=True, # run=True, # 仕様追加関数の実行後に物質収支およびエネルギー収支を計算
impacted_units=[P1], # 希釈水(Dilution water)がポンプP1に接続されている
# BioSTEAMは、影響を受ける機器P1と仕様を追加した機器(M1)の間にある機器も考慮します
)
# 以下のような指示の仕方も可能
# M1.specifications[0].impacted_units = [P1]
corn_slurry_sys = main_flowsheet.create_system('corn_slurry_sys')
dilution_water.empty() # 計算を実行する前にリセット
corn_slurry_sys.simulate()
corn_slurry_sys.diagram(format='png', kind='cluster', number=True)
corn_slurry_sys.show()
System: corn_slurry_sys
ins...
[0] -
phase: 'l', T: 298.15 K, P: 101325 Pa
flow: 0
[1] dilution_water
phase: 'l', T: 298.15 K, P: 101325 Pa
flow (kmol/hr): Water 2.2e+06
[2] corn_feed
phase: 'l', T: 298.15 K, P: 101325 Pa
flow (kmol/hr): Starch 1.49e+07
Oil 3.15e+03
Fiber 4.57e+06
Water 2e+05
outs...
[0] slurry
phase: 'l', T: 330.74 K, P: 101325 Pa
flow (kmol/hr): Starch 1.49e+07
Oil 3.15e+03
Fiber 4.57e+06
Water 2.4e+06
outs[0] slurry の水分が希釈水 dilution_water 分増えて、2.4e+06 kmol/hrになっています。指示通り、熱交換器H1の流量がセットされているか確認します。
H1.show()
HXutility: H1
ins...
[0] s2 from Pump-P1
phase: 'l', T: 298.15 K, P: 506625 Pa
flow (kmol/hr): Water 2.2e+06
outs...
[0] s3 to Mixer-M1
phase: 'l', T: 350 K, P: 506625 Pa
flow (kmol/hr): Water 2.2e+06
希釈水 dilution_water と同じ流量のストリームが流入して流出しています。では、impacted_unitsを設定しないとどうなるか試してみます。
M1.specifications.clear() # 一旦、仕様追加関数を削除
# "impacted units"を追加した、仕様追加関数を作成
M1.add_specification(
adjust_water_flow, # 仕様追加関数
args=[0.32], # 関数に渡す引数
run=True, # run=True, # 仕様追加関数の実行後に物質収支およびエネルギー収支を計算
# BioSTEAMは、影響を受ける機器P1と仕様を追加した機器(M1)の間にある機器も考慮します
)
# 以下のような指示の仕方も可能
# M1.specifications[0].impacted_units = [P1]
corn_slurry_sys = main_flowsheet.create_system('corn_slurry_sys')
dilution_water.empty() # 計算を実行する前にリセット
corn_slurry_sys.simulate()
corn_slurry_sys.show()
System: corn_slurry_sys
ins...
[0] -
phase: 'l', T: 298.15 K, P: 101325 Pa
flow: 0
[1] dilution_water
phase: 'l', T: 298.15 K, P: 101325 Pa
flow (kmol/hr): Water 2.2e+06
[2] corn_feed
phase: 'l', T: 298.15 K, P: 101325 Pa
flow (kmol/hr): Starch 1.49e+07
Oil 3.15e+03
Fiber 4.57e+06
Water 2e+05
outs...
[0] slurry
phase: 'l', T: 298.15 K, P: 101325 Pa
flow (kmol/hr): Starch 1.49e+07
Oil 3.15e+03
Fiber 4.57e+06
Water 2e+05
希釈水dilution_waterの流量は計算されていますが、流出ストリームは流入ストリームのcorn_feedそのままになっています。熱交換器H1の流量を確認します。
H1.show()
HXutility: H1
ins...
[0] s2 from Pump-P1
phase: 'l', T: 298.15 K, P: 506625 Pa
flow: 0
outs...
[0] s3 to Mixer-M1
phase: 'l', T: 350 K, P: 506625 Pa
flow: 0
熱交換器H1を通る経路が再計算されていないため、流量がゼロになっています。impacted_unitsの設定をする、しないでどのような違いが発生するか、確認できました。
混合器 M1ではなく、貯蔵タンク T1に仕様追加関数を設定してみます。
ポンプ P1 と 熱交換器 H1 は並列であり、上流ではないため、もし P1 が T1 より先に実行されると、シミュレーションの順序に問題が生じる可能性がある、と思われるかもしれませんが、impacted_units
を設定をすることで、BioSTEAMはシミュレーションの優先順位を判断することが出来ます。
M1.specifications.clear() # Remove specifications
# Create specification with impacted units.
T1.add_specification(
adjust_water_flow, # Specification function
args=[0.32], # Arguments passed to function.
run=True, # Run mass and energy balance after specification function.
impacted_units=[P1], # Dilution water is connected to P1.
)
corn_slurry_sys = main_flowsheet.create_system('corn_slurry_sys')
corn_slurry_sys.simulate()
corn_slurry_sys.show()
System: corn_slurry_sys
ins...
[0] -
phase: 'l', T: 298.15 K, P: 101325 Pa
flow: 0
[1] dilution_water
phase: 'l', T: 298.15 K, P: 101325 Pa
flow (kmol/hr): Water 2.2e+06
[2] corn_feed
phase: 'l', T: 298.15 K, P: 101325 Pa
flow (kmol/hr): Starch 1.49e+07
Oil 3.15e+03
Fiber 4.57e+06
Water 2e+05
outs...
[0] slurry
phase: 'l', T: 330.74 K, P: 101325 Pa
flow (kmol/hr): Starch 1.49e+07
Oil 3.15e+03
Fiber 4.57e+06
Water 2.4e+06
outs[0] slurry の水分が希釈水 dilution_water 分増えて、2.4e+06 kmol/hrになっていて、問題なさそうです。
数値解析的に求める仕様
フラッシュ蒸留器の計算
エタノールとプロパノールの混合液を、モル分率ではなく質量分率で50%蒸発させます。これは温度を変化させたときの蒸発率を繰り返し計算し、蒸気の質量と液体の質量が50%になる温度を数値解析的に計算することで解くことが出来ます。このとき、目的関数 f(x) = 0 となる x を求める解き方なので、目標が満足する状態で計算した結果がゼロになるような関数を与えます。ここでは、今の蒸発率が0.5のときにゼロになる → 蒸発率V - 0.5 を返す関数をセットします。
# 新しいフローシートに名前を付ける
main_flowsheet.set_flowsheet('flash_specification_example')
# 使用する成分の熱力学特性を設定
settings.set_thermo(['Water', 'Ethanol', 'Propanol'])
# 供給ストリーム
mixture = Stream('mixture', T=340,
Water=1000, Ethanol=1000, Propanol=1000,
units='kg/hr')
# フラッシュ蒸留器を設定
F1 = units.Flash('F1',
ins=mixture,
outs=('vapor', 'liquid'),
T=373, P=101325)
# 呼び出されたときに目的関数を解く数値解析的仕様変更関数を設定
@F1.add_bounded_numerical_specification(x0=351.4, x1=373, xtol=1e-9, ytol=1e-3)
def f(x):
# 目的関数 f(x) = 0 となる x を求める
# ここでは蒸発率 50 wt. % となる温度を求める
F1.T = x
F1.run() # 重要: 質量およびエネルギーバランスは新しい条件で計算
feed = F1.ins[0]
vapor = F1.outs[0]
V = vapor.F_mass / feed.F_mass
return V - 0.5
# Now create the system, simulate, and check results.
system = main_flowsheet.create_system()
system.simulate()
system.diagram(format='png')
system.show('cwt')
System: SYS2
ins...
[0] mixture
phase: 'l', T: 340 K, P: 101325 Pa
composition (%): Water 33.3
Ethanol 33.3
Propanol 33.3
-------- 3e+03 kg/hr
outs...
[0] vapor
phase: 'g', T: 357.66 K, P: 101325 Pa
composition (%): Water 28.3
Ethanol 40.7
Propanol 31.1
-------- 1.5e+03 kg/hr
[1] liquid
phase: 'l', T: 357.66 K, P: 101325 Pa
composition (%): Water 38.4
Ethanol 26
Propanol 35.6
-------- 1.5e+03 kg/hr
outs[0]の蒸気と、[1]の液体の質量流量が同じ(蒸気分率が50%)になり、温度が 357.66 K(84.51℃)と決まりました。
参考文献
-
[1]

