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]:
# Specify vapor fraction and isobaric conditions
F1 = bst.Flash('F1', ins=feed, V=0.1, P=101325)
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

Note that the outlets, outs, is populated by empty streams.

To simulate the flash, use the simulate method:

[16]:
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.84 K, P: 101325 Pa
    flow (kmol/hr): Water     2.59
                    Methanol  4.41
[1] s3
    phase: 'l', T: 352.84 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:

[17]:
F1.results() # Default returns DataFrame object with units
[17]:
Flash Units F1
Low pressure steam Duty kJ/hr 5.92e+05
Flow kmol/hr 15.3
Cost USD/hr 3.06
Design Vessel type Horizontal
Length ft 6.44
Diameter ft 4
Weight lb 1.38e+03
Wall thickness in 0.312
Vessel material Carbon steel
Purchase cost Horizontal pressure vessel USD 1.05e+04
Platform and ladders USD 3.02e+03
Heat exchanger - Double pipe USD 4.3e+03
Total purchase cost USD 1.78e+04
Utility cost USD/hr 3.06

Unit operations also have useful properties for accessing streams and utility requirements:

[18]:
F1.net_duty # Duty with heat transfer losses [kJ / hr]
[18]:
591519.5983175855
[19]:
F1.net_power # Electricity consumption [kW]
[19]:
0.0
[20]:
[F1.feed, F1.vapor, F1.liquid] # Inlet feed and vapor and liquid outlets
[20]:
[<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:

[21]:
recycle = bst.Stream('liquid_recycle')
feed = bst.Stream('feed', Methanol=100, Water=450)
M1 = bst.Mixer('M1', ins=(recycle, feed))
F1 = bst.Flash('F1',
    ins=M1-0, # -pipe- notation equivalent to M1.outs[0]
    outs=('vapor_product', 'liquid'),
    V=0.1, P=101325
)
S1 = bst.Splitter('S1',
    ins=F1-1, # -pipe- notation equivalent to F1.outs[1]
    outs=(recycle, 'liquid_product'),
    split=0.5 # Split to 0th output stream
)

Note that -pipe- notation was used to retrieve streams and connect units.

You can find unit operations and manage flowsheets with the main_flowsheet:

[22]:
bst.main_flowsheet.diagram()
# Note that empty streams are dashed and the
# width of streams depend on their flow rates (by mass)

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:

[23]:
sys = bst.main_flowsheet.create_system('sys')
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: 'l', T: 298.15 K, P: 101325 Pa
    flow: 0
[1] liquid_product
    phase: 'l', T: 298.15 K, P: 101325 Pa
    flow: 0

Simulate the System object:

[24]:
sys.simulate()
sys.show()
System: sys
Highest convergence error among components in recycle
stream S1-0 after 4 loops:
- flow rate   3.13e-01 kmol/hr (0.57%)
- temperature 3.56e-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.26 K, P: 101325 Pa
    flow (kmol/hr): Water     53.4
                    Methanol  46.7
[1] liquid_product
    phase: 'l', T: 359.26 K, P: 101325 Pa
    flow (kmol/hr): Water     397
                    Methanol  53.6

Note how the recycle stream converged and all unit operations (including the flash vessel) were simulated. Let’s have a look at system-level results:

[25]:
sys.results()
[25]:
System Units sys
Low pressure steam Duty kJ/hr 6.76e+06
Flow kmol/hr 175
Cost USD/hr 34.9
Total purchase cost USD 3.78e+04
Installed equipment cost USD 1.12e+05
Utility cost USD/hr 34.9
Material cost USD/hr 0
Sales USD/hr 0

Once your system has been simulated, you can save a system report to view detailed results in an excel spreadsheet:

[26]:
# 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.