1. Getting started#
1.1. Initialize streams#
Stream objects define material flow rates along with its thermodynamic state. Before creating streams, a Thermo property package must be defined. Alternatively, we can just pass chemical names and BioSTEAM will automatically create a property package based on ideal mixing rules and UNIFAC activity coefficients for phase equilibrium. More complex packages can be defined through Thermosteam, BioSTEAM’s premier thermodynamic engine. Please visit Thermosteam 101 for details. In this example, a simple feed stream with a few common chemicals will be initialized:
[1]:
import biosteam as bst
from biosteam import settings
bst.nbtutorial() # Light-mode html diagrams and filter warnings
settings.set_thermo(['Water', 'Methanol'])
feed = bst.Stream(Water=50, Methanol=20)
feed.show()
Stream: s1
phase: 'l', T: 298.15 K, P: 101325 Pa
flow (kmol/hr): Water 50
Methanol 20
Set prices for performing techno-economic analysis later:
[2]:
feed.price = 0.15 # USD/kg
feed.cost # USD/hr
[2]:
231.24018
1.2. Process settings#
Process settings include price of feeds and products, conditions of utilities, and the chemical engineering plant cost index. These should be set before simulating a system.
Set the chemical engineering plant cost index:
[3]:
settings.CEPCI # Default year is 2017
[3]:
567.5
[4]:
settings.CEPCI = 603.1 # To year 2018
Set PowerUtility options:
[5]:
settings.electricity_price # Default price (USD/kWhr)
[5]:
0.0782
[6]:
settings.electricity_price = 0.065 # Adjust price
Set HeatUtility options via UtilityAgent objects, which are Stream objects with additional attributes to describe a utility agent:
[7]:
settings.cooling_agents # All available cooling agents
[7]:
[<UtilityAgent: cooling_water>,
<UtilityAgent: chilled_water>,
<UtilityAgent: chilled_brine>,
<UtilityAgent: propane>]
[8]:
cooling_water = settings.get_cooling_agent('cooling_water')
cooling_water.show() # A UtilityAgent
UtilityAgent: cooling_water
heat_transfer_efficiency: 1.000
heat_transfer_price: 0 USD/kJ
regeneration_price: 0.000488 USD/kmol
T_limit: 325 K
phase: 'l'
T: 305.37 K
P: 101325 Pa
flow (kmol/hr): Water 1
[9]:
# Price of regenerating the utility in USD/kmol
cooling_water.regeneration_price
[9]:
0.00048785
[10]:
# Other utilities may be priced for amount of heat transfered in USD/kJ
chilled_water = settings.get_cooling_agent('chilled_water')
chilled_water.heat_transfer_price
[10]:
5e-06
[11]:
cooling_water.T = 302 # Change the temperature of cooling water (K)
[12]:
settings.heating_agents # All available heating agents
[12]:
[<UtilityAgent: low_pressure_steam>,
<UtilityAgent: medium_pressure_steam>,
<UtilityAgent: high_pressure_steam>]
[13]:
lps = settings.get_heating_agent('low_pressure_steam') # A UtilityAgent
lps.show() # Note that because utility changes phase, T_limit is None
UtilityAgent: low_pressure_steam
heat_transfer_efficiency: 0.950
heat_transfer_price: 0 USD/kJ
regeneration_price: 0.238 USD/kmol
T_limit: None
phase: 'g'
T: 412.19 K
P: 344738 Pa
flow (kmol/hr): Water 1
[14]:
lps.regeneration_price = 0.20 # Adjust price (USD/kmol)
1.3. Find design requirements and cost with Unit objects#
Creating a Unit can be flexible. But in summary, a Unit object is initialized with an ID, and unit-specific arguments. BioSTEAM includes essential unit operations with rigorous modeling and design algorithms. Here we create a Flash object as an example:
[15]:
from biosteam import units
# Specify vapor fraction and isobaric conditions
F1 = units.Flash('F1', V=0.1, P=101325)
F1.show()
Flash: F1
ins...
[0] missing stream
outs...
[0] s2
phase: 'l', T: 298.15 K, P: 101325 Pa
flow: 0
[1] s3
phase: 'l', T: 298.15 K, P: 101325 Pa
flow: 0
Note that, by default, Missing Stream objects are given to inputs, ins
, and empty streams to outputs, outs
:
[16]:
F1.ins
[16]:
[<MissingStream>]
[17]:
F1.outs
[17]:
[<Stream: s2>, <Stream: s3>]
You can connect streams by setting the ins
and outs
:
[18]:
F1.ins[0] = feed
F1.show()
Flash: F1
ins...
[0] s1
phase: 'l', T: 298.15 K, P: 101325 Pa
flow (kmol/hr): Water 50
Methanol 20
outs...
[0] s2
phase: 'l', T: 298.15 K, P: 101325 Pa
flow: 0
[1] s3
phase: 'l', T: 298.15 K, P: 101325 Pa
flow: 0
To simulate the flash, use the simulate
method:
[19]:
F1.simulate()
F1.show()
Flash: F1
ins...
[0] s1
phase: 'l', T: 298.15 K, P: 101325 Pa
flow (kmol/hr): Water 50
Methanol 20
outs...
[0] s2
phase: 'g', T: 352.87 K, P: 101325 Pa
flow (kmol/hr): Water 2.59
Methanol 4.41
[1] s3
phase: 'l', T: 352.87 K, P: 101325 Pa
flow (kmol/hr): Water 47.4
Methanol 15.6
You may get a “CostWarning” that notifies you whether purchase cost correlations are out of range for the given design. This is ok for the example, but its important to make sure that the process is well designed and cost correlations are suitable for the domain.
The results
method returns simulation results:
[20]:
print(F1.results()) # Default returns DataFrame object with units
Flash Units F1
Low pressure steam Duty kJ/hr 5.91e+05
Flow kmol/hr 15.2
Cost USD/hr 3.05
Design Vessel type Vertical
Length ft 44.5
Diameter ft 0.5
Weight lb 718
Wall thickness in 0.25
Vessel material Carbon steel
Purchase cost Heat exchanger - Double pipe USD 4.3e+03
Vertical pressure vessel USD 1.21e+04
Platform and ladders USD 3.85e+03
Total purchase cost USD 2.03e+04
Utility cost USD/hr 3.05
Unit operations also have useful properties for accessing streams and utility requirements:
[21]:
F1.net_duty # Duty [kJ / hr]
[21]:
591498.9300079723
[22]:
F1.net_power # Electricity consumption [kW]
[22]:
0.0
[23]:
[F1.feed, F1.vapor, F1.liquid] # Inlet feed and vapor and liquid outlets
[23]:
[<Stream: s1>, <Stream: s2>, <Stream: s3>]
Although BioSTEAM includes a large set of essential unit operations, many process specific unit operations are not yet available. In this case, you can create new Unit subclasses to model unit operations not yet available in BioSTEAM.
1.4. Solve recycle loops and process specifications with System objects#
Designing a chemical process is no easy task. A simple recycle process consisting of a flash with a partial liquid recycle is presented here.
Create a Mixer object and a Splitter object:
[24]:
M1 = units.Mixer('M1')
S1 = units.Splitter('S1', outs=('liquid_recycle', 'liquid_product'),
split=0.5) # Split to 0th output stream
F1.outs[0].ID = 'vapor_product'
F1.outs[1].ID = 'liquid'
You can find unit operations and manage flowsheets with the main_flowsheet
:
[25]:
bst.main_flowsheet.diagram()
# Note that empty streams are dashed and the
# width of streams depend on their flow rates (by mass)
Connect streams and make a recycle loop using -pipe- notation:
[26]:
feed = bst.Stream('feed', Methanol=100, Water=450)
# Broken down -pipe- notation
[S1-0, feed]-M1 # M1.ins[:] = [S1.outs[0], feed]
M1-F1 # F1.ins[:] = M1.outs
F1-1-S1 # S1.ins[:] = [F1.outs[1]]
# All together
[S1-0, feed]-M1-F1-1-S1;
Now lets check the diagram again:
[27]:
bst.main_flowsheet.diagram() # bst.F.diagram() also works
System objects take care of solving recycle loops and simulating all unit operations. Although there are many ways of creating a system, the most recommended way is to use the flowsheet:
[28]:
flowsheet_sys = bst.main_flowsheet.create_system('flowsheet_sys')
flowsheet_sys.show()
System: flowsheet_sys
ins...
[0] feed
phase: 'l', T: 298.15 K, P: 101325 Pa
flow (kmol/hr): Water 450
Methanol 100
outs...
[0] vapor_product
phase: 'g', T: 352.87 K, P: 101325 Pa
flow (kmol/hr): Water 2.59
Methanol 4.41
[1] liquid_product
phase: 'l', T: 298.15 K, P: 101325 Pa
flow: 0
Although not recommened due to the likelyhood of human error, a System object may also be created by specifying an ID, a recycle
stream and a path
of units to run element by element:
[29]:
sys = bst.System('sys', path=(M1, F1, S1), recycle=S1-0) # recycle=S1.outs[0]
sys.show()
System: sys
ins...
[0] feed
phase: 'l', T: 298.15 K, P: 101325 Pa
flow (kmol/hr): Water 450
Methanol 100
outs...
[0] vapor_product
phase: 'g', T: 352.87 K, P: 101325 Pa
flow (kmol/hr): Water 2.59
Methanol 4.41
[1] liquid_product
phase: 'l', T: 298.15 K, P: 101325 Pa
flow: 0
Simulate the System object:
[30]:
sys.simulate()
sys.show()
System: sys
Highest convergence error among components in recycle
stream S1-0 after 4 loops:
- flow rate 3.12e-01 kmol/hr (0.56%)
- temperature 3.55e-02 K (0.0099%)
ins...
[0] feed
phase: 'l', T: 298.15 K, P: 101325 Pa
flow (kmol/hr): Water 450
Methanol 100
outs...
[0] vapor_product
phase: 'g', T: 359.27 K, P: 101325 Pa
flow (kmol/hr): Water 53.5
Methanol 46.6
[1] liquid_product
phase: 'l', T: 359.27 K, P: 101325 Pa
flow (kmol/hr): Water 397
Methanol 53.7
Note how the recycle stream converged and all unit operations (including the flash vessel) were simulated:
[31]:
print(F1.results())
Flash Units F1
Low pressure steam Duty kJ/hr 6.76e+06
Flow kmol/hr 174
Cost USD/hr 34.8
Design Vessel type Vertical
Length ft 43.5
Diameter ft 2.5
Weight lb 3.6e+03
Wall thickness in 0.25
Vessel material Carbon steel
Purchase cost Heat exchanger - Floating head USD 2.29e+04
Vertical pressure vessel USD 2.81e+04
Platform and ladders USD 1.25e+04
Total purchase cost USD 6.35e+04
Utility cost USD/hr 34.8
You can retrieve summarized power and heat utilities from the system as well:
[32]:
sys.power_utility.show()
PowerUtility:
consumption: 0 kW
production: 0 kW
power: 0 kW
cost: 0 USD/hr
[33]:
for i in sys.heat_utilities: i.show()
HeatUtility: low_pressure_steam
duty: 6.76e+06 kJ/hr
flow: 174 kmol/hr
cost: 34.8 USD/hr
Once your system has been simulated, you can save a system report to view all results in an excel spreadsheet:
[34]:
# Try this on your computer and open excel
# sys.save_report('Example.xlsx')
Note that the cash flow analysis did not appear in the report because it requires a TEA object with all the necessary parameters (e.g., depreciation schedule, plant lifetime, construction schedule) to perform the analysis. A TEA object may also solve for economic indicators such as internal rate of return, minimum product selling price (MPSP), and maximum feedstock purchase price (MFPP). Techno-economic analysis is discussed in detail later in the tutorial due to the extensive nature of the cash flow analysis.