17. Property packages#

A Thermo object defines a thermodynamic property package. To build a Thermo object, we must first define all the chemicals involed. In the following example, we create a property package that BioSTEAM can use to model a sugarcane biorefinery producing ethanol from fermenting the juice and electricity from the burning the bagasse [1-2].

../_images/sugarcane_light.png
../_images/sugarcane_dark.png

17.1. Creating chemicals#

We can first start by defining the common chemicals already in the data base:

[1]:
import thermosteam as tmo
from warnings import filterwarnings
filterwarnings('ignore')
chemicals = tmo.Chemicals(
    ['Water', # Define common chemicals by name
     'Ethanol',
     'Octane',
     tmo.Chemical('Glucose', phase='s'), # These will always remain as solids
     tmo.Chemical('Sucrose', phase='s'), # Specify phase if chemicals not in vapor-liquid equilibrium
     tmo.Chemical('H3PO4', phase='s'),
     tmo.Chemical('P4O10',
                  rho=1540, # Density [kg/m3]
                  default=True,  # Default other chemicals properties like viscosity to that of water at 25 C
                  phase='s'),
     tmo.Chemical('CO2', phase='g'), # Assume they will always remain a gas
     tmo.Chemical('O2', phase='g'),
     tmo.Chemical('Cellulose',
                  Cp=1.364, # Heat capacity [kJ/kg]
                  rho=1540, # Density [kg/m3]
                  default=True, # Default other chemicals properties like viscosity to that of water at 25 C
                  search_db=False, # Not in database, so do not search the database
                  phase='s',
                  formula="C6H10O5", # Glucose monomer minus water, molecular weight is computed based on formula
                  Hf=-975708.8), # Heat of formation [J/mol]
     tmo.Chemical('Hemicellulose',
                  Cp=1.364,
                  rho=1540,
                  default=True,
                  search_db=False,
                  phase='s',
                  formula="C5H8O5", # Xylose monomer minus water
                  Hf=-761906.4),
     tmo.Chemical('Lignin',
                  Cp=1.364,
                  rho=1540,
                  default=True,
                  search_db=False,
                  phase='s',
                  formula='C8H8O3', # Vainillin formula
                  Hf=-452909.632),
     tmo.Chemical('Flocculant',
                  Cp=4.184,
                  rho=1540,
                  default=True,
                  search_db=False,
                  phase='s',
                  MW=1.), # No formula, so molecular weight should be defined
     tmo.Chemical('Solids',
                  Cp=1.100,
                  rho=1540,
                  default=True,
                  search_db=False,
                  phase='s',
                  MW=1.),
     tmo.Chemical('DryYeast',
                  rho=1540,
                  default=True,
                  search_db=False,
                  phase='s',
                  MW=1.,
                  aliases={'Yeast'}), # We can also give aliases to refer to them later
     tmo.Chemical('CaO',
                  Cp=1.02388,
                  rho=1540,
                  default=True,
                  search_db=False,
                  phase='s',
                  formula='CaO'),
     tmo.Chemical('Ash',
                  rho=1540,
                  Cp=0.37656,
                  default=True,
                  search_db=False,
                  phase='s',
                  MW=1.)]
)

# Compile once you are done adding chemicals so that the
# order of chemicals becomes immutable
chemicals.compile()

17.2. Mixture objects#

Before creating a Thermo property package, we must define the mixing rules to calculate mixture properties through a Mixture object:

[2]:
# Note that the mixture defaults to ideal mixing rules (weighted by mol)
# and excess energies are ignored by default
mixture = tmo.Mixture.from_chemicals(chemicals)
mixture.show()
Mixture(
    rule='ideal', ...
    include_excess_energies=False
)

You can use the mixture for estimating mixture properties:

[3]:
array = chemicals.array
mol = array(['Water', 'Ethanol'], [2, 2])
H = mixture.H('l', mol, 300, 101325)
round(H)
[3]:
695
[4]:
mol = array(['Water', 'Ethanol'], [2, 2])
mixture.Cn('l', mol / mol.sum(), 300) # Normalize composition for result on a molar basis
[4]:
94.07098289709057

You can also estimate multi-phase mixture properties through methods that start with “x” (e.g. xCn):

