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.