<jupyter_start><jupyter_text>Thermo property packages A [Thermo](../Thermo.txt) 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](https://biosteam.readthedocs.io/en/latest/) can use to model a sugarcane biorefinery producing ethanol from fermenting the juice and electricity from the burning the bagasse [[1-2]](References).![](./sugarcane_areas.png "Sugarcane diagram")  Creating chemicals We can first start by defining the common chemicals already in the data base:<jupyter_code>import thermosteam as tmo
sugarcane_chemicals = tmo.Chemicals(
    ['Water', 'Ethanol', 'Glucose', 'Sucrose',
     'H3PO4', 'P4O10', 'CO2', 'Octane', 'O2'])

(Water, Ethanol, Glucose, Sucrose,
 H3PO4, P4O10, CO2, Octane, O2) = sugarcane_chemicals<jupyter_output><empty_output><jupyter_text>We can assume that CO2 and O2 will always remain a gas in the process by setting the state:<jupyter_code>O2.at_state(phase='g')
CO2.at_state(phase='g')<jupyter_output><empty_output><jupyter_text>Similarly, we can assume glucose, sucrose, and phosphoric acid all remain as solids:<jupyter_code>H3PO4.at_state(phase='s')
P4O10.at_state(phase='s')
Glucose.at_state(phase='s')
Sucrose.at_state(phase='s')<jupyter_output><empty_output><jupyter_text>Now we can define the solids in the process (both soluble and insoluble). We can pass search_db=False to just create a blank chemical and add the thermo models ourselves:<jupyter_code>def create_new_chemical(ID, phase='s', **constants):
    # Create a new solid chemical without any data
    solid = tmo.Chemical(ID, search_db=False, phase=phase, **constants)
    
    # Add chemical to the Chemicals object
    sugarcane_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.)
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')<jupyter_output><empty_output><jupyter_text>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 [[2-4]](References):<jupyter_code>from thermosteam import functional as fn

# Insolubles occupy a significant volume
insoluble_solids = (Ash, Cellulose, Hemicellulose,
                    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, Sucrose, Glucose) 
for chemical in soluble_solids:
    V = fn.rho_to_V(rho=1e5, MW=chemical.MW)
    chemical.V.add_model(V, top_priority=True)

# 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)<jupyter_output><empty_output><jupyter_text>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:<jupyter_code>for chemical in sugarcane_chemicals: chemical.default()<jupyter_output><empty_output><jupyter_text>Finalize the chemicals by compiling:<jupyter_code>sugarcane_chemicals.compile()<jupyter_output><empty_output><jupyter_text>This enables methods such as `.array` to create chemical data ordered according to the IDs, as well as `.get_index` to get the index of a chemical:<jupyter_code>sugarcane_chemicals.array(['Water', 'Ethanol'], [2, 2])
sugarcane_chemicals.get_index(('Water', 'Ethanol'))<jupyter_output><empty_output><jupyter_text>After compiling, it is possible to set synonyms for indexing:<jupyter_code>sugarcane_chemicals.set_synonym('Water', 'H2O')
print(sugarcane_chemicals.H2O)<jupyter_output>Water<jupyter_text>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:<jupyter_code>mixture = tmo.Mixture.from_chemicals(sugarcane_chemicals)
# Note that the mixture defaults to ideal mixing rules
# and excess energies are ignored by default
mixture.show()<jupyter_output>Mixture(
    rule='ideal', ...
    include_excess_energies=False
)<jupyter_text>You can use the mixture for estimating mixture properties:<jupyter_code>array = sugarcane_chemicals.array
mol = array(['Water', 'Ethanol'], [2, 2])
H = mixture.H('l', mol, 300, 101325)
round(H)
mol = array(['Water', 'Ethanol'], [2, 2])
mixture.Cn('l', mol / mol.sum(), 300) # Normalize composition for result on a molar basis<jupyter_output><empty_output><jupyter_text>You can also estimate multi-phase mixture properties through methods that start with "x" (e.g. `xCn`):<jupyter_code>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)<jupyter_output><empty_output><jupyter_text>Note: To implement a your own Mixture object, you can request help through https://github.com/BioSTEAMDevelopmentGroup/thermosteam.  Thermo objects Once the chemicals and mixture objects are finalized, we can compile them into a Thermo object:<jupyter_code>thermo = tmo.Thermo(sugarcane_chemicals, mixture)
thermo.show()<jupyter_output>Thermo(
    chemicals=CompiledChemicals([Water, Ethanol, Glucose, Sucrose, H3PO4, P4O10, CO2, Octane, O2, Cellulose, Hemicellulose, Flocculant, Lignin, Ash, Solids, DryYeast, NaOCH3, CaO, HCl, NaOH]),
    mixture=Mixture(
        rule='ideal', ...
        include_excess_energies=False
    ),
    Gamma=DortmundActivityCoefficients,
    Phi=IdealFugacityCoefficients,
    PCF=IdealPoyintingCorrectionFactors
)<jupyter_text>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 factors are selected. Additionally, a `Thermo` object defaults to ideal mixing rules for estimating mixture properties, and neglects excess energies in the calculation of enthalpy and entropy:<jupyter_code>thermo = tmo.Thermo(sugarcane_chemicals)
thermo.mixture.show()<jupyter_output>Mixture(
    rule='ideal', ...
    include_excess_energies=False
)
{'fraction_non_alphanumeric': 0.10496805320119963, 'fraction_numerical': 0.019168079280219064, 'mean_word_length': 5.349337748344371, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 53, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '14', 'dir': 'jupyter-structured-clean-dedup', 'id': '446960', 'license': 'MIT', 'n_tokens_mistral': 2775, 'n_tokens_neox': 2514, 'n_words': 686, 'path': 'docs/tutorial/Thermo_property_packages.ipynb', 'repo_name': 'yoelcortes/thermotree', 'size': '7669'}