Unit クラスの継承(1)

バイオ・リファイナリー(再生可能資源であるバイオマスを原料にバイオ燃料や樹脂などを製造するプラントや技術)のシミュレーションソフト"BioSTEAM"で、プロセス・パラメータを動的に変更する方法について説明しています。 オリジナルのページはInheriting from Unitです。 ソースコードは以下の実行環境で確認しています。
  • Visual Studio Code バージョン: 1.104.2
  • 拡張機能:Jupyter バージョン 2025.8.0
  • Python 3.12.10
  • biosteam 2.52.13
  • graphviz-14.0.2
<< 目次 >>

Unit クラスの継承

共通の設定

サブクラスの例

シミュレーション テスト

Graphvizで作図

装置のコスト計算

付随設備

参考文献

<< 本文 >>

Unit クラスの継承

共通の設定

Unit クラスを継承する各機器のモデル(サブクラス)は、以下のクラス変数を持ち、特に指定のない時のデフォルトの値が決められています。

  • _F_BM_default : [str, float]の組み合わせの辞書型配列で、設置に関するコストを見積るための係数のデフォルト値。
  • _units :[str, float]の組み合わせの辞書型配列で、計算結果である辞書型配列 design_results の値の単位。
  • _N_ins =1:[int]流入ストリームの数。デフォルトは1。
  • _N_outs =1:[int]流出ストリームの数。デフォルトは1。
  • _ins_size_is_fixed =True:[bool]流入ストリームの数が固定値かどうか。デフォルトはTrue。
  • _out_size_is_fixed =True:[bool]流出ストリームの数が固定値かどうか。デフォルトはTrue。
  • auxiliary_unit_names =():tuple[str]補助装置の名前。
  • _graphics =():[biosteam Graphics]機器を表す図のグラフィック オブジェクト。デフォルトは四角。
  • _default_equipment_lifetime =():[int] または [str, int]の組み合わせの辞書型配列 装置の寿命のデフォルト値。生産事業の存続期間。
  • _line =():[str] 機器を図にするときのラベル。デフォルトはその機器のクラス名。

各機器の熱や物質収支、コストを計算するための抽象メソッドが設定されています。

  • _run() システムの収束計算時に呼び出され、流出ストリームを決定します。
  • _design() :システムの収束後にに呼び出され、設計要求を決定します。
  • _cost() :_design()実行後に呼び出され、コストを決定します。

以下の抽象メソッドは、それぞれのインスタンス変数によって設定されます。

  • ins [Stream] 流入ストリーム。
  • outs:[Stream] 流出ストリーム。
  • power_utilities:[Stream] 電力消費量。
  • heat_utilities:list[Stream] 加熱、冷却要求。
  • baseline_purchase_costs : dict [str, float] 設計、圧力、材料の係数を考慮する前の購入価格(ベースライン購入価格)の一覧。
  • parallel: dict [str, int] 並列で配置するパーツの名前と個数。並列で設置されるパーツがない時は{}
  • F_BM : dict [str, float] 設備コスト計算時の設置に関する係数。
  • F_D : dict [str, float] 設備コスト計算時の設計の係数。
  • F_P : dict [str, float] 設備コスト計算時の圧力の係数。
  • F_M : dict [str, float] 設備コスト計算時の材料の係数。
  • equipment_lifetime:[dict] 各パーツの寿命。
  • thermo:[Thermo] 機器で使用される成分の熱力学的特性

サブクラスの例

