{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Property packages" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A [Thermo](../API/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](../index.txt) can use to model a sugarcane biorefinery producing ethanol from fermenting the juice and electricity from the burning the bagasse [[1-2]](#References)." ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ ".. figure:: sugarcane_light.png\n", " :class: only-light\n", " :align: center\n", ".. figure:: sugarcane_dark.png\n", " :class: only-dark\n", " :align: center" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Creating chemicals" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here is a demonstrative example of how to create chemicals from the databank and define new chemicals. The assumptions used here are reasonable but may not be accurate depending on the lignocellulosic feedstock:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "tags": [ "nbval-ignore-output" ] }, "outputs": [], "source": [ "import thermosteam as tmo\n", "from warnings import filterwarnings\n", "filterwarnings('ignore')\n", "chemicals = tmo.Chemicals(\n", " ['Water', # Define common chemicals by name\n", " 'Ethanol',\n", " 'Octane',\n", " tmo.Chemical('Glucose', phase='s'), # These will always remain as solids\n", " tmo.Chemical('Sucrose', phase='s'), # Specify phase if chemicals not in vapor-liquid equilibrium\n", " tmo.Chemical('H3PO4', phase='s'),\n", " tmo.Chemical('P4O10',\n", " rho=1540, # Density [kg/m3]\n", " default=True, # Default other chemicals properties like viscosity to that of water at 25 C\n", " phase='s'),\n", " tmo.Chemical('CO2', phase='g'), # Assume they will always remain a gas\n", " tmo.Chemical('O2', phase='g'),\n", " tmo.Chemical('Cellulose',\n", " Cp=1.364, # Heat capacity [kJ/kg]\n", " rho=1540, # Density [kg/m3]\n", " default=True, # Default other chemicals properties like viscosity to that of water at 25 C\n", " search_db=False, # Not in database, so do not search the database\n", " phase='s', \n", " formula=\"C6H10O5\", # Glucose monomer minus water, molecular weight is computed based on formula\n", " Hf=-975708.8), # Heat of formation [J/mol]\n", " tmo.Chemical('Hemicellulose',\n", " Cp=1.364,\n", " rho=1540,\n", " default=True,\n", " search_db=False,\n", " phase='s',\n", " formula=\"C5H8O5\", # Xylose monomer minus water\n", " Hf=-761906.4),\n", " tmo.Chemical('Lignin',\n", " Cp=1.364,\n", " rho=1540,\n", " default=True,\n", " search_db=False,\n", " phase='s',\n", " formula='C8H8O3', # Vainillin formula\n", " Hf=-452909.632),\n", " tmo.Chemical('Flocculant',\n", " Cp=4.184,\n", " rho=1540,\n", " default=True,\n", " search_db=False,\n", " phase='s',\n", " MW=1.), # No formula, so molecular weight should be defined\n", " tmo.Chemical('Solids',\n", " Cp=1.100,\n", " rho=1540,\n", " default=True,\n", " search_db=False,\n", " phase='s',\n", " MW=1.),\n", " tmo.Chemical('DryYeast',\n", " rho=1540,\n", " default=True,\n", " search_db=False,\n", " phase='s',\n", " MW=1.,\n", " aliases={'Yeast'}), # We can also give aliases to refer to them later\n", " tmo.Chemical('CaO',\n", " Cp=1.02388,\n", " rho=1540,\n", " default=True,\n", " search_db=False,\n", " phase='s',\n", " formula='CaO'),\n", " tmo.Chemical('Ash',\n", " rho=1540,\n", " Cp=0.37656,\n", " default=True,\n", " search_db=False,\n", " phase='s',\n", " MW=1.)]\n", ")\n", "\n", "# Compile once you are done adding chemicals so that the\n", "# order of chemicals becomes immutable \n", "chemicals.compile() " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Mixture objects" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Before creating a `Thermo` property package, we must define the mixing rules to calculate mixture properties through a `Mixture` object:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "IdealMixture(...\n", " include_excess_energies=False\n", ")\n" ] } ], "source": [ "# Note that the mixture defaults to ideal mixing rules (weighted by mol)\n", "# and excess energies are ignored by default\n", "mixture = tmo.IdealMixture.from_chemicals(chemicals)\n", "mixture.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can use the mixture for estimating mixture properties:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "695" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "array = chemicals.array\n", "mol = array(['Water', 'Ethanol'], [2, 2])\n", "H = mixture.H('l', mol, 300, 101325)\n", "round(H)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "94.07098289709057" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mol = array(['Water', 'Ethanol'], [2, 2])\n", "mixture.Cn('l', mol / mol.sum(), 300) # Normalize composition for result on a molar basis" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also estimate multi-phase mixture properties through methods that start with \"x\" (e.g. `xCn`):" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "574" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mol_liquid = array(['Water', 'Ethanol'], [2, 2])\n", "mol_vapor = array(['Water', 'Ethanol'], [2, 2])\n", "phase_data = [('l', mol_liquid), ('g', mol_vapor)]\n", "Cn = mixture.xCn(phase_data, T=300) # Returns total capacity [J/K] because composition was not normalized\n", "round(Cn)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note: To implement a your own Mixture object, you can request help through https://github.com/BioSTEAMDevelopmentGroup/thermosteam." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Thermo objects" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once the chemicals and mixture objects are finalized, we can compile them into a Thermo object and set the default property package:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Thermo(\n", " chemicals=CompiledChemicals([Water, Ethanol, Octane, Glucose, Sucrose, H3PO4, P4O10, CO2, O2, Cellulose, Hemicellulose, Lignin, Flocculant, Solids, DryYeast, CaO, Ash]),\n", " mixture=IdealMixture(...\n", " include_excess_energies=False\n", " ),\n", " Gamma=DortmundActivityCoefficients,\n", " Phi=IdealFugacityCoefficients,\n", " PCF=MockPoyintingCorrectionFactors\n", ")\n" ] } ], "source": [ "tmo.settings.set_thermo(chemicals, mixture=mixture) # Set the default property package\n", "tmo.settings.thermo.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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):" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Thermo(\n", " chemicals=CompiledChemicals([Water, Ethanol, Octane, Glucose, Sucrose, H3PO4, P4O10, CO2, O2, Cellulose, Hemicellulose, Lignin, Flocculant, Solids, DryYeast, CaO, Ash]),\n", " mixture=IdealMixture(...\n", " include_excess_energies=False\n", " ),\n", " Gamma=DortmundActivityCoefficients,\n", " Phi=IdealFugacityCoefficients,\n", " PCF=MockPoyintingCorrectionFactors\n", ")\n" ] } ], "source": [ "tmo.settings.set_thermo(chemicals)\n", "tmo.settings.get_thermo()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### More rigorous thermodynamic calculations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you have mixtures with non-condensible gases, you may want to use IdealGasPoyintingCorrectionFactors to account for Poyinting effects:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "DewPointValues(T=470.72, P=10000000, IDs=('Butanol', 'CO2'), z=[0.091 0.909], x=[0.997 0.003])" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Without Poyinting correction factors, the dew point is high\n", "tmo.settings.set_thermo(['Butanol', 'CO2'], cache=True)\n", "eq = tmo.equilibrium\n", "s = tmo.Stream(None, Butanol=1, CO2=10, P=1e7)\n", "s.dew_point_at_P()" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "DewPointValues(T=458.21, P=10000000, IDs=('Butanol', 'CO2'), z=[0.091 0.909], x=[0.995 0.005])" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# With Poyinting correction factors, the dew point is lower\n", "tmo.settings.set_thermo(['Butanol', 'CO2'], cache=True, PCF=eq.IdealGasPoyintingCorrectionFactors)\n", "s = tmo.Stream(None, Butanol=1, CO2=10, P=1e7)\n", "s.dew_point_at_P()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may need more rigorous mixing rules and phase equilibrium for high-pressure processes. BioSTEAM features a wide selection of equations of state for estimating excess free energies and fugacity coefficients:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "H_ideal_gas: -306734\n", "MultiStream: ideal_gas\n", "phases: ('g', 'l'), T: 160 K, P: 1e+07 Pa\n", "composition (%): (g) H2 8.55\n", " N2 91\n", " CO2 0.495\n", " --- 236 kg/hr\n", " (l) H2 2.39e-06\n", " N2 13\n", " CO2 87\n", " --- 505 kg/hr\n", "H_eos: -499936\n", "MultiStream: eos\n", "phases: ('g', 'l'), T: 160 K, P: 1e+07 Pa\n", "composition (%): (g) H2 7.75\n", " N2 90.3\n", " CO2 1.94\n", " --- 260 kg/hr\n", " (l) H2 2.51e-06\n", " N2 9.43\n", " CO2 90.6\n", " --- 480 kg/hr\n" ] } ], "source": [ "chemicals = tmo.Chemicals(['H2', 'N2', 'CO2', 'H2O'])\n", "tmo.settings.set_thermo(chemicals)\n", "s_ideal_gas = tmo.Stream('ideal_gas', H2=10, N2=10, CO2=10, phase='g')\n", "s_ideal_gas.vle(T=160, P=1e7)\n", "print('H_ideal_gas:', round(s_ideal_gas.H))\n", "s_ideal_gas.show('cwt')\n", "\n", "mixture = tmo.SRKMixture.from_chemicals(chemicals) # Soave-Redlich-Kuang EOS\n", "tmo.settings.set_thermo(chemicals, mixture=mixture, Phi=tmo.SRKFugacityCoefficients)\n", "s_eos = tmo.Stream('eos', H2=10, N2=10, CO2=10, phase='g')\n", "s_eos.vle(T=160, P=1e7)\n", "print('H_eos:', round(s_eos.H))\n", "s_eos.show('cwt')\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### References" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "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\n", "\n", "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.\n", "\n", "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\n", "\n", "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\n" ] } ], "metadata": { "celltoolbar": "Raw Cell Format", "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.4" } }, "nbformat": 4, "nbformat_minor": 2 }