Source code for biosteam.units.tank

# -*- coding: utf-8 -*-
# BioSTEAM: The Biorefinery Simulation and Techno-Economic Analysis Modules
# Copyright (C) 2020-2023, Yoel Cortes-Pena <yoelcortes@gmail.com>
# 
# This module is under the UIUC open-source license. See 
# github.com/BioSTEAMDevelopmentGroup/biosteam/blob/master/LICENSE.txt
# for license details.
"""
.. contents:: :local:
    
.. autoclass:: biosteam.units.tank.Tank
.. autoclass:: biosteam.units.tank.MixTank
.. autoclass:: biosteam.units.tank.StorageTank

Factories
---------
.. autofunction:: biosteam.units.tank.tank_factory

References
----------
.. [1] Apostolakou, A. A., Kookos, I. K., Marazioti, C., Angelopoulos, K. C.
    (2009). Techno-economic analysis of a biodiesel production process from
    vegetable oils. Fuel Processing Technology, 90(7–8), 1023–1031.
    https://doi.org/10.1016/j.fuproc.2009.04.017
    
.. [2] Seider, W. D.; Lewin, D. R.; Seader, J. D.; Widagdo, S.; Gani, R.; 
    Ng, M. K. Cost Accounting and Capital Cost Estimation.
    In Product and Process Design Principles; Wiley, 2017; pp 426–485.

"""
from .design_tools.specification_factors import vessel_material_factors
from .design_tools.tank_design import (
    TankPurchaseCostAlgorithm,
    compute_number_of_tanks_and_purchase_cost,
    storage_tank_purchase_cost_algorithms,
    mix_tank_purchase_cost_algorithms)
from ..utils import ExponentialFunctor
from .._unit import Unit
from .mixing import Mixer

__all__ = ('Tank', 'MixTank', 'StorageTank', 'tank_factory')

# %% Factory functions