次の例では、新しい Boiler クラスを作成することで Unit クラスから継承する変数をどう設定するか示しています。

  1. 新しい Boiler クラスを作成
  2. import biosteam as bst
    from math import ceil
    bst.nbtutorial()
    
    class Boiler(bst.Unit):
        """
        供給ストリームの一部を沸騰させるボイラー オブジェクトを生成
    
        Parameters
        ----------
        ins :
            供給ストリーム
        outs :
            * [0] 蒸気
            * [1] 液体
        V : float
            モル蒸気分率
        P : float
            操作圧力 [Pa].
    
        """
        # ここでは`ID` や `thermo`パラメータを設定していませんが
        # BioSTEAMのほとんどのサブクラスはこのように記載されていて、問題ありません。
        # 全ての機器ユニットはインデックス付きのリスト化された流入/流出ストリームを指定しますが、
        # 流入ストリームもしくは流出ストリームが1つの場合は、リスト化する必要はありません。
        # `ins` や `outs`を書く必要もありません。
        # BioSTEAM はしい型を自動的に追加します。
        # 機器に対する追加の引数も列挙する必要があります(例:V や P)。
    
        _N_ins = 1 # 流入ストリームの数
        _N_outs = 2 # 流出ストリームの数
        _units = {'Area': 'm^2'}
    
        def _init(self, V, P):
            # _init メソッドで 新規作成時に指定された引数が追加されます。
            self.V = V #: モル蒸気分率
            self.P = P #: 操作圧力 [Pa].
    
        def _run(self):
            # 流入ストリームの数が1つのときは、self.ins[0] と等価。
            feed = self.feed
            vap, liq = self.outs
    
            # 気液平衡計算を実施
            stream = feed.copy()
            stream.vle(V=self.V, P=self.P)
    
            # 流出ストリームを更新します。
            vap.copy_like(stream['g'])
            liq.copy_like(stream['l'])
    
        def _design(self):
            # 加熱要求を出入口のエンタルピー差から計算します。
            T_operation = self.outs[0].T
            duty = self.H_out - self.H_in
            if duty < 0: raise RuntimeError(f'{repr(self)} is cooling.')
            heat_utility = self.add_heat_utility(duty, T_operation) # 新しい加熱要求を self.heat_utilities に設定
    
            # 熱交換器の入口温度の設定
            T_utility = heat_utility.inlet_utility_stream.T
    
            # 熱交換器の温度差の設定
            dT = T_utility - T_operation
    
            # 熱伝達係数 kJ/(hr*m2*K)
            U = 8176.699
    
            # 必要な熱交換面積 (m^2) = 要求熱量 / (熱伝達係数 * 出入口温度差)
            A = duty /(U * dT)
    
            # 1つの熱交換器の熱交換面積の上限
            A_max = 743.224
    
            # 必要な熱交換機の数 = A / A_max
            N = ceil(A / A_max)
    
            # 設計要求に計算結果を保存
            self.design_results['Area'] = A / N
    
            # ボイラー全体の設備費は、1つのボイラーの費用にボイラーの台数を掛けたものとします。
            self.parallel['Boiler'] = N
    
        def _cost(self):
            A = self.design_results['Area']
    
            # 長管式縦型ボイラーのコストは以下を参照しています。
            # "Product process and design". Warren et. al. (2016) Table 22.32, pg 592
            purchase_cost = bst.settings.CEPCI * 3.086 * A **0.55
    
            # ベースライン購入価格として保存
            self.baseline_purchase_costs['Boiler'] = purchase_cost # 材質の係数は未考慮
    
            # 設計、圧力、材質による係数は1とします。
            self.F_D['Boiler'] = self.F_P['Boiler'] = self.F_M['Boiler'] = 1.
    
            # ボイラーの設置に関する係数を使用します。
            self.F_BM['Boiler'] = 2.45

