Thermo property packages

A Thermo object defines a thermodynamic property package. To build a Thermo object, we must first define all the chemicals involed. The following example will show how this can be done through the creation of a property package for the co-production of biodiesel and ethanol from lipid-cane [1]:

Chemicals

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

[1]:
import thermosteam as tmo
Biodiesel = tmo.Chemical('Biodiesel',
                         search_ID='Methyl oleate')
lipidcane_chemicals = tmo.Chemicals(
    ['Water', 'Methanol', 'Ethanol', 'Glycerol',
     'Glucose', 'Sucrose', 'H3PO4', 'P4O10', 'CO2',
     'Octane', 'O2', Biodiesel])

(Water, Methanol, Ethanol,
 Glycerol, Glucose, Sucrose,
 H3PO4, P4O10, CO2, Octane, O2, Biodiesel) = lipidcane_chemicals

We can assume that CO2 and O2 will always remain a gas in the process by setting the state:

[2]:
O2.at_state(phase='g')
CO2.at_state(phase='g')

Similarly, we can assume glucose, sucrose, and phosphoric acid all remain as solids:

[3]:
H3PO4.at_state(phase='s')
P4O10.at_state(phase='s')
Glucose.at_state(phase='s')
Sucrose.at_state(phase='s')

Now we can define the solids in the process (both soluble and insoluble). We can use the Chemical.blank method to create a “blank” Chemical object and add the thermo models ourselves:

[4]:
def create_new_chemical(ID, phase='s', **constants):
    # Create a new solid chemical without any data
    solid = tmo.Chemical.blank(ID, phase=phase, **constants)

    # Add chemical to the Chemicals object
    lipidcane_chemicals.append(solid)

    return solid

# Cellulose and hemicellulose are modeled
# as their monomer minus on H2O.
Cellulose = create_new_chemical('Cellulose',
                                formula="C6H10O5", # Hydrolyzed glucose monomer
                                Hf=-975708.8)
Hemicellulose = create_new_chemical('Hemicellulose',
                                    formula="C5H8O5", # Hydrolyzed xylose monomer
                                    Hf=-761906.4)
Flocculant = create_new_chemical('Flocculant', MW=1.)
# Lignin is modeled as vanillin.
Lignin = create_new_chemical('Lignin',
                             formula='C8H8O3', # Vanillin
                             Hf=-452909.632)
Ash = create_new_chemical('Ash', MW=1.)
Solids = create_new_chemical('Solids', MW=1.)
DryYeast = create_new_chemical('DryYeast', MW=1., CAS='Yeast')
NaOCH3 = create_new_chemical('NaOCH3', formula='NaOCH3')
CaO = create_new_chemical('CaO', formula='CaO')
HCl = create_new_chemical('HCl', formula='HCl')
NaOH = create_new_chemical('NaOH', formula='NaOH')

Note that we are still missing the lipid, modeled as Triolein. However, Triolein is not in the data bank, so let’s start making it from scratch:

[5]:
Lipid = create_new_chemical(
    'Lipid',
    phase='l',
    Hf=-2193.7e3,
    formula = 'C57H104O6',
)

Instead of creating new models based on external sources, here we will approximate Triolein with the properties of Tripalmitin (which does exist in the data bank):

[6]:
Tripalmitin = tmo.Chemical('Tripalmitin').at_state(phase='l', copy=True)
Lipid.copy_models_from(Tripalmitin, ['V', 'sigma', 'kappa', 'Cn'])

All what is left is to fill the chemical properties. This done through the add_model method of the chemical model handles. Let’s begin with the solids using data from [1-4]:

[7]:
from thermosteam import functional as fn

# Assume a constant volume for lipid
lipid_molar_volume = fn.rho_to_V(rho=900, MW=Lipid.MW)
Lipid.V.add_model(lipid_molar_volume)

# Insolubles occupy a significant volume
insoluble_solids = (Ash, Cellulose, Hemicellulose, Sucrose,
                    Flocculant, Lignin, Solids, DryYeast, P4O10)
for chemical in insoluble_solids:
    V = fn.rho_to_V(rho=1540, MW=chemical.MW)
    chemical.V.add_model(V, top_priority=True)

# Solubles don't occupy much volume
soluble_solids = (CaO, HCl, NaOH, H3PO4, Glucose)
for chemical in soluble_solids:
    V = fn.rho_to_V(rho=1e5, MW=chemical.MW)
    chemical.V.add_model(V, top_priority=True)