[5]:
mol_liquid = array(['Water', 'Ethanol'], [2, 2])
mol_vapor = array(['Water', 'Ethanol'], [2, 2])
phase_data = [('l', mol_liquid), ('g', mol_vapor)]
Cn = mixture.xCn(phase_data, T=300) # Returns total capacity [J/K] because composition was not normalized
round(Cn)
[5]:
574

Note: To implement a your own Mixture object, you can request help through BioSTEAMDevelopmentGroup/thermosteam.

17.3. Thermo objects#

Once the chemicals and mixture objects are finalized, we can compile them into a Thermo object and set the default property package:

[6]:
tmo.settings.set_thermo(chemicals, mixture=mixture) # Set the default property package
tmo.settings.thermo.show()
Thermo(
    chemicals=CompiledChemicals([Water, Ethanol, Octane, Glucose, Sucrose, H3PO4, P4O10, CO2, O2, Cellulose, Hemicellulose, Lignin, Flocculant, Solids, DryYeast, CaO, Ash]),
    mixture=Mixture(
        rule='ideal', ...
        include_excess_energies=False
    ),
    Gamma=DortmundActivityCoefficients,
    Phi=IdealFugacityCoefficients,
    PCF=MockPoyintingCorrectionFactors
)

Note that a Thermo object contains ActivityCoefficients, FugacityCoefficients, and PoyintingCorrectionFactors subclasses to define fugacity estimation methods. By default, activities are estimated by Dortmund modified UNIFAC method, while vapor phase fugacity coefficients and Poyinting correction factors are assumed to be 1. If you plan on using all defaults, you can just use the chemicals to set the property package (and skip the creation of Thermo and Mixture objects):

[7]:
tmo.settings.set_thermo(chemicals)
tmo.settings.get_thermo()
Thermo(
    chemicals=CompiledChemicals([Water, Ethanol, Octane, Glucose, Sucrose, H3PO4, P4O10, CO2, O2, Cellulose, Hemicellulose, Lignin, Flocculant, Solids, DryYeast, CaO, Ash]),
    mixture=Mixture(
        rule='ideal', ...
        include_excess_energies=False
    ),
    Gamma=DortmundActivityCoefficients,
    Phi=IdealFugacityCoefficients,
    PCF=MockPoyintingCorrectionFactors
)

If you have mixtures with non-condensible gases, you may want to use IdealGasPoyintingCorrectionFactors to account for Poyinting effects:

[8]:
# Without Poyinting correction factors, the dew point is high
tmo.settings.set_thermo(['Butanol', 'CO2'])
eq = tmo.equilibrium
s = tmo.Stream(None, Butanol=1, CO2=10, P=1e7)
s.dew_point_at_P()
[8]:
DewPointValues(T=470.72, P=10000000, IDs=('Butanol', 'CO2'), z=[0.091 0.909], x=[0.997 0.003])
[9]:
# With Poyinting correction factors, the dew point is lower
tmo.settings.set_thermo(['Butanol', 'CO2'], PCF=eq.IdealGasPoyintingCorrectionFactors)
s = tmo.Stream(None, Butanol=1, CO2=10, P=1e7)
s.dew_point_at_P()
[9]:
DewPointValues(T=458.21, P=10000000, IDs=('Butanol', 'CO2'), z=[0.091 0.909], x=[0.995 0.005])

17.4. References#

  1. Huang, H., Long, S., & Singh, V. (2016) “Techno-economic analysis of biodiesel and ethanol co-production from lipid-producing sugarcane” Biofuels, Bioproducts and Biorefining, 10(3), 299–315. https://doi.org/10.1002/bbb.1640

  2. Cortes-Peña, Y.; Kumar, D.; Singh, V.; Guest, J. S. BioSTEAM: A Fast and Flexible Platform for the Design, Simulation, and Techno-Economic Analysis of Biorefineries under Uncertainty. ACS Sustainable Chem. Eng. 2020. https://doi.org/10.1021/acssuschemeng.9b07040.

  3. Hatakeyama, T., Nakamura, K., & Hatakeyama, H. (1982). Studies on heat capacity of cellulose and lignin by differential scanning calorimetry. Polymer, 23(12), 1801–1804. https://doi.org/10.1016/0032-3861(82)90125-2

  4. Thybring, E. E. (2014). Explaining the heat capacity of wood constituents by molecular vibrations. Journal of Materials Science, 49(3), 1317–1327. https://doi.org/10.1007/s10853-013-7815-6