# Source code for biosteam.units.design_tools.vacuum

```# -*- coding: utf-8 -*-
"""
Functional algorithms for the design and purchase cost estimation of
vacuum systems.

References
----------
.. [1] 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.
.. [2] Amazon. Robinair (15115) VacuMaster Single Stage Vacuum Pump - Single-Stage, 1.5 CFM.
https://www.amazon.com/Robinair-15115-VacuMaster-Single-Vacuum/dp/B005CO9FDW?ref_=ast_sto_dp.
Accessed on 09/28/2023.
.. [3] Amazon. Robinair (15300) VacuMaster Economy Vacuum Pump - 2-Stage, 3 CFM.
https://www.amazon.com/Robinair-15300-VacuMaster-Economy-Vacuum/dp/B000O1E5UQ?ref_=ast_sto_dp
Accessed on 09/28/2023.

"""
from numpy import log as ln
from . import mechanical as mch
from math import ceil
from biosteam.utils import checkbounds
from biosteam.exceptions import DesignError
from numba import njit
import biosteam as bst

__all__ = ('compute_vacuum_system_power_and_cost',)

# %% Data

# System types of vacuum systems
# Volumetric flowrate ranges, (cfm) and lower limit of suction (torr)
# Rotary vane pumps based on ref. [2], [3]
_steamjet_ejectors = {
'One stage':               ((10, 1000000), 100),
'Two stage':               ((10, 1000000),  15),
'Three stage':             ((10, 1000000),   2)}
_liquid_ring = {
'One stage water seal':    (( 3,   18000),  50),
'Two stage water seal':    (( 3,   18000),  25),
'Oil seal':                (( 3,   18000),  10)}
_dry_vacuum = {
'Three stage rotary lobe': ((60,     240), 1.5),
'Three stage claw':        ((60,     270), 0.3),
'Screw compressor':        ((50,    1400), 0.1)}
_rotary_vane = {
'One stage':               ((0,     1.51), 0.115),
'Two stage':               ((1.5,   3.01), 0.035)}

_default_vacuum_systems = {'Liquid-ring pump': _liquid_ring,
'Steam-jet ejector': _steamjet_ejectors,
'Dry-vacuum pump': _dry_vacuum,
'Rotary-vane pump': _rotary_vane}

_default_rotary_vane_work_cost = {
'One stage': (1/5 * 0.7457, 127*1.08), # hp to kW; 2023 USD (including tax & shipping)
'Two stage': (1/3 * 0.7457, 248*1.08)
}

_air_density = 1.2041 # kg/m3 dry air

# %% Calculate vacuum system requirements

[docs]
def compute_vacuum_system_power_and_cost(
F_mass, F_vol, P_suction, vessel_volume,
vacuum_system_preference=None):
"""
Return a dictionary of vacuum system requirements.

Parameters
----------
F_mass : float
Vapor mass flow rate entering vacuum system from vessel in kg/hr (not including inleakage).
F_vol : float
Vapor volumetric flow rate entering vacuum system from vessel in m3/hr (not including inleakage).
P_suction : float
Suction pressure in Pa
vessel_volume : float
Vacuum volume in m3
vacuum_system_preference : 'Liquid-ring pump', 'Steam-jet ejector', or 'Dry-vacuum pump'
Name(s) of preferred vacuum systems

"""
P_suction *= 7.5006e-3 # to torr
if vessel_volume:
vessel_volume *= 35.315 # to ft3
F_mass_air = calculate_air_inleakage(vessel_volume, P_suction) # lb/hr
F_vol_air = 0.26697*F_mass_air/_air_density # cfm
else:
F_vol_air = F_mass_air = 0
F_vol_cfm = 0.5886*F_vol + F_vol_air
# if F_vol_cfm < 3.01:
# factor = 3.01/F_vol_cfm
# F_vol_cfm = 3.01
# else:
#     factor = 1
factor = 1
F_mass_kgph = (F_mass + 0.4536*F_mass_air)*factor # kg/hr
F_mass_lbph = 2.205 * F_mass_kgph
vacuum_systems = get_prefered_vacuum_systems(vacuum_system_preference)
name, grade, N = select_vacuum_system(vacuum_systems, F_vol_cfm, P_suction, bool(vacuum_system_preference))
base_cost = calculate_vacuum_cost(name, grade, F_mass_lbph, F_vol_cfm, P_suction)
cost = bst.CE / 567.  * base_cost
if name == 'Steam-jet ejector':
for agent in bst.HeatUtility.heating_agents:
if agent.P > 689475.: break # 100 psig
steam = 0.41631 * F_mass_kgph # [kmol/hr] 7.5 weight steam/ weight gas
work = 0.
has_condenser = grade != 'One stage'
elif name == 'Rotary-vane pump':
has_condenser = False
agent = None
steam = 0.
N = 1
else:
has_condenser = False
agent = None
steam = 0.
work = calculate_vacuum_power(F_mass_kgph, P_suction)
return {'Work': work,
'Cost': N * cost,
'In parallel': N,
'Condenser': has_condenser,
'Steam flow rate': steam,
'Heating agent': agent}

# %% Supporting functions

def get_prefered_vacuum_systems(preference):
defaults = _default_vacuum_systems
if preference is None:
return defaults
else:
if isinstance(preference, str):
if preference not in defaults:
raise ValueError(f"preference must be in the following list: {list(defaults)}")
preference = (preference, *defaults)
else:
for name in preference:
if name not in defaults:
raise ValueError(f"preference must be in the following list: {list(defaults)}")
return {name: defaults[name] for name in preference}

def get_available_vacuum_systems(F_vol_cfm, P_suction):
"""
Return available vacuum type and grade

Parameters
----------
F_vol_cfm : float
Vapor volumetric flow rate entering vacuum system from vessel in cfm (including inleakage).
P_suction : float
Suction pressure in Torr
"""
types = []
for vacuumtype, vacuum_sys in _default_vacuum_systems.items():
flowrange, minsuction = flowrange_minsuction
if checkbounds(F_vol_cfm, flowrange) and P_suction > minsuction:
return types

def select_vacuum_system(vacuum_systems, F_vol_cfm, P_suction, ignore_F_lb=False):
"""
Return a heuristic vacuum type and grade

Parameters
----------
F_vol_cfm : float
Vapor volumetric flow rate entering vacuum system from vessel in cfm (including inleakage).
P_suction : float
Suction pressure in Torr
"""
for name, vacuum_sys in vacuum_systems.items():
flowrange, minsuction = flowrange_minsuction
if ignore_F_lb:
if F_vol_cfm < flowrange[-1] and P_suction > minsuction:
elif checkbounds(F_vol_cfm, flowrange) and P_suction > minsuction:
for name, vacuum_sys in vacuum_systems.items(): # Flow rate too large
flowrange, minsuction = flowrange_minsuction
if P_suction > minsuction:
N = ceil(F_vol_cfm / flowrange[-1])
raise DesignError(f'no vacuum system available at current flow ({F_vol_cfm:.2f} cfm) and suction pressure ({P_suction:.2f} Torr)')

@njit(cache=True)
def calculate_heuristic_air_inleakage(V, P):
"""
Return air in-leakage in kg/hr through a heuristic calculation.

Parameters
----------
V : float
Vacuum volume in m3
P : float
Suction pressure in Pa

"""
if P > 11999.013: k = 0.2
elif P > 4132.993: k = 0.15
elif P > 2799.77: k = 0.10
elif P > 133.322: k = 0.051
else: raise ValueError('cannot calculate air inleakage at pressures lower than 133.32 Pascal')
return k*V**0.667

@njit(cache=True)
def calculate_air_inleakage(V, P):
"""
Return air in-leakage in kg/hr.

Parameters
----------
V : float
Vacuum volume in ft3
P : float
Suction pressure in Torr
"""
lnP = ln(P)
return 5. + (0.0298 + 0.03088*lnP - 5.733e-4*lnP*lnP)*V**0.66

def calculate_vacuum_power(F_mass,  P_suction):
"""
Return vacuum power (after accounting for motor efficiency) in kW.

Parameters
----------
F_mass : float
Total mass flow rate entering vacuum system in kg/hr (including inleakage).
P_suction : float
Suction pressure in Torr

"""
SF = F_mass/P_suction # Size factor
if SF < 0.2: SF = 0.2
elif SF > 16.: SF = 16.
Pb = 21.4*SF**0.924 # Break power assuming Liquid-ring (NASH)
mu_m = mch.motor_efficiency(Pb)
return Pb/mu_m

def calculate_vacuum_cost(vacuum_sys, grade, F_mass_lbph, F_vol_cfm, P_suction):
# Costing for different vacuums ASSUMING THAT ALL ARE CARBON STEEL!!!!
if vacuum_sys ==  'Steam-jet ejector':
S = F_mass_lbph/P_suction
Cp = 1915*S**0.41
Cs = 1
Cs = 1.8 * 1.6 # 2 stage + 1 condenser
Cs = 2.1 * 2.3 # 3 stage + 2 condenser
Cost = Cp*Cs
elif vacuum_sys == 'Liquid-ring pump':
S = F_vol_cfm
Cp = 8250*S**0.37
if grade == 'One stage water seal':
Cs = 1
elif grade == 'Two stage water seal':
Cs = 1.8
Cs = 2.1
Cost = Cp*Cs
elif vacuum_sys == 'Dry-vacuum pump':
S = F_vol_cfm
if grade == 'Three stage rotary lobe':
Cp = 8075*S**0.41
elif grade == 'Three stage claw':
Cp = 9785*S**0.36