# Assume sodium methoxide has some of the same properities as methanol
LiquidMethanol = Methanol.at_state(phase='l', copy=True)
NaOCH3.copy_models_from(LiquidMethanol, ['V', 'sigma','kappa', 'Cn'])

# Add constant models for molar heat capacity of solids
Ash.Cn.add_model(0.09 * 4.184 * Ash.MW)
CaO.Cn.add_model(1.02388 * CaO.MW)
Cellulose.Cn.add_model(1.364 * Cellulose.MW)
Hemicellulose.Cn.add_model(1.364 * Hemicellulose.MW)
Flocculant.Cn.add_model(4.184 * Flocculant.MW)
Lignin.Cn.add_model(1.364 * Lignin.MW)
Solids.Cn.add_model(1.100 * Solids.MW)

[7]:
1.1

We don’t care much about the rest of the properties (e.g. thermal conductivity), so we can default them to the values of water:

[8]:
for chemical in lipidcane_chemicals: chemical.default()

Finalize the chemicals by compiling:

[9]:
lipidcane_chemicals.compile()

This enables methods such as <CompiledChemicals>.array to create chemical data ordered according to the IDs, as well as <CompiledChemicals>.get_index to get the index of a chemical:

[10]:
lipidcane_chemicals.array(['Water', 'Ethanol'], [2, 2])
[10]:
array([2., 0., 2., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0.])
[11]:
lipidcane_chemicals.get_index(('Water', 'Ethanol'))
[11]:
[0, 2]

After compiling, it is possible to set synonyms for indexing:

[12]:
lipidcane_chemicals.set_synonym('Water', 'H2O')
print(lipidcane_chemicals.H2O)
Water

Mixture objects

Before creating a Thermo object, we must define the mixing rules to calculate mixture properties. A Mixture object is able to calculate mixture properties through functors. In this example we will use a function to create a Mixture object with ideal mixing rules:

[13]:
mixture = tmo.mixture.ideal_mixture(lipidcane_chemicals,
                                    rigorous_energy_balance=True,
                                    include_excess_energies=False)
mixture
Mixture(
    rule='ideal mixing', ...
    rigorous_energy_balance=True,
    include_excess_energies=False
)

You can use the mixture for estimating mixture properties:

[14]:
array = lipidcane_chemicals.array
mol = array(['Water', 'Ethanol'], [2, 2])
mixture.H('l', mol, 300, 101325)
[14]:
694.8275427425033
[15]:
mol = array(['Water', 'Ethanol'], [2, 2])
mixture.Cn('l', mol, 300)
[15]:
376.3036205916924

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

[16]:
mol_liquid = array(['Water', 'Ethanol'], [2, 2])
mol_vapor = array(['Water', 'Ethanol'], [2, 2])
phase_data = [('l', mol_liquid), ('g', mol_vapor)]
mixture.xCn(phase_data, T=300)
[16]:
574.6439104469846

Note: To implement a your own Mixture object, you can request help on this process through https://github.com/BioSTEAMDevelopmentGroup/thermosteam.

Thermo objects

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

[17]:
thermo = tmo.Thermo(lipidcane_chemicals, mixture)
thermo
Thermo(
    chemicals=CompiledChemicals([Water, Methanol, Ethanol, Glycerol, Glucose, Sucrose, H3PO4, P4O10, CO2, Octane, O2, Biodiesel, Cellulose, Hemicellulose, Flocculant, Lignin, Ash, Solids, DryYeast, NaOCH3, CaO, HCl, NaOH, Lipid]),
    mixture=Mixture(
        rule='ideal mixing', ...
        rigorous_energy_balance=True,
        include_excess_energies=False
    ),
    Gamma=DortmundActivityCoefficients,
    Phi=IdealFugacityCoefficients,
    PCF=IdealPoyintingCorrectionFactors
)

Note that a Thermo object contains ActivityCoefficients, FugacityCoefficients, and PoyintingCorrectionFactors subclasses to define fugacity estimation methods. By default, the Dortmund modified UNIFAC method for estimating activities is selected, while ideal values for (vapor phase) fugacity coefficients and poyinting correction factos are selected. Additionally, a Thermo object defaults to ideal mixing rules for estimating mixture properties, and neglects excess properties in the calculation of enthalpy and entropy:

[18]:
thermo = tmo.Thermo(lipidcane_chemicals)
thermo.mixture
Mixture(
    rule='ideal mixing', ...
    rigorous_energy_balance=True,
    include_excess_energies=False
)

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. 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

  3. 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

  4. Murphy W. K., and K. R. Masters. (1978). Gross heat of combustion of northern red oak (Quercus rubra) chemical components. Wood Sci. 10:139-141.