[docs] def tank_factory(name, *, CE, cost, S, tau, n=0.6, kW_per_m3=0., V_wf=0.9, V_min=0., V_max=1e6, V_units='m3', material='Carbon steel', vessel_type='Tank', BM=None, module=None, mixing=False): """ Return a Tank sublcass. Parameters ---------- name : str Name of subclass. CE : float Chemical plant cost index. cost : float Cost of tank. S : float Size of tank. tau : float Residence time [hr]. n : float, optional Scale up factor. The default is 0.6. kW_per_m3 : float, optional Electricity consumption per volume [kW/m3]. The default is 0.. V_wf : float, optional Fraction of working volume. The default is 0.9. V_min : float, optional Minimum tank volume. The default is 0.. V_max : float, optional Maximum tank volume. The default is 1e6. V_units : str, optional Volume units of measure. The default is 'm3'. material : str, optional Vessel material. The default is 'Carbon steel'. vessel_type : str, optional Name of vessel type. The default is 'Tank'. BM : float, optional Bare module factor. The default is 2.3 for all tanks. module : str, optional Module to implement class. mixing: bool, optional Whether multiple streams are mixed in the tank. Examples -------- >>> import biosteam as bst >>> Corn = bst.Chemical('Corn', search_db=False, default=True, phase='s') >>> bst.settings.set_thermo([Corn]) >>> CornStorage = bst.tank_factory('CornStorage', ... CE=525, cost=979300., S=185400, tau=259.2, n=1.0, V_wf=0.9, V_max=3e5, ... V_units='m3' ... ) >>> corn = bst.Stream('corn', Corn=46350.72444) >>> T101 = CornStorage('T101', corn, 'outlet') >>> T101.simulate() >>> T101.show() CornStorage: T101 ins... [0] corn phase: 'l', T: 298.15 K, P: 101325 Pa flow (kmol/hr): Corn 4.64e+04 outs... [0] outlet phase: 'l', T: 298.15 K, P: 101325 Pa flow (kmol/hr): Corn 4.64e+04 >>> T101.results() Corn storage Units T101 Design Residence time hr 259 Total volume m^3 1.33e+04 Purchase cost Tank USD 7.62e+04 Total purchase cost USD 7.62e+04 Utility cost USD/hr 0 """ dct = { 'purchase_cost_algorithms': { vessel_type: TankPurchaseCostAlgorithm( ExponentialFunctor(cost/S**n, n), V_min, V_max, V_units, CE, material ) }, '_default_vessel_type': vessel_type, '_default_tau': tau, '_default_V_wf': V_wf, '_default_vessel_material': material, '_default_kW_per_m3': kW_per_m3, } if BM: dct['_F_BM_default'] = {'Tank': BM} if mixing: dct['_ins_size_is_fixed'] = False dct['_N_ins'] = 2 dct['_run'] = Mixer._run dct['rigorous'] = False cls = type(name, (Tank,), dct) if module: cls.__module__ = module return cls
# %% Abstract tank class
[docs] class Tank(Unit, isabstract=True): r""" Abstract class for tanks. Attributes ---------- vessel_type : str Vessel type. tau : float Residence time [hr]. V_wf : float Fraction of working volume over total volume. vessel_material : str Vessel material. kW_per_m3 : float Electricity requirement per unit volume [kW/m^3]. Notes ----- The total volume [:math:`m^3`] is given by: :math:`V_{total} = \frac{\tau \cdot Q }{V_{wf}}` Where :math:`\tau` is the residence time [:math:`hr`], :math:`Q` is the flow rate [:math:`\frac{m^3}{hr}`], and :math:`V_{wf}` is the fraction of working volume over total volume. The number of tanks is given by: :math:`N = Ceil \left( \frac{V_{total}}{V_{max}} \right)` Where :math:`V_{max}` is the maximum volume of a tank depends on the vessel type. The volume [:math:`m^3`] of each tank is given by: :math:`V = \frac{V_{total}}{N}` The purchase cost will depend on the vessel type. Child classes should implement the following class attributes and methods: purchase_cost_algorithms : dict[str: VesselPurchaseCostAlgorithm] All purchase cost algorithms options for selected vessel types. """ _default_kW_per_m3 = 0. _units = {'Total volume': 'm^3', 'Residence time': 'hr'} _F_BM_default = {'Tank': 2.3} _N_outs = 1 def _init(self, vessel_type=None, tau=None, V_wf=None, vessel_material=None, kW_per_m3=0. ): # [str] Vessel type. self.vessel_type = vessel_type or self._default_vessel_type #: [float] Residence time in hours. self.tau = tau or self._default_tau #: [float] Fraction of working volume to total volume. self.V_wf = V_wf or self._default_V_wf #: [str] Vessel construction material. self.vessel_material = vessel_material or self._default_vessel_material # [float] Electricity requirement per unit volume [kW/m^3]. self.kW_per_m3 = kW_per_m3 or self._default_kW_per_m3 def __init_subclass__(cls, isabstract=False): if not isabstract: hasfield = hasattr if not hasfield(cls, 'purchase_cost_algorithms'): raise NotImplementedError("Tank subclass must implement " "a 'purchase_cost_algorithms' dictionary") attributes = ('_default_vessel_type', '_default_tau', '_default_V_wf', '_default_vessel_material') for i in attributes: if not hasfield(cls, i): raise NotImplementedError("Tank subclass must implement " "a '{i}' attribute") super().__init_subclass__(isabstract) @property def vessel_material(self): return self._vessel_material @vessel_material.setter def vessel_material(self, material): self._vessel_material = material default_material = self.purchase_cost_algorithm.material if material == default_material: self.F_M['Tank'] = vessel_material_factors.get(default_material, 1.) else: try: self.F_M['Tank'] = vessel_material_factors[material] except: raise ValueError("no material factor available for " f"vessel construction material '{material}';" "only the following materials are " f"available: {', '.join(vessel_material_factors)}") @property def vessel_type(self): return self._vessel_type @vessel_type.setter def vessel_type(self, vessel_type): if vessel_type in self.purchase_cost_algorithms: self._vessel_type = vessel_type self.purchase_cost_algorithm = self.purchase_cost_algorithms[vessel_type] else: raise ValueError(f"vessel type '{vessel_type}'" "is not avaiable; only the following vessel " "types are available: " f"{', '.join(self.purchase_cost_algorithms)}") def _design(self): design_results = self.design_results design_results['Residence time'] = tau = self.tau design_results['Total volume'] = tau * self.F_vol_out / self.V_wf def _cost(self): design_results = self.design_results V = design_results['Total volume'] N, Cp = compute_number_of_tanks_and_purchase_cost( V, self.purchase_cost_algorithm ) if N: self.parallel['self'] = N default_material = self.purchase_cost_algorithm.material self.baseline_purchase_costs['Tank'] = Cp / vessel_material_factors.get(default_material, 1.) self.add_power_utility(self.kW_per_m3 * V / N)
# %% Storage tank purchase costs
[docs] class StorageTank(Tank): r""" Create a storage tank with a purchase cost based on volume as given by residence time. Parameters ---------- ins : Inlet. outs : Outlet. vessel_type : str, optional Vessel type. Defaults to 'Field erected'. tau : float, optional Residence time [hr]. Defaults to 672. V_wf : float, optional Fraction of working volume over total volume. Defaults to 1. vessel_material : str, optional Vessel material. Defaults to 'Stainless steel'. kW_per_m3 : float, optional Electricity requirement per unit volume [kW/m^3]. Defaults to 0. Notes ----- For a detailed discussion on the design and cost algorithm, please read the :doc:`Tank` documentation. References to the purchase cost algorithms for each available vessel type are as follows: * Field erected: [1]_ * Floating roof: [2]_ * Cone roof: [2]_ Examples -------- Create a carbon steel, floating roof storage tank for the storage of bioethanol: >>> from biosteam import units, settings, Stream >>> settings.set_thermo(['Ethanol'], cache=True) >>> feed = Stream('feed', Ethanol=23e3, units='kg/hr') >>> effluent = Stream('effluent') >>> T1 = units.StorageTank('T1', ins=feed, outs=effluent, ... tau=7*24, # In hours ... vessel_type='Floating roof', ... vessel_material='Carbon steel') >>> T1.simulate() >>> T1.show(flow='kg/hr') StorageTank: T1 ins... [0] feed phase: 'l', T: 298.15 K, P: 101325 Pa flow (kg/hr): Ethanol 2.3e+04 outs... [0] effluent phase: 'l', T: 298.15 K, P: 101325 Pa flow (kg/hr): Ethanol 2.3e+04 >>> T1.results() Storage tank Units T1 Design Residence time hr 168 Total volume m^3 4.92e+03 Purchase cost Tank (x2) USD 8.41e+05 Total purchase cost USD 8.41e+05 Utility cost USD/hr 0 """ _outs_size_is_fixed = _ins_size_is_fixed = True _default_vessel_type = 'Field erected' _default_tau = 4*7*24 _default_V_wf = 1.0 _default_vessel_material = 'Stainless steel' _default_kW_per_m3 = 0. #: dict[str: VesselPurchaseCostAlgorithm] All cost algorithms available for vessel types. purchase_cost_algorithms = storage_tank_purchase_cost_algorithms
[docs] class MixTank(Tank): """ Create a mixing tank with volume based on residence time. Parameters ---------- ins : Inlet fluids to be mixed. outs : Outlet. vessel_type : str, optional Vessel type. Defaults to 'Conventional'. tau : float, optional Residence time [hr]. Defaults to 1. V_wf : float, optional Fraction of working volume over total volume. Defaults to 0.8. vessel_material : str, optional Vessel material. Defaults to 'Stainless steel'. kW_per_m3 : float, optional Electricity requirement per unit volume [kW/m^3]. Defaults to 0.0985. Notes ----- For a detailed discussion on the design and cost algorithm, please see :class:`~biosteam.units.tank.Tank`. The purchase cost algorithm is based on [1]_. The electricity rate is based on [2]_. """ rigorous = False conserve_phases = False _N_ins = 2 _ins_size_is_fixed = False _run = Mixer._run _default_vessel_type = 'Conventional' _default_tau = 1 _default_V_wf = 0.8 _default_vessel_material = 'Stainless steel' _default_kW_per_m3 = 0.0985 #: dict[str: VesselPurchaseCostAlgorithm] All cost algorithms available for vessel types. purchase_cost_algorithms = mix_tank_purchase_cost_algorithms
MixTank._graphics.edge_in *= 3 MixTank._graphics.edge_out *= 3