シミュレーション テスト

  1. オブジェクトの生成
  2. import biosteam as bst
    bst.settings.set_thermo(['Water'])
    water = bst.Stream('water', Water=300)
    B1 = Boiler('B1', ins=water, outs=('gas', 'liq'),
                V=0.5, P=101325)
    B1.diagram(format='png')
    B1.show()
    Boiler: B1
    ins...
    [0] water  
        phase: 'l', T: 298.15 K, P: 101325 Pa
        flow (kmol/hr): Water  300
    outs...
    [0] gas  
        phase: 'l', T: 298.15 K, P: 101325 Pa
        flow: 0
    [1] liq  
        phase: 'l', T: 298.15 K, P: 101325 Pa
        flow: 0
  3. シミュレーションの実行
  4. B1.simulate()
    B1.show()
    Boiler: B1
    ins...
    [0] water  
        phase: 'l', T: 298.15 K, P: 101325 Pa
        flow (kmol/hr): Water  300
    outs...
    [0] gas  
        phase: 'g', T: 373.12 K, P: 101325 Pa
        flow (kmol/hr): Water  150
    [1] liq  
        phase: 'l', T: 373.12 K, P: 101325 Pa
        flow (kmol/hr): Water  150
  5. 結果の確認
  6. ユーティリティの要求は`design_results`に辞書型配列として保存されます。購入価格は自動的に結果一覧に追加されます。
    B1.results()
    Boiler Units B1
    Low pressure steam Duty kJ/hr 8.21e+06
    Flow kmol/hr 212
    Cost USD/hr 50.4
    Design Area m^2 24.4
    Purchase cost Boiler USD 1.02e+04
    Total purchase cost USD 1.02e+04
    Utility cost USD 50.4
  7. 供給量の変更
  8. プラントの規模が大きくなった時には、ボイラーの個数は辞書型配列の`parallel`を使って自動的に個数が増えます。

    B1.feed.scale(100) # 供給流量を 100倍にします。
    B1.simulate()
    B1.results()
    Boiler Units B1
    Low pressure steam Duty kJ/hr 8.21e+08
    Flow kmol/hr 2.12e+04
    Cost USD/hr 5.04e+03
    Design Area m^2 610
    Purchase cost Boiler (x4) USD 2.38e+05
    Total purchase cost USD 2.38e+05
    Utility cost USD 5.04e+03
    ここで、
    B1.parallel
    {'Boiler': 4}
    となります。

Graphvizで作図

Graphvizで作られた図は_graphicsにグラフィック オブジェクトとして保存されています。 グラフィック オブジェクトはそれぞれの機器 サブクラスに一つ作成されます。

  1. 作図情報
  2. graphics = Boiler._graphics
    edge_in = graphics.edge_in
    edge_out = graphics.edge_out
    node = graphics.node
  3. エッジ イン
  4. 流入ストリーム一つにつき、一つのエッジ インが生成されます。例えば、B1.ins[0]にはedge_in[0]が対応して生成されます。

    edge_in
    [{'headport': 'c'}]
  5. エッジ アウト
  6. 流出ストリーム一つにつき、一つのエッジ アウトが生成されます。例えば、B1.outs[0]にはedge_outs[0]が対応して生成されます。

    edge_out
    [{'tailport': 'c'}, {'tailport': 'c'}]
  7. ノード
  8. ノードが機器に対応します。

    node
    {'shape': 'box',
     'style': 'filled',
     'gradientangle': '0',
     'width': '0.6',
     'height': '0.6',
     'orientation': '0.0',
     'peripheries': '1',
     'margin': 'default',
     'fontname': 'Arial'}
  9. デフォルトの設定での図
  10. B1.diagram(format='png')
  11. 設定の変更
  12. 図の設定はユーザーの好みに応じて変更できます。

    edge_out[0]['tailport'] = 'n'
    edge_out[1]['tailport'] = 's'
    node['width'] = '1'
    node['height'] = '1.2'
    
    B1.diagram(format='png')
  13. 設定の動的な変更
  14. 図の設定はtailor_node_to_unitの設定をすることで動的に変更できます。以下の例では、ストリームの流量がゼロの時に機器内に-empty-という文字が追加されるようにしています。

    def tailor_node_to_unit(node, unit):
        if unit.feed.isempty(): node['label'] += '\n-empty-'
    graphics.tailor_node_to_unit = tailor_node_to_unit
    B1.ins[0].empty()
    B1.diagram(format='png')

    注意:tailor_node_to_unit関数のサンプル実装は推奨されません。図はシンプルに保つのが最適です。

このブログの人気の投稿

さあ、始めよう!

蒸留塔

機器ユニットの計算結果