Thermosteam: BioSTEAM’s Premier Thermodynamic Engine

Thermosteam is a standalone thermodynamic engine capable of estimating mixture properties, solving thermodynamic phase equilibria, and modeling stoichiometric reactions. Thermosteam builds upon chemicals, the chemical properties component of the Chemical Engineering Design Library, with a robust and flexible framework that facilitates the creation of property packages. The Biorefinery Simulation and Techno-Economic Analysis Modules (BioSTEAM) is dependent on thermosteam for the simulation of unit operations.

Key Features & Capabilities

  • Simple and straight foward estimation of mixture properties, thermodynamic phase equilibria, and chemical reactions with just a few lines of code.

  • Clear representation of chemical and phase data within every object using IPython’s rich display system.

  • Fast estimation of thermodynamic equilibrium within hundreds of microseconds through the smart use of cache and Numba Jit compiled functions.

  • Flexible implemention of thermodynamic models for estimating pure component properties in just a few lines of code.

  • Extendable framework that allows easy integration of new methods for computing thermodynamic equilibrium coefficients and mixture properties.

Overview

Thermosteam is an extensive object oriented package for the estimation of thermodynamic equilibrium, mixture properties, and mass and energy balances. The Stream object is the main interface for performing these calculations. Before creating streams, a thermodynamic property package must be defined through a Thermo object, which compiles the working chemicals, mixing rules, and the equlibrium estimation methods. Each Chemical object contains model handles that manages the thermodynamic models and makes sure to use a valid model at given temperatures and pressures whenever estimating properties. The functional algorithms for estimating pure component properties are presented as functors which implicitly store and use fitted coefficients for the estimation of temperature and pressure dependent properties.

_images/class_diagram.png

A functor-oriented class diagram describes the relationship between the core classes of Thermosteam. Overall, the diagram follows the Universal Modeling Language (UML) guidelines except for a few modifications. In particular, the function signature of callable objects is presented after their name. For example: an ActivityCoefficients object returns an array of activity coefficients when called with the liquid molar composition (x) and temperature (T); and a TPFunctor object returns the value of a chemical property when called with the temperature (T), and pressure (P). This is in contrast to traditional UML class diagrams where the name of parent class should come after the class name (e.g. TPFunctor(Functor) instead of TPFunctor(T, P)). Similar to UML class diagrams, however, the italized classes are abstract, meaning that child classes must implement the abstract attributes and methods (italized as well) to become functional. Function signatures with a “…” are abstract signatures so that a child class may implement a variety of signatures.

Installation

Get the latest version of Thermosteam from PyPI. If you have an installation of Python with pip, simple install it with:

$ pip install thermosteam

To get the git version and install it, run:

$ git clone --depth 100 git://github.com/BioSTEAMDevelopmentGroup/thermosteam
$ cd thermosteam
$ pip install .

We use the depth option to clone only the last 100 commits. Thermosteam has a long history, so cloning the whole repository (without using the depth option) may take over 30 min.

Common Issues

  • Cannot install/update Thermosteam:

    If you are having trouble installing or updating Thermosteam, it may be due to dependency issues. You can bypass these using:

    $ pip install --user --ignore-installed thermosteam
    

    You can make sure you install the right version by including the version number:

    $ pip install thermosteam==<version>
    
{
“cells”: [
{

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“# An hour blitz to practical thermodynamics”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Pure component chemical models”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Thermosteam packages chemical and mixture thermodynamic models in a flexible framework that allows users to fully customize and extend the models, as well as create new models. Central to all thermodynamic algorithms is the [Chemical](../Chemical.txt) object, which contains constant chemical properties, as well as thermodynamic and transport properties as a function of temperature and pressure:”

]

}, {

“cell_type”: “code”, “execution_count”: 1, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Chemical: Water (phase_ref=’l’)n”, “[Names] CAS: 7732-18-5n”, ” InChI: H2O/h1H2n”, ” InChI_key: XLYOFNOQVPJJNP-U…n”, ” common_name: watern”, ” iupac_name: (‘oxidane’,)n”, ” pubchemid: 962n”, ” smiles: On”, ” formula: H2On”, “[Groups] Dortmund: <1H2O>n”, ” UNIFAC: <1H2O>n”, ” PSRK: <1H2O>n”, “[Data] MW: 18.015 g/moln”, ” Tm: 273.15 Kn”, ” Tb: 373.12 Kn”, ” Tt: 273.15 Kn”, ” Tc: 647.14 Kn”, ” Pt: 610.88 Pan”, ” Pc: 2.2048e+07 Pan”, ” Vc: 5.6e-05 m^3/moln”, ” Hf: -2.8582e+05 J/moln”, ” S0: 70 J/K/moln”, ” LHV: 44011 J/moln”, ” HHV: 0 J/moln”, ” Hfus: 6010 J/moln”, ” Sfus: Nonen”, ” omega: 0.344n”, ” dipole: 1.85 Debyen”, ” similarity_variable: 0.16653n”, ” iscyclic_aliphatic: 0n”, ” combustion: {‘H2O’: 1.0}n”

]

}

], “source”: [

“import thermosteam as tmon”, “# Initialize chemical with an identifier (e.g. by name, CAS, InChI…)n”, “Water = tmo.Chemical(‘Water’) n”, “Water.show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“All fields can be easility accessed, for example:”

]

}, {

“cell_type”: “code”, “execution_count”: 2, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“‘7732-18-5’”

]

}, “execution_count”: 2, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# CAS numbern”, “Water.CAS”

]

}, {

“cell_type”: “code”, “execution_count”: 3, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“18.01528”

]

}, “execution_count”: 3, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Molecular weight (g/mol)n”, “Water.MW”

]

}, {

“cell_type”: “code”, “execution_count”: 4, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“373.124”

]

}, “execution_count”: 4, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Boiling point (K)n”, “Water.Tb”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Temperature (in Kelvin) and pressure (in Pascal) dependent properties can be computed:”

]

}, {

“cell_type”: “code”, “execution_count”: 5, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“101284.55179999319”

]

}, “execution_count”: 5, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Vapor pressure (Pa)n”, “Water.Psat(T=373.15)”

]

}, {

“cell_type”: “code”, “execution_count”: 6, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“0.07197220523022964”

]

}, “execution_count”: 6, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Surface tension (N/m)n”, “Water.sigma(T=298.15)”

]

}, {

“cell_type”: “code”, “execution_count”: 7, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“1.8069204487889095e-05”

]

}, “execution_count”: 7, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Liquid molar volume (m^3/mol)n”, “Water.V(phase=’l’, T=298.15, P=101325)”

]

}, {

“cell_type”: “code”, “execution_count”: 8, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“0.023505774739491968”

]

}, “execution_count”: 8, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Vapor molar volume (m^3/mol)n”, “Water.V(phase=’g’, T=298.15, P=101325)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Temperature dependent properties are managed by indexable model handles, which contain many models ordered in decreasing priority:”

]

}, {

“cell_type”: “code”, “execution_count”: 9, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“TDependentModelHandle(T, P=None) -> Psat [Pa]n”, “[0] Wagner originaln”, “[1] Antoinen”, “[2] EQ101n”, “[3] Wagnern”, “[4] boiling critical relationn”, “[5] Lee Keslern”, “[6] Ambrose Waltonn”, “[7] Sanjarin”, “[8] Edalatn”

]

}

], “source”: [

“Water.Psat.show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Each model is applicable to a certain domain, as given by their Tmin and Tmax:”

]

}, {

“cell_type”: “code”, “execution_count”: 10, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“TDependentModel(T, P=None) -> Psat [Pa]n”, ” name: Wagner originaln”, ” Tmin: 275 Kn”, ” Tmax: 647.35 Kn”

]

}

], “source”: [

“Wagner_origial = Water.Psat[0]n”, “Wagner_origial.show()”

]

}, {

“cell_type”: “code”, “execution_count”: 11, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“(647.35, 275.0)”

]

}, “execution_count”: 11, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Note that these attributes can be get/set toon”, “Wagner_origial.Tmax, Wagner_origial.Tmin”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“When called, the model handle searches through each model until it finds one with an applicable domain. If none are applicable, a domain error is raised:”

]

}, {

“cell_type”: “code”, “execution_count”: 12, “metadata”: {}, “outputs”: [

{

“ename”: “DomainError”, “evalue”: “Water (CAS: 7732-18-5) has no valid saturated vapor pressure model at T=1000.00 K”, “output_type”: “error”, “traceback”: [

“u001b[1;31m—————————————————————————u001b[0m”, “u001b[1;31mDomainErroru001b[0m Traceback (most recent call last)”, “u001b[1;32m<ipython-input-12-5818a3190dca>u001b[0m in u001b[0;36m<module>u001b[1;34mu001b[0mnu001b[1;32m—-> 1u001b[1;33m u001b[0mWateru001b[0mu001b[1;33m.u001b[0mu001b[0mPsatu001b[0mu001b[1;33m(u001b[0mu001b[1;36m1000.0u001b[0mu001b[1;33m)u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[0m”, “u001b[1;32m~\OneDrive\Code\thermosteam\thermosteam\base\thermo_model_handle.pyu001b[0m in u001b[0;36m__call__u001b[1;34m(self, T, P)u001b[0mnu001b[0;32m 284u001b[0m u001b[1;32mifu001b[0m u001b[0mmodelu001b[0mu001b[1;33m.u001b[0mu001b[0mindomainu001b[0mu001b[1;33m(u001b[0mu001b[0mTu001b[0mu001b[1;33m)u001b[0mu001b[1;33m:u001b[0m u001b[1;32mreturnu001b[0m u001b[0mmodelu001b[0mu001b[1;33m.u001b[0mu001b[0mevaluateu001b[0mu001b[1;33m(u001b[0mu001b[0mTu001b[0mu001b[1;33m)u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[0;32m 285u001b[0m raise DomainError(f"{no_valid_model(self._chemical, self._var)} "nu001b[1;32m–> 286u001b[1;33m f"at T={T:.2f} K", chemical=self._chemical)nu001b[0mu001b[0;32m 287u001b[0m u001b[1;33mu001b[0mu001b[0mnu001b[0;32m 288u001b[0m u001b[0mat_Tu001b[0m u001b[1;33m=u001b[0m u001b[0m__call__u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mn”, “u001b[1;31mDomainErroru001b[0m: Water (CAS: 7732-18-5) has no valid saturated vapor pressure model at T=1000.00 K”

]

}

], “source”: [

“Water.Psat(1000.0)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Model handles as well as the models themselves have tabulation and plotting methods to help visualize how properties depend on temperature and pressure.”

]

}, {

“cell_type”: “code”, “execution_count”: 13, “metadata”: {}, “outputs”: [

{
“data”: {

“image/png”: “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n”, “text/plain”: [

“<Figure size 432x288 with 1 Axes>”

]

}, “metadata”: {

“needs_background”: “light”

}, “output_type”: “display_data”

}

], “source”: [

“import matplotlib.pyplot as pltn”, “Water.Psat.plot_vs_T([Water.Tm, Water.Tb], ‘degC’, ‘atm’, label="Water")n”, “plt.show()”

]

}, {

“cell_type”: “code”, “execution_count”: 14, “metadata”: {}, “outputs”: [

{
“data”: {

“image/png”: “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n”, “text/plain”: [

“<Figure size 432x288 with 1 Axes>”

]

}, “metadata”: {

“needs_background”: “light”

}, “output_type”: “display_data”

}

], “source”: [

“# Plot all modelsn”, “Water.Psat.plot_models_vs_T([Water.Tm, Water.Tb], ‘degC’, ‘atm’)n”, “plt.show()”

]

}, {

“cell_type”: “code”, “execution_count”: 15, “metadata”: {}, “outputs”: [

{
“data”: {

“image/png”: “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n”, “text/plain”: [

“<Figure size 432x288 with 1 Axes>”

]

}, “metadata”: {

“needs_background”: “light”

}, “output_type”: “display_data”

}

], “source”: [

“# Plot only the ‘Wagner original model’n”, “Water.Psat[0].plot_vs_T(T_units=’degC’, units=’atm’) # Bounds are the model’s Tmin and Tmaxn”, “plt.show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Manage the model order with the set_model_priority and move_up_model_priority methods:”

]

}, {

“cell_type”: “code”, “execution_count”: 16, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“TDependentModel(T, P=None) -> Psat [Pa]n”, ” name: Antoinen”, ” Tmin: 273.2 Kn”, ” Tmax: 473.2 Kn”

]

}

], “source”: [

“# Note: In this case, we pass the model name, but itsn”, “# also possible to pass the current index, or the model itself.n”, “Water.Psat.move_up_model_priority(‘Antoine’)n”, “Water.Psat[0].show() # Notice how Antoine is now in the top priority”

]

}, {

“cell_type”: “code”, “execution_count”: 17, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“TDependentModel(T, P=None) -> Psat [Pa]n”, ” name: Wagner originaln”, ” Tmin: 275 Kn”, ” Tmax: 647.35 Kn”

]

}

], “source”: [

“Water.Psat.set_model_priority(‘Wagner original’)n”, “Water.Psat[0].show() # Notice how Wagner original is back on top priority”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“When setting a model priority, the default priority is 0 (or top priority), but you can choose any priority:”

]

}, {

“cell_type”: “code”, “execution_count”: 18, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“TDependentModel(T, P=None) -> Psat [Pa]n”, ” name: Antoinen”, ” Tmin: 273.2 Kn”, ” Tmax: 473.2 Kn”

]

}

], “source”: [

“Water.Psat.set_model_priority(‘Antoine’, 2)n”, “Water.Psat[2].show() # Moved Antoine to priority #2”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Thermodynamic properties dependent on the phase are handled by phase properties:”

]

}, {

“cell_type”: “code”, “execution_count”: 19, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“PhaseTPHandle(phase, T, P) -> V [m^3/mol]n”

]

}

], “source”: [

“Water.V.show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Phase properties contain model handles as attributes:”

]

}, {

“cell_type”: “code”, “execution_count”: 20, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“TPDependentModelHandle(T, P) -> V.l [m^3/mol]n”, “[0] volume VDI PPDSn”, “[1] Campbell Thodosn”, “[2] Yen Woods saturationn”, “[3] Rackettn”, “[4] Yamada Gunnn”, “[5] Bhirud normaln”, “[6] Townsend Halesn”, “[7] CRC inorganic liquid constantn”, “[8] Rackettn”, “[9] COSTALDn”, “[10] COSTALD compressedn”

]

}

], “source”: [

“Water.V.l.show()”

]

}, {

“cell_type”: “code”, “execution_count”: 21, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“TPDependentModelHandle(T, P) -> V.g [m^3/mol]n”, “[0] Tsonopoulos extendedn”, “[1] Tsonopoulosn”, “[2] Abbottn”, “[3] Pitzer Curln”, “[4] CRCVirialn”, “[5] ideal gasn”

]

}

], “source”: [

“Water.V.g.show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“A new model can be added easily to a model handle through the add_model method, for example:”

]

}, {

“cell_type”: “code”, “execution_count”: 22, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“TDependentModel(T) -> Psat [Pa]n”, ” name: User antoine modeln”, ” Tmin: 273.2 Kn”, ” Tmax: 473.2 Kn”

]

}

], “source”: [

“# Set top_priority=True to place model in postion [0]n”, “@Water.Psat.add_model(Tmin=273.20, Tmax=473.20, top_priority=True)n”, “def User_antoine_model(T):n”, ” return 10.0**(10.116 - 1687.537 / (T - 42.98))n”, “Water.Psat[0].show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“The add_model method is a high level interface that even lets you create a constant model:”

]

}, {

“cell_type”: “code”, “execution_count”: 23, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“ConstantThermoModel(T=None, P=None) -> V.l [m^3/mol]n”, ” name: User constantn”, ” value: 1.687e-05n”, ” Tmin: 0 Kn”, ” Tmax: inf Kn”, ” Pmin: 0 Pan”, ” Pmax: inf Pan”

]

}

], “source”: [

“Water.V.l.add_model(1.687e-05, name=’User constant’)n”, “# Model is appended at the end by defaultn”, “Water.V.l[-1].show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Lastly, all default models in thermosteam have functors (i.e. functions with adjustable parameters):”

]

}, {

“cell_type”: “code”, “execution_count”: 24, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Functor: Wagner_original(T, P=None) -> Psat [Pa]n”, ” Tc: 647.35 Kn”, ” Pc: 2.2122e+07 Pan”, ” a: -7.7645n”, ” b: 1.4584n”, ” c: -2.7758n”, ” d: -1.233n”

]

}

], “source”: [

“# The saturated vapor pressure model from beforen”, “Wagner_origial.evaluate.show()”

]

}, {

“cell_type”: “code”, “execution_count”: 25, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Functor: Wagner_original(T, P=None) -> Psat [Pa]n”, ” Tc: 647.35 Kn”, ” Pc: 2.2064e+07 Pan”, ” a: -7.7645n”, ” b: 1.4584n”, ” c: -2.7758n”, ” d: -1.233n”

]

}

], “source”: [

“Wagner_origial.evaluate.Pc = 22.064e6n”, “Wagner_origial.evaluate.show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“## Managing chemical sets”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Define multiple chemicals as a [Chemicals](../Chemicals.txt) object:”

]

}, {

“cell_type”: “code”, “execution_count”: 26, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Chemicals([Water, Ethanol])n”

]

}

], “source”: [

“chemicals = tmo.Chemicals([‘Water’, ‘Ethanol’])n”, “chemicals”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“The chemicals are attributes:”

]

}, {

“cell_type”: “code”, “execution_count”: 27, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“(Chemical(‘Water’), Chemical(‘Ethanol’))”

]

}, “execution_count”: 27, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“(chemicals.Water, chemicals.Ethanol)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Chemicals are indexable:”

]

}, {

“cell_type”: “code”, “execution_count”: 28, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Chemical(‘Water’)n”

]

}

], “source”: [

“Water = chemicals[‘Water’]n”, “print(repr(Water))”

]

}, {

“cell_type”: “code”, “execution_count”: 29, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“[Chemical(‘Ethanol’), Chemical(‘Water’)]”

]

}, “execution_count”: 29, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“chemicals[‘Ethanol’, ‘Water’]”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Chemicals are also iterable:”

]

}, {

“cell_type”: “code”, “execution_count”: 30, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Chemical(‘Water’)n”, “Chemical(‘Ethanol’)n”

]

}

], “source”: [

“for chemical in chemicals:n”, ” print(repr(chemical))”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“More chemicals can also be appended:”

]

}, {

“cell_type”: “code”, “execution_count”: 31, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Chemicals([Water, Ethanol, Propanol])n”

]

}

], “source”: [

“Propanol = tmo.Chemical(‘Propanol’)n”, “chemicals.append(Propanol)n”, “chemicals”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“The main benefit of using a Chemicals object, is that they can be compiled and used as part of a thermodynamic property package, as defined through a [Thermo](../Thermo.txt) object:”

]

}, {

“cell_type”: “code”, “execution_count”: 32, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Thermo(n”, ” chemicals=CompiledChemicals([Water, Ethanol, Propanol]),n”, ” mixture=Mixture(n”, ” rule=’ideal mixing’, …n”, ” include_excess_energies=Falsen”, ” ),n”, ” Gamma=DortmundActivityCoefficients,n”, ” Phi=IdealFugacityCoefficients,n”, ” PCF=IdealPoyintingCorrectionFactorsn”, “)n”

]

}

], “source”: [

“# A Thermo object is built with an iterable of Chemicals or their IDs.n”, “# Default mixture, thermodynamic equilibrium models are selected.n”, “thermo = tmo.Thermo(chemicals)n”, “thermo.show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“[Creating a thermo property package](./Thermo_property_packages.ipynb), may be a little challenging if some chemicals cannot be found in the database, in which case they can be built from scratch. A complete example on how this can be done is available in another [tutorial](./Thermo_property_packages.ipynb).”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Material and energy balance”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“A [Stream](../Stream.txt) object is the main interface for estimating thermodynamic properties, vapor-liquid equilibrium, and material and energy balances. First set the thermo property package and we can start creating streams:”

]

}, {

“cell_type”: “code”, “execution_count”: 33, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Stream: s1n”, ” phase: ‘l’, T: 298.15 K, P: 101325 Pan”, ” flow (kg/hr): Water 20n”, ” Ethanol 20n”

]

}

], “source”: [

“tmo.settings.set_thermo(thermo)n”, “s1 = tmo.Stream(‘s1’, Water=20, Ethanol=20, units=’kg/hr’)n”, “s1.show(flow=’kg/hr’)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Create another stream at a higher temperature:”

]

}, {

“cell_type”: “code”, “execution_count”: 34, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Stream: s2n”, ” phase: ‘l’, T: 350 K, P: 101325 Pan”, ” flow (kg/hr): Water 10n”

]

}

], “source”: [

“s2 = tmo.Stream(‘s2’, Water=10, units=’kg/hr’, T=350, P=101325)n”, “s2.show(flow=’kg/hr’)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Mix both stream into a new one:”

]

}, {

“cell_type”: “code”, “execution_count”: 35, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Stream: s_mixn”, ” phase: ‘l’, T: 310.53 K, P: 101325 Pan”, ” flow (kg/hr): Water 30n”, ” Ethanol 20n”

]

}

], “source”: [

“s_mix = tmo.Stream(‘s_mix’)n”, “s_mix.mix_from([s1, s2])n”, “s_mix.show(flow=’kg/hr’)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Check the energy balance through enthalpy:”

]

}, {

“cell_type”: “code”, “execution_count”: 36, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“9.094947017729282e-12”

]

}, “execution_count”: 36, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“s_mix.H - (s1.H + s2.H)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Note that the balance is not perfect as the solver stops within a small temperature tolerance. However, the approximation is less than 0.01% off:”

]

}, {

“cell_type”: “code”, “execution_count”: 37, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“0.00%n”

]

}

], “source”: [

“error = s_mix.H - (s1.H + s2.H)n”, “percent_error = 100 * error / (s1.H + s2.H)n”, “print(f"{percent_error:.2%}")”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Split the mixture to two streams by defining the component splits:”

]

}, {

“cell_type”: “code”, “execution_count”: 38, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Stream: s1n”, ” phase: ‘l’, T: 310.53 K, P: 101325 Pan”, ” flow (kg/hr): Ethanol 20n”, “Stream: s2n”, ” phase: ‘l’, T: 310.53 K, P: 101325 Pan”, ” flow (kg/hr): Water 30n”

]

}

], “source”: [

“# First define an array of component splitsn”, “component_splits = s_mix.chemicals.array([‘Water’, ‘Ethanol’], [0, 1])n”, “s_mix.split_to(s1, s2, component_splits)n”, “s1.T = s2.T = s_mix.T # Take care of energy balancen”, “s1.show(flow=’kg/hr’)n”, “s2.show(flow=’kg/hr’)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Flow rates”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“The most convenient way to get and set flow rates is through the get_flow and set_flow methods:”

]

}, {

“cell_type”: “code”, “execution_count”: 39, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“1.0”

]

}, “execution_count”: 39, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Set and get flow of a single chemicaln”, “# in gallons per minuten”, “s1.set_flow(1, ‘gpm’, ‘Water’)n”, “s1.get_flow(‘gpm’, ‘Water’)”

]

}, {

“cell_type”: “code”, “execution_count”: 40, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“array([10., 20.])”

]

}, “execution_count”: 40, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Set and get flows of many chemicalsn”, “# in kilograms per hourn”, “s1.set_flow([10, 20], ‘kg/hr’, (‘Ethanol’, ‘Water’))n”, “s1.get_flow(‘kg/hr’, (‘Ethanol’, ‘Water’))”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“It is also possible to index flow rate data using chemical IDs through the imol, imass, and ivol [indexers](../indexer/indexer_module.txt):”

]

}, {

“cell_type”: “code”, “execution_count”: 41, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“ChemicalMolarFlowIndexer (kmol/hr):n”, ” (l) Water 1.11n”, ” Ethanol 0.2171n”

]

}

], “source”: [

“s1.imol.show()”

]

}, {

“cell_type”: “code”, “execution_count”: 42, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“1.1101687012358397”

]

}, “execution_count”: 42, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“s1.imol[‘Water’]”

]

}, {

“cell_type”: “code”, “execution_count”: 43, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“array([0.217, 1.11 ])”

]

}, “execution_count”: 43, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“s1.imol[‘Ethanol’, ‘Water’]”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“All flow rates are stored as an array in the mol attribute:”

]

}, {

“cell_type”: “code”, “execution_count”: 44, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“array([1.11 , 0.217, 0. ])”

]

}, “execution_count”: 44, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“s1.mol # Molar flow rates [kmol/hr]”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Mass and volumetric flow rates are available as [property arrays](https://free-properties.readthedocs.io/en/latest/property_array.html):”

]

}, {

“cell_type”: “code”, “execution_count”: 45, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“property_array([<Water: 20 kg/hr>, <Ethanol: 10 kg/hr>,n”, ” <Propanol: 0 kg/hr>])”

]

}, “execution_count”: 45, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“s1.mass”

]

}, {

“cell_type”: “code”, “execution_count”: 46, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“property_array([<Water: 0.020166 m^3/hr>, <Ethanol: 0.012898 m^3/hr>,n”, ” <Propanol: 0 m^3/hr>])”

]

}, “execution_count”: 46, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“s1.vol”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“These arrays work just like ordinary arrays, but the data is linked to the molar flows:”

]

}, {

“cell_type”: “code”, “execution_count”: 47, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“<Water: 18.015 kg/hr>”

]

}, “execution_count”: 47, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Mass flows are always up to date with molar flowsn”, “s1.mol[0] = 1n”, “s1.mass[0]”

]

}, {

“cell_type”: “code”, “execution_count”: 48, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“2.0”

]

}, “execution_count”: 48, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Changing mass flows changes molar flowsn”, “s1.mass[0] *= 2n”, “s1.mol[0]”

]

}, {

“cell_type”: “code”, “execution_count”: 49, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“array([38.031, 12. , 2. ])”

]

}, “execution_count”: 49, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Property arrays act just like normal arraysn”, “s1.mass + 2 # A new array is created”

]

}, {

“cell_type”: “code”, “execution_count”: 50, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“15.34352”

]

}, “execution_count”: 50, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Array methods are also the samen”, “s1.mass.mean()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Thermal condition”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Temperature and pressure can be get and set through the T and P attributes:”

]

}, {

“cell_type”: “code”, “execution_count”: 51, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Stream: s1n”, ” phase: ‘l’, T: 400 K, P: 202650 Pan”, ” flow (kmol/hr): Water 2n”, ” Ethanol 0.217n”

]

}

], “source”: [

“s1.T = 400.n”, “s1.P = 2 * 101325.n”, “s1.show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“The phase may also be changed (‘s’ for solid, ‘l’ for liquid, and ‘g’ for gas):”

]

}, {

“cell_type”: “code”, “execution_count”: 52, “metadata”: {}, “outputs”: [], “source”: [

“s1.phase = ‘g’”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Notice that VLE is not enforced, but it is possible to perform. For now, just check that the dew point is lower than the actual temperature to assert it must be gas:”

]

}, {

“cell_type”: “code”, “execution_count”: 53, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“DewPointValues(T=390.84, P=202650, IDs=(‘Water’, ‘Ethanol’), z=[0.902 0.098], x=[0.991 0.009])”

]

}, “execution_count”: 53, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“dp = s1.dew_point_at_P() # Dew point at constant pressuren”, “dp”

]

}, {

“cell_type”: “code”, “execution_count”: 54, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“True”

]

}, “execution_count”: 54, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“dp.T < s1.T”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“It is also possible to get and set in other units of measure:”

]

}, {

“cell_type”: “code”, “execution_count”: 55, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“1.0”

]

}, “execution_count”: 55, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“s1.set_property(‘P’, 1, ‘atm’)n”, “s1.get_property(‘P’, ‘atm’)”

]

}, {

“cell_type”: “code”, “execution_count”: 56, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“256.99999999999994”

]

}, “execution_count”: 56, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“s1.set_property(‘T’, 125, ‘degC’)n”, “s1.get_property(‘T’, ‘degF’)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Enthalpy can also be set. An energy balance is made to solve for temperature at isobaric conditions:”

]

}, {

“cell_type”: “code”, “execution_count”: 57, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“130.80215821464316”

]

}, “execution_count”: 57, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“s1.H = s1.H + 500n”, “s1.get_property(‘T’, ‘degC’) # Temperature should go up”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Thermal properties”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Thermodynamic properties are pressure, temperature and phase dependent. In the following examples, let’s just use water as it is easier to check properties:”

]

}, {

“cell_type”: “code”, “execution_count”: 58, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“997.0156689562489”

]

}, “execution_count”: 58, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“s_water = tmo.Stream(‘s_water’, Water=1, units=’kg/hr’)n”, “s_water.rho # Density [kg/m^3]”

]

}, {

“cell_type”: “code”, “execution_count”: 59, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“971.4430230945908”

]

}, “execution_count”: 59, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“s_water.T = 350n”, “s_water.rho # Density changes”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Get properties in different units:”

]

}, {

“cell_type”: “code”, “execution_count”: 60, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“0.06324769600985489”

]

}, “execution_count”: 60, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“s_water.get_property(‘sigma’, ‘N/m’) # Surface tension”

]

}, {

“cell_type”: “code”, “execution_count”: 61, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“0.01854486528979459”

]

}, “execution_count”: 61, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“s_water.get_property(‘V’, ‘m3/kmol’) # Molar volume”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Flow properties”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Several flow properties are available, such as net material and energy flow rates:”

]

}, {

“cell_type”: “code”, “execution_count”: 62, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“0.05550843506179199”

]

}, “execution_count”: 62, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Net molar flow rate [kmol/hr]n”, “s_water.F_mol”

]

}, {

“cell_type”: “code”, “execution_count”: 63, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“1.0”

]

}, “execution_count”: 63, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Net mass flow rate [kg/hr]n”, “s_water.F_mass”

]

}, {

“cell_type”: “code”, “execution_count”: 64, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“0.0010293964506682433”

]

}, “execution_count”: 64, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Net volumetric flow rate [m3/hr]n”, “s_water.F_vol”

]

}, {

“cell_type”: “code”, “execution_count”: 65, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“216.85387645424356”

]

}, “execution_count”: 65, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Enthalpy flow rate [kJ/hr]n”, “s_water.H”

]

}, {

“cell_type”: “code”, “execution_count”: 66, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“4.556131378540336”

]

}, “execution_count”: 66, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Entropy flow rate [kJ/hr]n”, “s_water.S”

]

}, {

“cell_type”: “code”, “execution_count”: 67, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“4.197680667946338”

]

}, “execution_count”: 67, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Capacity flow rate [J/K]n”, “s_water.C”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Thermodynamic equilibrium”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Before moving into performing vapor-liquid and liquid-liquid equilibrium calculations, it may be useful to have a look at the phase envelopes to understand chemical interactions and ultimately how they separate between phases.”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Plot the binary phase evelope of two chemicals in vapor-liquid equilibrium at constant pressure:”

]

}, {

“cell_type”: “code”, “execution_count”: 68, “metadata”: {}, “outputs”: [

{
“data”: {

“image/png”: “iVBORw0KGgoAAAANSUhEUgAAAZ4AAAEFCAYAAADT3YGPAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjMsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+AADFEAAAgAElEQVR4nOzdd1gUV9vH8e8BlmLBgkhVwC5gi9g1lmg0MT1RY57kSWKMRmPvvRtrYm9oetWYxFTzxBIjRo29d6xYUAEFFKWd949deNWALi4wlPtzXXPBzs7M3rsgP8+ZM2eU1hohhBAit9gZXYAQQojCRYJHCCFErpLgEUIIkaskeIQQQuQqCR4hhBC5SoJHCCFErnIwugCjKKW0nZ3kbnbQWqOUMrqMAkM+z+wln2f2Sk1NRWtt0wdaaIMHICUlxegSCoSQkBB27NhhdBkFhnye2Us+z+yVHSEu/+UXQgiRqyR4hBBC5KpCGzzS55t9unXrZnQJBYp8ntlLPs9sZ/M8a6qwztVmb2+v5RyPsFZSUhIRERHcunXL6FKEyBXOzs74+vpiMpnuWq+UStVa29ty7BwLHqWUM7ARcMI8iGGl1nqsUioMKG7ZrCywTWv9nFLqWWAikAokA/201psyOO5k4L9AKa11sXue6wiMw5zIe7XWr2RWnwSPyIpTp05RvHhx3NzcpLUsCjytNVFRUcTFxREQEHDXc9kRPDk5qu020EprHa+UMgGblFKrtdbN0jZQSn0H/Gh5uA74SWutlVI1gRVAtQyO+zMwHzh+50qlVGVgONBEax2jlCqb/W9JFFa3bt3C399fQkcUCkop3NzcuHLlSo4cP8eCR5ubUvGWhybLkt68UkoVB1oBb1q2j79j96Jk0o+otd5q2f/ep94GFmitYyzbXbb5TQhxBwkdUZjk5O97jg4uUErZK6X2AJeBNVrrf+54+nlgndY69o7tn1dKHQF+Bbpk8eWqAFWUUn8rpbYqpdrdb+PU1FRu376dxZcQwjj29vbUrl07fZk6dSoAs2fP5ubNm+nbFStWLLNDZJuMXuPatWu4ubmR1n2/ZcsWlFJEREQAcP36dUqXLk1qamqmx121ahWHDh2yqbYNGzZQokQJ6tSpQ/Xq1Rk/fjwAn3zyCb169bLp2Fmt4c6f19q1a7P1Nd544w1WrlyZrcfMLTl6AanWOgWorZQqCfyglArWWh+wPN0ZWHbP9j9YtnsU8/me1ll4OQegMtAC8AXCLK937c6NlFLdgG4Aj7Vtw3NPP8uggQOz/uaEyGUuLi7s2bPnX+tnz57Nq6++SpEiRQyo6v+VLFkST09PDh8+TGBgIJs3b6ZOnTps3ryZjh07snXrVho0aMD9ZgxZtWoVTz31FIGBgVa/bnJyMg4Od/8pa9asGb/88gs3btygdu3aPPXUUw/9vh5WWg35XWhoKKGhoXeusrkplCvDqS1//DcA7QCUUm5Afcwtm4y23whUVEqVycLLRAA/aq2TtNangKOYg+jeY4dqrUMASpYsxZo/17Huz/VZeTtC5Blz587lwoULtGzZkpYtW6avHzlyJLVq1aJhw4ZERkYC8PPPP9OgQQPq1KlD69at09ePGzeOLl260KJFCypUqMDcuXPTj/PBBx8QHBxMcHAws2fPfmA9TZo0YfPmzQBs3ryZ/v373/W4cePGACxdupR69epRq1YtXnzxRW7evMnmzZv56aefGDx4MLVr1yY8PJzw8HDatWtH3bp1adasGUeOHAHM/9sfMGAALVu2ZOjQoZnWU7RoUerWrUt4eDgAFy5coF27dlSuXJkhQ4akb9ejRw9CQkIICgpi7Nix6euHDRtGYGAgNWvWZNCgQQBcuXKFF198kXr16lGvXj3+/vvvB34uaU6fPk316tV5++23CQoK4vHHHychIYHDhw9Tv379u7arWbMmADt37qR58+bUrVuXtm3bcvHixX8dd926ddSpU4caNWrQpUuX9N4cf39/hg4dSv369alfvz4nTpzI8nvo1q0bO3bsSF/IhuHUaK1zZAHcgZKW712AMOApy+N3gE/v2b4S/z/K7hHgfNrjTI4ff8/jdmnHBMoA5wC3++yvL0VG6h6939XNWrXQM2d9oG/dvq2FyMihQ4fSv58zf57u3b9vti5z5s97YA12dna6Vq1a6cs333yjtdbaz89PX7lyJX07QP/0009aa60HDx6sJ06cqLXWOjo6WqempmqttV66dKkeMGCA1lrrsWPH6kaNGulbt27pK1eu6NKlS+vExES9Y8cOHRwcrOPj43VcXJwODAzUu3bt0lprXbRo0Qxr/Pjjj/Wbb76ptda6du3aOiEhQTdp0kRrrXXr1q31unXrtNZaX716NX2fkSNH6rlz52qttX799df1t99+m/5cq1at9LFjx7TWWm/dulW3bNkyfbv27dvr5OTkf9Xw559/6vbt26e/jp+fnz5w4ID++OOPdUBAgL527ZpOSEjQ5cuX12fPntVaax0VFaW11jo5OVk3b95c7927V0dFRekqVaqkf2YxMTFaa607d+6sw8LCtNZanzlzRlerVi3DGlxdXe/6eZ04cUKfOnVK29vb6927d2utte7QoYP+/PPPtdZa16pVS4eHh2uttZ46daqeOHGiTkxM1I0aNdKXL1/WWmv9zTffpH++aZ9VQkKC9vX11UePHtVaa/3aa6/pWbNmaa3NvxuTJk3SWmv96aefpn8u1rwHre/+vU8DpGgb8yEnu9q8gE+VUvaYW1YrtNZp7c6Xgan3bP8i8F+lVBKQAHSyvEmUUnu01rUt308HXgGKKKUigGVa63HA/4DHlVKHgBRgsNY66n4FlnFzY+4Hs1n64TK+XrGcQ4cPMX7MOHx9fLLh7QuRvTLraruXo6NjetdS3bp1WbNmDQARERF06tSJixcvkpiYeNcw2fbt2+Pk5ISTkxNly5YlMjKSTZs28fzzz1O0aFEAXnjhBcLCwqhTp06mr92kSROmTp3KqVOn8Pf3x9nZGa018fHx7Ny5M/1/9QcOHGDUqFFcu3aN+Ph42rZt+69jxcfHs3nzZjp06JC+7s7zsh06dMDePuNRvWl12tnZMWzYMIKCgti+fTuPPfYYJUqUACAwMJAzZ85Qrlw5VqxYQWhoKMnJyVy8eJFDhw4RGBiIs7MzXbt2pX379umf6dq1a+86DxUbG0tcXBzFixe/q4aMutpOnz5NQEAAtWvXTv/5nD59GoCOHTuyYsUKhg0bxvLly1m+fDlHjx7lwIEDtGnTBjDPL+nl5XXXMY8ePUpAQABVqlQB4PXXX2fBggX069cPgM6dO6d/7d+/f5beQ07JyVFt+4AMf0O11i0yWDcNmJbJ9rXv+H4IMCSDbTQwwLJY5fKVK3h5etKj+zvUqlmL96ZNpes73RgycBCtWrR88AFEodTn3dw5Qf2wTCZT+ogke3t7kpOTAejduzcDBgzgmWeeYcOGDYwbNy59Hycnp/Tv0/bRVlzjN3LkSH791dxjvmfPHipXrkxMTAw///wzjRo1Asx/XD/++GMCAgLSByW88cYbrFq1ilq1avHJJ5+wYcOGfx07NTWVkiVLZhq2aYGYkczOr2T0Pk+dOsXMmTPZvn07pUqV4o033uDWrVs4ODiwbds21q1bxzfffMP8+fNZv349qampbNmyBRcXlwd+Phm5t4aEhAQAOnXqRIcOHXjhhRdQSlG5cmX2799PUFAQW7ZsyfR4D/o53Tk6Le17W9+DrQrtlDlA+mgbgMaNGvHhklAC/AMYN3ECH8yZze3ERAOrE8I6xYsXJy4u7oHbXb9+HR9La/7TTz994PaPPvooq1at4ubNm9y4cYMffviBZs2a3bXN5MmT2bNnz13h0KhRI+bMmZMePI0aNWL27Nnp53cA4uLi8PLyIikpiS+//DLD9+Lq6kpAQADffvstYP4Du3fv3gfWnVWxsbEULVqUEiVKEBkZyerVqwFzi+v69es8+eSTzJ49O/09Pv7448yfPz99f2taodaoWLEi9vb2TJw4kU6dOgFQtWpVrly5kh48SUlJHDx48K79qlWrxunTp9PP33z++ec0b948/fnly5enf037meTUe7BWoQ6e8xfO3/XYw8ODebNm83LHTqz66Ud69u71r22EMEpCQsJdw3OHDRsGmE/+PvHEE3cNLsjIuHHj6NChA82aNaNMmQeP23nkkUd44403qF+/Pg0aNKBr16737WZL06RJE86dO0dISAhgDp6TJ0/eFTwTJ06kQYMGtGnThmrV/v868ZdffpkZM2ZQp04dwsPD+fLLL/nwww+pVasWQUFB/Pjjj/96PVvVqlWLOnXqEBQURJcuXWjSpAlgDsennnqKmjVr0rx5c2bNmgWYB3Ts2LGDmjVrEhgYyOLFizM8blhY2F0/L2uGPnfq1IkvvviCjh07AuZu05UrVzJ06FBq1apF7dq10wdrpHF2dubjjz+mQ4cO1KhRAzs7O955553052/fvk2DBg2YM2dOlt9DTim0c7UppfS8hQvo1aNnhs//vXkz702fSmpqKkMGDqJl8xa5W6DIUw4fPkz16tWNLkOILPH392fHjh1W/UcjIxn93mfHlDnS4slEk8aN+XBJKH7lyzN2wnhmzZ1DonS9CSGEzQpv8CjF+fP370bz9PBk3qw5dHypAz/8uIqefaTrTQiRf5w+ffqhWzs5qdAGj8J8Mdn9pu8A8wihXj16MnnCRC5cvEjXd7qzYeNfuVOkEEIUQIU2eAASk5Ksnn21WZOmfLhkKeXLlWPM+HHMmT9Xut4KmcJ6PlQUTjn5+16ogwcgIgtdZ16ensyfPZeOL77Edz/8QK9+fbhw4UIOVifyCmdnZ6KioiR8RKGgLffjcXZ2zpHjF9pRbXZ2drppy+YM6j+AZ556Osv7h23axJQZ00Brhg4eQvNmj+ZAlSKvkDuQisImJ+9AmqOzU+dlSikcTaYHDjDITLOmTalYsSLjJk5g9LixvPj8C/To1h1HR8dsrlTkBSaT6V93YhRCPJxC3dXm7e1NxEMGD4C3lxcL5szlpRde5LsfvqdXv75cyGDmWCGEEP+vUAePj49Pls7xZMRkMtHn3V5MGj+BiIhzdH2nG2GbNmVThUIIUfAU7uDx9rFqSLU1Hm3ajGVLluLr48PIsaOZt3ABSUlJ2VClEEIULIU6eHx9fLh9+zZRUfe9e4LVvL28mD97Li8+/wLffreSXv36cPHSpWw5thBCFBSFPHh8gftPnZNVjo6O9O3Vm4njxnPu3Dne6v42YX9L15sQQqQp1MGTNkV8RET2T4PTvNmjLFscio+3NyPHjGa+dL0JIQRQyIOnrLs7Dg4ONg8wyIy3tzcL5szjheeeZ8V3K+ndvy+XIqXrTQhRuBXq4LG3t8fby+uhr+WxhqOjI/1692HCmHGcOXuWt7p34+977qchhBCFSaEOHjCPbMuNGadbNG/OssVL8PL0ZPjokSxYvCj9lsRCCFGYFPrg8fX15fz587kyB5ePtw8L587n+WefY/m3K+jdry+RkZE5/rpCCJGXFPrg8fH2JuHWLaKio3Pl9RwdHenfpy/jx4zl1JnTdOn+Npst91MXQojCoNAHj69lZFtOnufJSMvmLVi2OBRPD0+GjRrBwiWLpetNCFEoFPrgSRtSbcSdRX19fFg4bz7PPfMM36xYTu/+/Yi8fDnX6xBCiNxU6IPHw8MTe3v7XG/xpHFydGRA3/6MHTWaU6dP8Va3t9myVbrehBAFV6EPHgd7ezw9PXPsWh5rPdayFUsXLcHDoyxDR45gUegS6XoTQhRIhT54wDx1jlEtnjuV8/Vl4bwFPPv0M3y9/Bv6DJCuNyFEwSPBA/j6mO/Lkxfuxurk6MjAfv0ZO3I04SdP0rX722z5Z6vRZQkhRLaR4MF8fc3Nmze5du2a0aWke6xVK5YtWoK7uztDRwxn8dJQ6XoTQhQIEjzcMVmowed57lWuXDkWzVvA0+2f4qtvvqbvwP5cvnLF6LKEEMImEjyAr7cx1/JYw8nJicEDBjJ6xEhOhIfzVve3+WfbNqPLEkKIhybBA3h6emJvZ5cngydNm8das3TRYsq4uTF4+FBCly0lOSXF6LKEECLLJHgAk8mEh4cnEXk4eADKlyvP4vkLebp9e774+iv6DRzAFel6E0LkMxI8Fj4+3obMXpBV5q63QYwaPoLjx4/RpfvbbNsuXW9CiPxDgsfCx9uHiIiIPDGk2hqPt25D6KIllC5dmkHDhrL0w2XS9SaEyBckeCx8fXyIv3GD2NhYo0uxml/58iyZv5D2TzzJ5199Sf9BA7h69arRZQkhxH1J8Fj4eBs3WagtnJ2dGTpoMCOHDefoMUvX247tRpclhBCZkuCx8PX1BSAiIn8FT5q2bR4ndOFiSpUqxeBhQ1n28UfS9SaEyJMkeCy8PD1RSuW5i0izwt/PjyXzF/JE23Z89sXnDBg8kKtRUUaXJYQQd5HgsXB0dMSjbNk8fS2PNZydnRk2eAgjhg7jyNGjvNXtbbbv2GF0WUIIkU6C5w4+Pj757hxPZto93pbQBYsoUcKVQcOG8OHHH5EiXW9CiDxAgucOPt4++b7Fcyd/f3+WLFhEu8fb8ukXnzNg8CDpehNCGC7Hgkcp5ayU2qaU2quUOqiUGm9Zr5RSk5VSx5RSh5VSfSzrByul9liWA0qpFKVU6QyOm9n+pZRSPyil9lleNzirNfv6+HI9Npa4uDhb336e4eLiwvAhQxk+eCiHjhyma/e32blrp9FlCSEKsZxs8dwGWmmtawG1gXZKqYbAG0A5oJrWujrwDYDWeobWurbWujYwHPhLax2dwXEz3B8YAezRWtcE/gvMyWrBPj7eAJw7H5HVXfO8J9q1I3ThIlxdXRkwZDCffv5ZvrlYVghRsORY8GizeMtDk2XRQA9ggtY61bJdRrfY7Ax8ncmhM9s/EFhnWXcE8FdKeWSlZn8/fwDCw8Ozslu+EeAfwJIFi2jzWGs+/ORjJkyexO3ERKPLEkIUMjl6jkcpZa+U2gNcBtZorf8BKgKdlFI7lFKrlVKV79mnCNAO+C6Tw2a2/17gBcsx6gN+gG8GNXVTSu1ITU0lJCSE0NDQ9Od8fXwoUaIEBw4etOl952UuLi6MHDacbl3fZt2f6+k3oD/R0Rk1LIUQAkJDQwkJCUlfAGXrMVVudLcopUoCPwC9ga3AWK31+0qpF4D+Wutmd2zbCXhVa/10JseKz2h/pZQr5u61OsB+oBrQVWu9N6Pj2Nvb64xGeQ0fPZIzZ87y1Wef2/KW84W/wjYyacp7lCpZkqmTp1AhIMDokoQQeZxSKlVrbW/LMXJlVJvW+hqwAXNLJoL/b838ANS8Z/OXybybjcz211rHaq3ftJwj+i/gDpzKaq01goKJOB+Rp26DnVOaN3uU+bPnkJScTM8+vdjyz1ajSxJCFAI5OarN3dLSQSnlArQGjgCrgFaWzZoDx+7Yp4Rl3Y/3OXSG+yulSiqlHC3ruwIbtdZZnvEzOMg8GO7AoYLb3XanqlWqsmTBIny8fRg+aiQrv/9OBh0IIXJUTrZ4vIA/lVL7gO2Yz/H8AkwFXlRK7QemYA6JNM8Df2itb9x5IKXUb0opb8vDzPavDhxUSh0BngD6PkzRVatUwcHBoUCf57lXWXd35s2eQ+OGjZi7YD6z5s6Red6EEDkmV87x5EWZneMBeKdXTxwcTMyfneUR2flaamoqS5Yt5evl31AvJIRxo8dSvFgxo8sSQuQh+eYcT34THBjEkaNHSEpKMrqUXGVnZ0ePbt0ZMnAQu3bvpmfvXly4cMHosoQQBYwETwaCg4JJTEzkePgJo0sxxFNPtueD6TOJjomme6+e7Nu/3+iShBAFiARPBoKDggA4cOCAwZUYp07t2iyev5DixYrTf/BA/rfmD6NLEkIUEBI8GShTpgyeHh6FaoBBRsr5+rJ4/gKCg4KZPHUKSz/6kNTUVKPLEkLkcw6ZPaGU+sCK/WO11uOyr5y8Izg4mD179qK1RimbL9TNt1xdXZk5dRofzJnN519+QUTEOYYPGYazs7PRpQkh8qn7tXheBA4+YOmU0wUaJTgwiKtRV4m8HGl0KYYzmUwMGTiIHt3fYcPGjfQZ0J8omWZHCPGQMm3xAPO01h/eb2elVKlsrifPSL+Q9OBBPD08Da7GeEopOnfshK+3DxOnTKb7uz2YOmkylSpWMro0IUQ+c78WT6bT1iilngDQWs/M9oryiAoVKuDi7Fzoz/Pcq1nTpsyfPRedmsq7ffuwecsWo0sSQuQz9wuetUqp8veuVEr9F1iQcyXlDQ729lSvXp0DBwvvyLbMVKlcmSULFlG+XDmGjx7JipXfyjQ7Qgir3S94hmAOnwppK5RSg4GhQIscritPCA4KJjw8nJsJCUaXkueUKVOGebPmmFtAixYyc9YHJCcnG12WECIfyDR4tNY/A72A/ymlApVSMzEPOHhUa302two0UnBgECmpqRw+ctjoUvIkZ2dnJowZx6udX+HnX39h8PChBeq24UKInHHf63i01n8AbwMbMU/C2VJrHZUbheUFQYGBAHKe5z7s7Ozo1vVthg8Zyt59++jR+10izp83uiwhRB6WafAopWKUUtGY73lTHGgKnL9jfYFXvHhxAvz9JXis8ETbdnwwYybXrl3nnXd7smdfhvffE0KI+7Z4ymC+mVoZoAhQ+o7H7jlfWt4QHBTEwUMH5Yp9K9SuWYvFCxZSsmQJBgwexOrffze6JCFEHnS/czwp91tys0gjBQcGEx8fz9mzheK0ls18fXxYNG8BtWrWZMqMaSxZtlRCWwhxl/t1tW170M7WbJPfpU8YKt1tVitevDgzpkzjmaee5suvv2LMhHEkyMhAIYTF/braaiildt1n2Q0U+Ev6fX19KeHqyoFDcj1PVjg4ODCwX3969XyXsE2b6DOgH1evXjW6LCFEHnC/KXOCrdi/wF+4oZQiKCiY/QekxZNVSik6vvgSvt4+jJ80ge7v9mDKpPeoUrmy0aUJIQx0v3M84VYsZ3KzWKMEBwVxLuIc165fN7qUfKlxo0YsmDsPZWdHr359CPt7k9ElCSEMJPfjsULaeZ6Dcp7noVWqWInF8xfi7+fPqLFj+HrFcplmR4hCSoLHCtWrVsPe3p4DhyR4bFHGzY15s2bT4tFHWbRkMdPfn0lSUpLRZQkhcplVwaOU8lVKtbR876SUKpqzZeUtTk5OVKlcWSYMzQZOTk6MHTWG/776Gr+u/o1BQ4cQGxtrdFlCiFz0wOBRSnUBfgKWWVb5AT/mZFF5UXBQMIePHJH/oWcDOzs7ur7ZhVHDRnDg0EHe6SXT7AhRmFjT4ukDNARiAbTWx4CyOVlUXlS7Zi0SExPZu2+f0aUUGI+3acPsme8TFxdLzz69OHrsqNElCSFygTXBc0trnZj2QCllD6icKylvql+vHi7Ozvz5159Gl1Kg1AiuwYK583B2cqLPgP7s2LnT6JKEEDnMmuD5Wyk1BHC2nOdZDvySs2XlPU5OTjRu1JiNYWEkpxSaGYNyRfly5Vk4dz5enl4MGTGMdevXG12SECIHWRM8Q4A44AjQF1gHjMzJovKqli1acD02lt27dxtdSoFTpkwZ5s2eQ1BgIOMnT2Tl998ZXZIQIofcN3gs3Wofaa0Xaa2f11o/Z/m+UM762KBefVxcXNiwcYPRpRRIxYsVY+a0GTRr2oy5C+azZNlSudZHiALoQTeCSwG8lFKmXKonT3NycqJJo0bm7ja5zXOOcHJ0ZMKYsTzd/im+/Porps2cLl2bQhQw95urLc1JIEwp9SNwI22l1npujlWVh7Vs3pK169eze88e6oWEGF1OgWRvb8+g/gNwc3Pjk88+5dq164wbPQZnZ2ejSxNCZANrzvFcAdZgvhmc+x1LoVS/Xj1cXFz4868NRpdSoCml6PL6Gwzs158t/2yl/+BBcqGpEAWEKqx96Pb29jrlIbtwJrw3iW3btrNq5Xc4OFjTaBS22LDxLya+NxlvL29mTpuOR9lCdxmZEHmGUipVa21vyzGsmblgjVLqj3sXW140v2vZvAWxcbHs2iOj23JDi0ebM3PqdK5GXaVnn16cPn3a6JKEEDawpqttFDDaskzGPKx6b04WldfVr1efIkWK8OeGDUaXUmjUqV2bebNmk5KSwrv9+rBf5s0TIt96YPBorf+5Y/lLa90HqJ8LteVZTo6ONGnUmLBNm2R0Wy6qVLESC+fOp4RrCQYMHsTmLVuMLkkI8RCs6WpzvWMpqZR6DPDKhdrytJYtLN1tu3cZXUqh4u3lxcK58wjw92fkmFH89vtqo0sSQmSRNV1tB4EDlq+7Mc9a8HZOFpUf1AupZ+5u++svo0spdEqWLMnsmR9Qp84jTJ0xnS++/kouNBUiH7EmeCporctrrctprQO01q2Av3O6sLzOydGRpo2bSHebQYoUKcK0ye/R+rHHCF22lHkLF5CaWign1BAi37EmeP7JYN227C4kP2rRvLl0txnIZDIxatgIOr74Eiu//45JUybL/ZKEyAcyvQhFKVUW87kcF6VUDf7/VgiumC8mLfTqhdSjaNGirN+wgfr1CvV4C8PY2dnxbo+elC5dmsVLQ7l27TqTxk+gSBH5FRUir7pfi6c9MB/wBRYCCyzLCMxDq+9LKeWslNqmlNqrlDqolBpvWa+UUpOVUseUUoeVUn0s6/+jlNpnWTYrpWplctwvlVJHlVIHlFIfpc0jZ+3+2cnc3daYsL+lu81ISileebkzw4cMZfee3fQd2J+YmBijyxJCZCLT4NFaf6y1bga8pbVudsfypNb6WyuOfRtopbWuBdQG2imlGgJvAOWAalrr6sA3lu1PAc211jWBiUBoJsf9EqgG1ABcgK5Z3D9btXi0BXFxcezcJd1tRnuibTvemziJ02fO0LNvby5cuGB0SUKIDFhzHc8KpVRbpdQApdSItMWK/bTWOt7y0GRZNNADmJB2awWt9WXL181a67T/pm7F3NLK6Li/WY6tMZ9r8s3K/tmtXkgIRYsWlTuT5hGNGjZi9swPiIuNo2efXhw/ccLokoQQ97DmOp6FwOvAAMwtjFeBStYcXCllr5TaA1wG1mit/zbH/hAAACAASURBVAEqAp2UUjuUUquVUpUz2PUt4L4XaFi62F4Dfn+Y/bOLo2V028awMG4mJOTGS4oHCAoMZP6cuTiYTPQZ0I+9+/YZXZIQ4g7WjGprqrV+BYjSWo8GGmBla0JrnaK1rm3Zvr5SKhhwAm5prUOApcBHd+5jub32W8DQBxx+IbBRax2Wlf2VUt2UUjtSU1MJCQkhNNT2HrnnnnmW+Bs3+OW3X20+lsge/n5+LJw7Hzc3NwYNG8K27TIQU4iHERoaSkhISPrC/w80e2gPnJ1aKbVNa11fKfUP8CwQBRzUWlfJ0gspNRbz/Xy6Au201qeVUgq4prUuYdmmJvAD8ITW+tgDjlUHeOHOu6Fauz/YNjt1RvoM6MeFixf5+rMvMJnkvnl5RUxMDIOGDeHU6dOMGTmKFo82N7okIfK1XJmdGvhNKVUSmAnsAU4DK60ozt2yH0opF6A15glGVwGtLJs1B45ZtikPfA+89oDQ6Qq0BTrfEzpW7Z9TXunUmcuXL7N2/brcfmlxH6VKlWL2+7OoVrUq4yZOYPX/MuqZFULkpvu2eJRSdkA9y7mZtABx0VpHP/DA5tbHp4A95oBbobWeYAmjL4HyQDzwjtZ6r1JqGfAicMZyiGRLdxxKqd+ArlrrC0qpZMs2cZbtvrccN9P9M5LdLR6tNV26dSUlJYVPln2EnZ01mS5yS0JCAiPHjmHHzh307dWbF59/weiShMiXsqPFY01X21atdUNbXiQvyu7gAfhj7RomTXmPKRMn06Rx42w9trBdYmIi4ydPImxTGF27vMVrr/wHc2+vEMJaudXVtkYp9awtL1JYtGrZCk9PT75a/rXRpYgMODo6Mn7MWNq2acOyjz5kcegSmVxUCANYEzy9gB+UUglKqWilVIxS6oFdbYWRg709nTp0ZP+BA+zbv9/ockQGHOztGT5kGM898yxfr1jO+7Nnkd0tXyHE/VkTPGUwX/xZDHC3PHbPyaLys/btnqBEiRJ89Y20evIqOzs7+vfpy6udX+GnX35m8rQpMuWRELnImpkLUoAOwFDL916Yp8ARGXB2dubF555n89YtnDx1yuhyRCaUUnTr+jbdur7N2nXrGDVuLLcTE40uS4hCwZqZC+YDLTHPEgBwE1ick0Xld88/+xzOzs58Led68rxXO79C/z592bxlM0NHDJPZJ4TIBdZ0tTXWWncHbgFYhlI75mhV+VyJEiV4uv1TrF2/nsjISKPLEQ/w/LPPMWrYCPbu3cuAwQOJjY01uiQhCjRrgifJcj2PBlBKuQFyq8cH6PhSBwCWr7RmIm9htMfbtGHC2PEcP3GCvgP7Ex0t42eEyCnWBM8C4DvA3XJPnU3AtBytqgDwKFuW1q0e45fffuX69etGlyOs0KxpU6ZNnsL5Cxfo1a+vtFaFyCHWDC74DBiFecqcaKCD1vqb++8lAF55+WVu3brF9z+uMroUYaWQunV5f/oMrl2L4d1+fTh37pzRJQlR4Fg7r4s9kAQkZmGfQi/AP4CmjZuw/NsVXL5yxehyhJVqBAUz54PZJCYm0rt/XxmdKEQ2s2ZU20jga8Ab8+0NvlJKDc/pwgqKXj16kpyczNwF84wuRWRB5UqVmPvBbJSdHX0H9OPY8eNGlyREgWFN6+VVzBOFjtJajwTqA//N2bIKDm9vb9547b9sDAvj782bjS5HZIG/nx/zZs3B2dmFfgP7c+jwIaNLEqJAsCZ4zgAOdzx2AE7mTDkFU6cOHQnw92f2vLkkyHUi+Yqvjw/zZs+mRIkS9B88iD379hpdkhD5njXBcxM4qJRappRaCuwHrimlPlBKfZCz5RUMJpOJQf0HEHk5ko8/+9TockQWeXp4Mm/WHNzd3Rk8bCg7du40uiQh8jVrbovw1v2e11p/mK0V5ZKcuC3Cg8z4YCa/rV7N0sVLqFSxUq6+trBdTEwMA4YM4ty5c0wYO57GjRoZXZIQuS5X7sdTUBkRPLGxsbz65ut4e3mxYM487O1t+tkJA8TGxjJo2BCOnzjB2FGj5VbaotDJlfvxKKXaKaW2K6Uuy20RbOPq6kqvHj05dPgwP/3ys9HliIfg6urKB9NnUr1aNcZPnMAfa9cYXZIQ+Y4153jmA90BH+S2CDZr81hrQh6pS+iHy7gaFWV0OeIhFCtWjJnTZlCzZi0mT53CL7/9anRJQuQr1gRPBLBHa52ktU5JW3K6sIJKKUX/vv1ISkxk3sL5RpcjHlIRFxemvzeF+iH1mP7+TL5b9YPRJQmRb1gTPEOAn5VSg5VSfdKWnC6sICvn68tr/3mVPzdsYMs/W40uRzwkJycnJk+YSNPGTZgzby5fL5eZpISwhjXBMx5IAUpi7mJLW4QNOnd6mQB/f96bNpVLkZeMLkc8JEdHRyaMHUerFi1ZFLqETz//zOiShMjzrBlOvVNrXTeX6sk1Roxqu9fZc2fp/m5PvL28WTBnLs7OzobWIx5eSkoKU2dM539r/uC1/7xK1ze7oJQyuiwhsl2ujGoD1imlWtnyIiJj5cuVZ8zIUZwIP8H092dQWIe2FwT29vYMHzKUp55sz+dffsGi0CXy8xQiE9YEz9vAWqVUvAynzn6NGjTk7S5vsXb9er5ZsdzocoQN7OzsGNR/AM8/+xzfrFjO3AXzJHyEyIDDgzehTI5XUcj9p/MrHD9xnCXLllKxQgXq16tvdEniIdnZ2dGvdx9MJhMrVn5LYmISA/v1x85O7iYiRBqrZi5QSr0MVNBav6eU8gU8tNb5esKqvHCO504JCQn07NOLyMuXCV24GF8fH6NLEjbQWrPsow/5/Ksvafd4W4YOGiwzVYgCIVemzFFKzQdMwKNa6+pKqdLA/7TW9Wx5YaPlteABuHDxIt17vkOpUqVYPH8hRYoUMbokYaNPP/+MDz/5mMdatmLksOE4OFjTySBE3pVbgwsaa627A7cAtNbRgKMtLyoy5u3lxbjRYzh37hyTp04hNTXV6JKEjV5/7b9079qNdX+uZ9ykCSQlJRldkhCGsyZ4kpRSdoAGUEq5AfIXMYfUfaQuPd7pQdjfm5i/aKGcnC4A/tO5M717vsvGsDBGjxvL7cREo0sSwlCZBo9SKq1PYAHwHeCulBoPbAKm5UJthVaHF17kpRdeZOX33zFnvoyMKgg6vPgSA/v1Z/PWLYwYPZJbt24ZXZIQhsn0HI9SapfW+hHL90FAa0ABa7XWB3KvxJyRF8/x3ElrzcIli1n+7QqeeeppBvTtJyOjCoDffl/NtJkzqF2zFlMmv0cRFxejSxIiS3J0cIFSarfWuo4tB8/L8nrwgDl8Qj9cxpdff0X7J55k8ICBEj4FwJp1a3lv6hSqV6/O9PemUqxYMaNLEsJqOR08EUCmt7bWWufr217nh+ABc/h8+MnHfPbF5zIstwDZ8NdfjJ88kcqVKvP+tOkUL17c6JKEsEp2BM/9xnbaA8Uwd68Jgyil6PpmFxzs7fno009ISUlh+NBhOEj45GstmjfHZDIxZsI4+g0awPvTZ1KyRAmjyxIiV1h1jqcgyi8tnjt9/uUXLP3oQ1q1aMmo4SPkmpAC4J9t2xg5djQ+3t58MON93EqXNrokIe4rp6/jkZZOHvPaf16lR7furN/wJ/0HDyQ6WqbMy+8a1K/PtPemcPHSJfr078flK1eMLkmIHHe/Fk9py8WiBVJ+bPGkWbNuLdPfn4mrqyuTxk2gerVqRpckbLRv/36GjBhGiRIlmDXjfby9vIwuSYgM5WiLpyCHTn7X5rHWLJgzD3s7O3r368Nvv682uiRho5o1ajBrxvvEx8fTu18fzp07Z3RJQuQYqyYJLYjyc4snzbXr1xk/aQI7d+3iuWeepXfPdzGZTEaXJWwQHh5O/yGDsFOKD2a8T4WAAKNLEuIuuTJJaEFVEIIHIDklhdBlS/lmxXJqBAczYex4OUGdz505e5b+gwaSmJjI+9NnULVKFaNLEiJdng4epZQzsBFwwjxse6XWeqxS6hOgOXDdsukbWus9ln1aALMxz4Z9VWvdPIPjhgFpFz2UBbZprZ+z7PsjcMry3Pda6wmZ1VdQgifNuvXrmTpzOsWKFWPowME0bNDA6JKEDc5fOE//QQOJj49n2pSp1AgKNrokIYC8HzwKKKq1jldKmTDP8dYXeAf4RWu98p7tSwKbgXZa67NKqbJa68sPeI3vgB+11p9ZgmeQ1vopa+oraMEDcCL8BBMmT+L0mTM82a4d7/Z4l+JyVXy+FRkZSf/Bg7gadZX3JkwipG5do0sSItdui/BQtFm85aHJstwv5V7B3Eo5a9n/QaFTHGgFrMqGcguEShUrsXRxKK92foX//fEHr3d5ky1btxhdlnhIHh4ezJ89B28vb4aOHE7Y35uMLkmIbJGjE38ppeyVUnuAy8AarfU/lqcmK6X2KaVmKaWcLOuqAKWUUhuUUjuVUv99wOGfB9ZprWPvWNdIKbVXKbXaMrFpoePk6Ei3rm+zaP5CirsWZ+jIEUyeOoW4uDijSxMPoXTp0sz9YBaVKlZkzLixrFm31uiShLBZjgaP1jpFa10b8AXqK6WCgeFANaAeUBoYatncAagLtAfaAqOVUvc7q9oZ+PqOx7sAP611LWAembSElFLdlFI7UlNTCQkJITQ09OHfYB5WrWpVli5czOuvvsbadWt5rcsbbNwUJrdYyIdcXV2ZNeN9ataoyaQp7/Hjzz8ZXZIoREJDQwkJCUlfyIbJBXJtVJtSaixwQ2s98451LbCcl1FKDQOctdbjLM99CPyutf42g2O5AccAH611hjc2UUqdBkK01lczer4gnuPJzNFjx5g6YxrhJ09S95FHePedHlSqWMnoskQW3b59mzHjx7Hln61079qN/3TubHRJohDK0+d4lFLulgEDKKVcMN/P54hSysuyTgHPAWn39vkRaKaUclBKFQEaAIczOXwHzAMU0kNHKeVpOSZKqfqY31tU9r+z/KdqlSosXbSEvr16c/z4Cd7q3o1pM2dwNUo+nvzEycmJSeMn0LpVK5YsC2XRksXSghX5Uk6OaqsJfIp5lms7YIXWeoJSaj3gjrm5tgd4J20QglJqMPAm5ltrL9Naz7as/w3oqrW+YHm8AZiqtf79jtfrBfQAkoEEYIDWenNm9RWmFs+d4uLi+PSLz/l+1Q+YHBx4pfMrdHqpA87OzkaXJqyUmprK7HlzWfXTj7R/4kkG9R8gt8oQuSZPD6fO6wpr8KSJOH+exUuXsDEsDHd3d956400eb91GZrzOJ7TWfPTJx3z6xec0b/Yoo0eMxNHR0eiyRCEgwWODwh48afbu28eCxQs5cvQoXl5e/OflzrR7vK38EcsnVny3kvkLF1Cndh0mj58gdzMVOU6CxwYSPP9Pa82WrVv59IvPOHzkCO7u7rzS6WWeerI9Tk5ODz6AMNQfa9cwZfo0Avz9mT5lGmXc3IwuSRRgEjw2kOD5N601O3bu5LMvP2fvvn2ULlWKTh068uzTz1CkSBGjyxP3sW37NkaPG0uJkiV5f9oMyvn6Gl2SKKAkeGwgwXN/e/bt5bMvvmDHzh0ULVqUJ9q24/lnn5M/aHnYoSOHGTpiOADTJr9HYPVAgysSBZEEjw0keKxz+MgRVn7/HX/+tYHk5GQa1G/AS8+/QL2QEOzscvT6Y/EQzp07x+Dhw4iKjmLMyFE0a9LU6JJEASPBYwMJnqyJio7mp19+5seffyI6OhpfH19eeO452j7eViYizWNiYmIYNmoER48do8+7vXjhueeNLkkUIBI8NpDgeThJSUls2PgX3//wAwcPH8LR0ZHmzR7lySeeoE6t2tIKyiMSEhIYP3kSm7dspuOLL9Gj+ztyrY/IFhI8NpDgsd3RY0f5dfVq1q5bS/yNG3h6evJE23Y80bYtnh6eRpdX6KWkpDB/0UK+++F7mjRuzOgRoyji4mJ0WSKfk+CxgQRP9rl9+zZhf2/it9Wr2bl7FwB16zxCm8da06xpU7m2xGDfrfqBeQvmU7FCRaZMmkxZd3ejSxL5mASPDSR4csalyEv8/r//sfqP/3Hx4kUcTSYaNGjIYy1b0rhhI5maxyBb/tnK+EkTcXZ2ZvL4iQQFyog38XAkeGwgwZOztNYcPnKYtevXs37Dn0RHR+Pi4kLTxk14rGUrQurWldkRctmp06cYPmoUV69eYfDAQbRt87jRJYl8SILHBhI8uSclJYW9+/ax7s91bNi4kbi4OIoUKUKjBg15tFkzGtRvIOcecsn169cZM2E8u/fs5qUXXqRn93dkfj6RJRI8NpDgMUZSUhI7d+0i7O8wwv7+m2vXruFoMhESUo9HmzalcaPGlCxRwugyC7Tk5GQWhS7h2+9WUrtWLcaNGkPp0qWNLkvkExI8NpDgMV5KSgr7Dx4gbNMmNoaFEXk5Ejs7O4ICA2nUsBGNGzYiwN8fy22WRDb7Y80aZsx6H9fixRk3egw1gmsYXZLIByR4bCDBk7dorTl2/Bh/b97M5q1bOHb8OACeHh40bNCQxg0bUad2bZm0NJsdP3GCMePHcikyknfe7kbHlzpI0Iv7kuCxgQRP3nb16lW2/vMPm7duYceundy6dQtHR0fq1KpNvXr1qB9SD7/y5eWPZDaIj49nyozphG0Ko0njxgwbNIQS0t0pMiHBYwMJnvzjdmIie/bsYeu2f9i+Yztnz50DwKOsB/XrhVAvpB51H6krU/fYQGvNt99/x+LQJZQqVYrRI0ZSu2Yto8sSeZAEjw0kePKvi5cusW37Nrbt2MGu3bu4ceMGdnZ2VKlchbqPPELdOo9QIzhYuuUewtFjxxg/aSIXLl7gPy935o3/vo7JZDK6LJGHSPDYQIKnYEhOTubgoUPs3L2Lnbt2cujwYVJSUnA0mQgODqZunUd4pM4jVK1SRYYNW+nmzZvMW7iAX1f/RpXKlRk1fCT+fn5GlyXyCAkeG0jwFEw3b95k7/597Ny5k527dxF+8iQALs7O1KhRg9q1alOnVm0JIiuEbdrE9PdnkJCQwFtvdqHjSx1kolEhwWMLCZ7CISYmhr379rF77x727N3DqdOnAXMQBQcHU6tmLWrVqEm1atVwkpkU/iU6Opr358wmbFMYQdUDGTpoMP7+/kaXJQwkwWMDCZ7CKbMgMplMVK9ajZo1alCzRk2Cg4JkclMLrTXr/lzPnHlzuXHzJq92foVXX/mPTHlUSEnw2ECCR4B5Cpn9Bw+wb/9+9u7by7Hjx0lJScHOzo4KAQEEBwURHBRMjeAaeHp4FOrh29euXWP+ooX8sXYN5XzL0a93H+qFhBhdlshlEjw2kOARGUlISODQ4UPs3b+fAwcPcvDQQRISEgAo41bGEkRBBAUGUblSpUL5v/5t27cxa95czp8/T8vmzenZvQceHh5GlyVyiQSPDSR4hDVSUlI4eeoU+w/s58DBAxw4eJBLkZGAuXuuSqXKBAUGEhQURGD1QDzKljW44txxOzGRb5Z/w+dffYlSis4dO9G508u4yGSvBZ4Ejw0keMTDunLlCgcPH+LQ4cMcPHSQo0ePkpiUBJhbRdWrVaN69epUr1aNalWqUrRoUYMrzjmXIi+xODSU9Rv+pIxbGbq8/gbt2rXDQUa/FVgSPDaQ4BHZJSkpiRPh4Rw8dJBDRw5z+MgRzp8/D4BSCr/y5QmsXp1qVatRrWo1KlaoUOAuyty3fz+Llizm4OFD+Pv58XaXrjRt0qRQnxMrqCR4bCDBI3LS9evXOXL0KIctQXToyGGuX78OmLvoKlaoQLWqValapSrVqlbDz88v37cStNZs3BRG6LJlnIs4R5XKlXnrjTdp2KChBFABIsFjAwkekZu01lyKjOTI0SMcOXqUI0ePcuz4MW7cuAGAk5MTlSpUpHLlylStUoWqVarg7+efLy9yTU5JYc2aNXzyxWdcvHiRqlWq8Np/XqVp4ybY2dkZXZ6wkQSPDSR4hNFSU1OJOB/BkSNHOXr8GMeOH+PY8ePpo+gcTSYqVKhI5UqVqFK5MpUrVaJCQAWcnZ0Nrtw6ycnJ/P7H//jym685f/48/n5+dOrQkTaPtS6UowELCgkeG0jwiLwoNTWV8+fPc/T4MY4eMwfR8RPHiY+PB8DOzo7y5cpRuVJlKleqTKWKFalUsSIlS5Y0uPLMJaeksOGvDXz59VeEnzxJ6VKleP7Z53jmqacpVaqU0eWJLJLgsYEEj8gv0rrpjp84zvHjxzkeHs7xE8e5cuVK+jZubm5UqlCRipYgqlihAuV8y+WprjqtNTt37WL5tyv4Z/s2TCYTLR5tzvPPPkdQYKCcB8onJHhsIMEj8rtr168THn6CE+HhnAgPJ/xkOKfPnCE5ORkwD2LwK1+eCgEVqBAQYP5aoQLuZcoY/kf+zNmzrPppFb//8Qc3btwgICCA9k88yeOt21BSbkKXp0nw2ECCRxRESUlJnD17lhMnwzl58iQnT53i5KmTXLl6NX2bYsWKEeDvj7+fPwH+aUsApUqVyvVAupmQwNp1a/l19W8cPnIEk8lEwwYNebx1axo1aCjngvIgCR4bSPCIwiQ2NpaTp08RfvIkp0+f5tSpU5w6c5q4uLj0bUq4uuLn549f+fL4+/nh5+eHX3k/yrq750oghZ88yerfV7N2/TqiY2IoVqwYjzZtSotHW1D3kUcK3LVP+ZUEjw0keERhp7UmKjraHESnT3H6zGlOnznD6TNn7gqkIkWKUL5cefzKl6NcufKU9y1H+fLl8fHxyZFbSSSnpLBr9y7WrF3Dps2buXHjBsWLF6dxw0Y0bdKEeiH1KCJT8xhGgscGEjxCZExrTUxMDGfOnuXMWXMQnTl7hrNnz97VZaeUwtPTk/K+5fD19cXX15dyPj74+vji4eGRLTeNS0xMZPuOHWzYuIHNW7cSFxeHyWTikdp1aFC/PvXr1aOcbznDz1kVJhI8NpDgESLrbiYkcO7cOc5FnOPsuXOcPXuWcxHniIiIIOHWrfTtTCYTXl5e+Pr44uPtjbe3Nz5e3vh4e+Pp6flQ3WbJKSkcOLCfTZs3s3nLFiLORwDg6eFBSN0Q6tQ23122TJky2fZ+xb9J8NhAgkeI7JPWbRdxPoKIiAgizp+3fH+eCxcvcOuOULKzs6OsuzteXt54eXri5eWFl4cnXl6eeHp44ubmZtUMBxcuXmT7ju1s276d3Xt2E2+ZBcLXx5eaNYIJDgwmOCiI8uXLy4wJ2UiCxwYSPELkDq010TExXLhwnvMXLnDhwgXz14sXuRR5iaioqLu2N5lMlC1bFk8PDzzKlqVsWfNXj7IeeHh4UNbdHScnp7v2SUlJ4UR4OLv3mO8se+DgQWLjYgHzKL5qVapSpUqV9K9enp7SPfeQJHhsIMEjRN5w+/ZtIi9HcvHiJS5eusjFixeJvHyZyMhIIi9fJio6inv/TrkWd8XdvQxlyrhT1r0M7mXcKVOmDGXcyuDm5kbp0qWIi4vj8OEjHDh0gKPHjnHy1Kn0a5yKFi1KgL9/+jVO/n7+lC9XDjc3NwmkB8jTwaOUcgY2Ak6AA7BSaz1WKfUJ0By4btn0Da31HqVUNeBj4BFgpNZ6ZibH/RIIAZKAbUB3rXWSUqoE8AVQ3vJ6M7XWH2dWnwSPEPlDUlISV65eJTIykkuRl7h69SpXrl7hytWrXLlyhStXrhJzLeZf+9nb2VGqVGnc3ErjVtqNEpYLUxMSEoi/EU9UdDSXLl1KnxsP0kbwlcPXx+eurkBvL2/cy5TJUzNBGCWvB48Cimqt45VSJmAT0Bd4B/hFa73ynu3LAn7Ac0DMfYLnSWC15eFXwEat9SKl1AighNZ6qFLKHTgKeGqtEzM6jgSPEAVHYmIiUdHRXI26SlRUFFHR0URHRREVHUVUVDTRMdHExFwj5loMmf27d3R0TA+WlJQUEm/f5t6/jsWKFaNUqVK4lS6d3soq627+WrpUKUqWKElxV1eKFSuWb25zkZKSQmxcHFevXCEqJpqoqCiuXL1CVFQ0MTExXI+NJS4ulhs3b5KQkMCvq36yOXhyLL61OdHiLQ9NliXTlNNaXwYuK6XaP+C4v6V9r5TaBvimPQUUtwReMSAaSH7oNyCEyDccHR3NrRNPz/tup7UmLi6OmGvmELpmCaPY2FiuXb9ObGwssbGxXI+9zvXr17keG8vNmzfT94+Pjyc+Pp5z5849sCY7OzscHBwwmUyYHEw4Ojri6Jj21RGTgwmTowmTyRGTyYSjyYTJ5ICdnR12dvbY21u+2tmh7OzQqamkak1qaqp50anoVE1SchJJSXcuySRb1iUmJpoXy3PJyckkJyeTkpxMckoKqampVn/G9vb22XYRb462G5VS9sBOoBKwQGv9j1KqBzBZKTUGWAcM01rffohjm4DXMLeiAOYDPwEXgOJAJ631vz5VpVQ3oBtASEgI3bp1o1u3bll/c0KIfEcphaurK66urviVL2/VPikpKdxMSCA+Pp64uDjz1/g4bt64SVx8HNExMVy7do3r168TFxfHjZs3uHX7Nrdv3yYxMZGkpCRuJpi3TU1N/df5qpxmDjK79CB0cHDAxcUFJycnXJydKVKkCEWLFqVYsWK4FnelRIkSuJcpg4eHB16envz808989NFHdx7S5pNguTK4QClVEvgB6A1EAZcARyAUCNdaT7hj23FAfGZdbXdstxS4obXuZ3n8EtAEGABUBNYAtbTWsRntL11tQggjpaSkkJSUxK3bt7lp6cZKvH2blJQUklNTSElJJiU5heSUVFJTUrCzN7d+7OzssLc3t4Ls7e1xNDni7OyMi4szzs4uFHFxwcHBIccGSWTHOZ5cOVOmtb6mlNoAtLsjUG4rpT4GBmX1eEqpsYA70P2O1W8CUy1dfCeUUqeAapgHIAghRJ5ib2+Pvb09zs7OhW5G7hy7qkop5W5p6aCUcgFaA0eUUl6WdQrzQIIDWTxuV6At0PmerrSzwGOWbTyAqsBJW9+HhuMPOgAACGpJREFUEEKI7JWTo9pqAp8C9pgDboXWeoJSaj3m1ooC9gDvWEa+eQI7AFcgFfPAhECtdaxS6jegq9b6glIqGTgDpM1i+L3luN7AJ4CX5dhTtdZfZFafdLUJIUTW5enh1HmdBI8QQmRddgSPTGAkhBAiV0nwCCGEyFWFNngKaxdjTggNDTW6hAJFPs/sJZ9ntrN5nLYEj7CZ/MPOXvJ5Zi/5PLNd/riANC9SSmnMo+eE7eyQzzI7yeeZveTzzF52WmubwqcwT7W6U2sdYnQRBYFSaod8ltlHPs/sJZ9n9lJK7bD1GIW2q+3/2jvbGDuLKo7//qFYWqpllzVEBW1JCAHEUGgaP/iCWAPRtIvhAxVIWCOJIC8hfNJoQgMxVupLInxAqA1iTF8oSlaFUKQlEJotUtl22SqytBtS+YBQFYukuO3xw5ybnd7ee/e53d3n7t57fsmkc+c5M/c8/06e2Xlm7hlSuJ5gaggtp5bQc2oJPaeWSevZsa/agiAIgtbQyTOeIAiCoAXEwBMEQRCUStsNPJKukPSKpBFJ365xfa6kTX59p6RF2bXvePkrki4v0++ZSgE975C0V9IeSU9L+kR27YikQU/95Xo+MymgZ5+kf2S63ZBdu17Sq56uL9fzmUcBLX+a6fg3Sf/KrkXfrELSeklvSqoZuFmJn7neeyRdnF1rrm+aWdskUkDS14CzSef97CYFGs1tvgXc7/lVwCbPn+/2c4HF3s5Jrb6nWaDnF4D5nr+poqd/PtTqe5hJqaCefcB9Nep2k6KtdwNdnu9q9T3NZC2r7G8F1mefo28er9HngIuBl+tc/zLwBOl3PJ8Gdnp5032z3WY8y4ARM9tnZu8DG4HeKpteUtRsgC3AF/2Ihl5go5kdNrP9wIi318lMqKeZbTezytnAA4wfRR4cT5H+WY/LgafM7KCZ/ZN00OEV0+TnbKBZLb8GbCjFs1mKmT0LHGxg0gs8bIkB4DQ/5qbpvtluA8/HgPww9ANeVtPGzMaAfwOnF6zbaTSryTdIfxFVOEXSi5IGJF05HQ7OMorqeZW/ytgi6awm63YKhfXw17+LgW1ZcfTN5qmnedN9s91+QFrr17TV+8Xr2RSp22kU1kTSdcBS4PNZ8cctnaF0NrBN0pCZvTYNfs4Wiuj5O2CDmR2WdCNpdn5ZwbqdRDN6rAK2mFl+Dkr0zeaZsmdnu814DgBnZZ/PBN6oZyNpDrCQNL0sUrfTKKSJpOXAd4GVZna4Um5mb/i/+4BngCXT6ewsYEI9zeztTMMHgUuK1u0wmtFjFVWv2aJvnhD1NG++b7Z6QWuKF8fmkBa2FjO+4HhBlc3NHLu5YLPnL+DYzQX7iM0FRfRcQlrkPaeqvAuY6/ke4FUaLP52Qiqo50ey/FeBAc93A/td1y7Pd7f6nmaylm53LjCK/1jey6Jv1td1EfU3F3yFYzcXvODlTffNtnrVZmZjkm4BniTtellvZsOS7gJeNLN+4BfArySNkGY6q7zusKTNwF5gDLjZjp2adxwF9VwLLAAeSXs0eN3MVgLnAT+XdJQ0s15jZntbciMzhIJ63iZpJakPHiTtcsPMDkq6G/iTN3eXmTVaCG5rCmoJaVPBRvMnpBN9swaSNgCXAj2SDgB3AicDmNn9wOOknW0jwH+Br/u1pvtmhMwJgiAISqXd1niCIAiCGU4MPEEQBEGpxMATBEEQlEoMPEEQBEGpxMATBEEQlEoMPMGspCq68GAlOrGk2yXNz+wOleDLlH+HR6m+b5JtrJU0LGntFPhTrevjkk6bbLtBZxLbqYNZiaRDZragRvkosNTM3mpkV4Yvk2yzj3QftxS0n2Mp9mBe9g7wYcuiSdSzLdD+KJmuQTAZYsYTtA2SbgM+CmyXtD0r/76k3R4Q8gwvW6F0HtNLkv6Yla/2c0mekbTP26y0c4eklz3dPoEviyT9VdI6t/+1pOWSnvczS5a5Xbekxzwo6ICkT9Voq5GvD0jaCjxcVacfOBXYKelqSQ9J+onr8kNJyyTt8DZ3SDrX650k6UeShtynW2vpKmlUUk89Xfz+/yLpQZ91bZU0r5n/z6CNaXWIhkiRTiQBR4DBLF3t5aNAT2ZnwArP3wN8z/NdjM/4bwB+7PnVwA5S6KQe4G3Sr7cvAYZID/MFwDCwxOscd7YLKfTIGHAh6Q+8XcB6UriRXuAxt7sXuNPzlwGDnu/Dz+WZwNddwLw6Gh3K8g8Bv8fDQAEfAuZ4fjnwqOdvAh7NrnXX0XXU9ampS3b/F7n9ZuC6VvebSDMjtVXInKCjeM/MLipg9z7pgQvpIf0lz58JbFI6T+QDpPhSFf5g6fXUYUlvAmcAnwF+a2bvAkj6DfBZ4KUG373fzIbcfhh42sxM0hDpwYy3exWAmW2TdLqkhVXtNPK138zeK6ADwCM2HgZqIfBLSeeQBueTvXw5KZbhmPs0UVieerr0+/0Put2u7J6DDidetQXtzv/MrLKQeYTxo0DuJc0oLgS+CZyS1cnXRCp1aoV+n4i8naPZ56OZH0VCyjfy9d0m/Mlt7wa2m9kngRVZm6rx/Y1opEstHYMgBp6g7fgP8MECdguBv3t+4jPi4VngSknzJZ1Kihz93Im5eFy71wJIuhR4y8zemaSvRcjb7MvKtwI3Kh0ZgqRuL6+n63TpErQxMfAEs5V5Vdup13j5A8AT+eaCOqwmRdR+Dphwp5aZ/Zm0TvICsBNYZ2aNXrMVZTWwVNIeYA21B5amfC3IPcAPJD1Piu5cYR3wOrBH0m7gGi+vqes06hK0MbGdOgiCICiVmPEEQRAEpRIDTxAEQVAqMfAEQRAEpRIDTxAEQVAqMfAEQRAEpRIDTxAEQVAqMfAEQRAEpRIDTxAEQVAq/wf1IqtMU2k4pQAAAABJRU5ErkJggg==n”, “text/plain”: [

“<Figure size 432x288 with 3 Axes>”

]

}, “metadata”: {

“needs_background”: “light”

}, “output_type”: “display_data”

}

], “source”: [

“eq = tmo.equilibrium # Thermosteam’s equilibrium modulen”, “eq.plot_vle_binary_phase_envelope([‘Ethanol’, ‘Water’], P=101325)n”, “plt.show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Plot the ternary phase diagram of three chemicals in liquid-liquid equilibrium at constant pressure:”

]

}, {

“cell_type”: “code”, “execution_count”: 69, “metadata”: {}, “outputs”: [

{
“data”: {

“image/png”: “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n”, “text/plain”: [

“<Figure size 432x288 with 1 Axes>”

]

}, “metadata”: {}, “output_type”: “display_data”

}

], “source”: [

“# This one will take like 30 secondsn”, “# Thermosteam’s LLE algorithm is stochastic,n”, “# so its much slower than the VLE algorithm.n”, “# You’ll need to "pip install python-ternary" to run this linen”, “eq.plot_lle_ternary_diagram(‘Water’, ‘Ethanol’, ‘EthylAcetate’, T=298.15)n”, “plt.show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Vapor-liquid equilibrium”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Vapor-liquid equilibrium can be performed by setting 2 degrees of freedom from the following list: T (Temperature; in K), P (Pressure; in Pa), V (Vapor fraction), and H (Enthalpy; in kJ/hr).n”, “n”, “For example, set vapor fraction and pressure:”

]

}, {

“cell_type”: “code”, “execution_count”: 70, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“MultiStream: s_eqn”, ” phases: (‘g’, ‘l’), T: 353.88 K, P: 101325 Pan”, ” composition: (g) Water 0.3861n”, ” Ethanol 0.6139n”, ” ——- 10 kmol/hrn”, ” (l) Water 0.6139n”, ” Ethanol 0.3861n”, ” ——- 10 kmol/hrn”

]

}

], “source”: [

“s_eq = tmo.Stream(‘s_eq’, Water=10, Ethanol=10)n”, “s_eq.vle(V=0.5, P=101325)n”, “s_eq.show(composition=True)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Note that the stream is a now a MultiStream to manage multiple phases. Each phase can be accessed separately too:”

]

}, {

“cell_type”: “code”, “execution_count”: 71, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Stream: n”, ” phase: ‘l’, T: 353.88 K, P: 101325 Pan”, ” flow (kmol/hr): Water 6.14n”, ” Ethanol 3.86n”

]

}

], “source”: [

“s_eq[‘l’].show()”

]

}, {

“cell_type”: “code”, “execution_count”: 72, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Stream: n”, ” phase: ‘g’, T: 353.88 K, P: 101325 Pan”, ” flow (kmol/hr): Water 3.86n”, ” Ethanol 6.14n”

]

}

], “source”: [

“s_eq[‘g’].show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Note that the phase of these substreams cannot be changed:”

]

}, {

“cell_type”: “code”, “execution_count”: 73, “metadata”: {}, “outputs”: [

{

“ename”: “AttributeError”, “evalue”: “phase is locked”, “output_type”: “error”, “traceback”: [

“u001b[1;31m—————————————————————————u001b[0m”, “u001b[1;31mAttributeErroru001b[0m Traceback (most recent call last)”, “u001b[1;32m<ipython-input-73-ed0136a78442>u001b[0m in u001b[0;36m<module>u001b[1;34mu001b[0mnu001b[1;32m—-> 1u001b[1;33m u001b[0ms_equ001b[0mu001b[1;33m[u001b[0mu001b[1;34m’g’u001b[0mu001b[1;33m]u001b[0mu001b[1;33m.u001b[0mu001b[0mphaseu001b[0m u001b[1;33m=u001b[0m u001b[1;34m’l’u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[0m”, “u001b[1;32m~\OneDrive\Code\thermosteam\thermosteam\_stream.pyu001b[0m in u001b[0;36mphaseu001b[1;34m(self, phase)u001b[0mnu001b[0;32m 589u001b[0m u001b[1;33m@u001b[0mu001b[0mphaseu001b[0mu001b[1;33m.u001b[0mu001b[0msetteru001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[0;32m 590u001b[0m u001b[1;32mdefu001b[0m u001b[0mphaseu001b[0mu001b[1;33m(u001b[0mu001b[0mselfu001b[0mu001b[1;33m,u001b[0m u001b[0mphaseu001b[0mu001b[1;33m)u001b[0mu001b[1;33m:u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[1;32m–> 591u001b[1;33m u001b[0mselfu001b[0mu001b[1;33m.u001b[0mu001b[0m_imolu001b[0mu001b[1;33m.u001b[0mu001b[0mphaseu001b[0m u001b[1;33m=u001b[0m u001b[0mphaseu001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[0mu001b[0;32m 592u001b[0m u001b[1;33mu001b[0mu001b[0mnu001b[0;32m 593u001b[0m u001b[1;33m@u001b[0mu001b[0mpropertyu001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mn”, “u001b[1;32m~\OneDrive\Code\thermosteam\thermosteam\indexer.pyu001b[0m in u001b[0;36mphaseu001b[1;34m(self, phase)u001b[0mnu001b[0;32m 223u001b[0m u001b[1;33m@u001b[0mu001b[0mphaseu001b[0mu001b[1;33m.u001b[0mu001b[0msetteru001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[0;32m 224u001b[0m u001b[1;32mdefu001b[0m u001b[0mphaseu001b[0mu001b[1;33m(u001b[0mu001b[0mselfu001b[0mu001b[1;33m,u001b[0m u001b[0mphaseu001b[0mu001b[1;33m)u001b[0mu001b[1;33m:u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[1;32m–> 225u001b[1;33m u001b[0mselfu001b[0mu001b[1;33m.u001b[0mu001b[0m_phaseu001b[0mu001b[1;33m.u001b[0mu001b[0mphaseu001b[0m u001b[1;33m=u001b[0m u001b[0mphaseu001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[0mu001b[0;32m 226u001b[0m u001b[1;33mu001b[0mu001b[0mnu001b[0;32m 227u001b[0m u001b[1;32mdefu001b[0m u001b[0m__format__u001b[0mu001b[1;33m(u001b[0mu001b[0mselfu001b[0mu001b[1;33m,u001b[0m u001b[0mtabsu001b[0mu001b[1;33m=u001b[0mu001b[1;34m""u001b[0mu001b[1;33m)u001b[0mu001b[1;33m:u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mn”, “u001b[1;32m~\OneDrive\Code\thermosteam\thermosteam\_phase.pyu001b[0m in u001b[0;36m__setattr__u001b[1;34m(self, name, value)u001b[0mnu001b[0;32m 68u001b[0m u001b[1;32mdefu001b[0m u001b[0m__setattr__u001b[0mu001b[1;33m(u001b[0mu001b[0mselfu001b[0mu001b[1;33m,u001b[0m u001b[0mnameu001b[0mu001b[1;33m,u001b[0m u001b[0mvalueu001b[0mu001b[1;33m)u001b[0mu001b[1;33m:u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[0;32m 69u001b[0m u001b[1;32mifu001b[0m u001b[0mvalueu001b[0m u001b[1;33m!=u001b[0m u001b[0mselfu001b[0mu001b[1;33m.u001b[0mu001b[0mphaseu001b[0mu001b[1;33m:u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[1;32m—> 70u001b[1;33m u001b[1;32mraiseu001b[0m u001b[0mAttributeErroru001b[0mu001b[1;33m(u001b[0mu001b[1;34m’phase is locked’u001b[0mu001b[1;33m)u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[0mu001b[0;32m 71u001b[0m u001b[1;33mu001b[0mu001b[0mnu001b[0;32m 72u001b[0m u001b[0mNoPhaseu001b[0m u001b[1;33m=u001b[0m u001b[0mLockedPhaseu001b[0mu001b[1;33m(u001b[0mu001b[1;32mNoneu001b[0mu001b[1;33m)u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mn”, “u001b[1;31mAttributeErroru001b[0m: phase is locked”

]

}

], “source”: [

“s_eq[‘g’].phase = ‘l’”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Again, the most convenient way to get and set flow rates is through the get_flow and set_flow methods:”

]

}, {

“cell_type”: “code”, “execution_count”: 74, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“1.0”

]

}, “execution_count”: 74, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Set flow of liquid watern”, “s_eq.set_flow(1, ‘gpm’, (‘l’, ‘Water’))n”, “s_eq.get_flow(‘gpm’, (‘l’, ‘Water’))”

]

}, {

“cell_type”: “code”, “execution_count”: 75, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“array([10., 20.])”

]

}, “execution_count”: 75, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Set multiple liquid flowsn”, “key = (‘l’, (‘Ethanol’, ‘Water’))n”, “s_eq.set_flow([10, 20], ‘kg/hr’, key)n”, “s_eq.get_flow(‘kg/hr’, key)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Chemical flows across all phases can be retrieved if no phase is given:”

]

}, {

“cell_type”: “code”, “execution_count”: 76, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“array([ 89.565, 292.793])”

]

}, “execution_count”: 76, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Get water and ethanol flows summed across all phasesn”, “s_eq.get_flow(‘kg/hr’, (‘Water’, ‘Ethanol’))”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“However, setting chemical data of MultiStream objects requires the phase to be specified:”

]

}, {

“cell_type”: “code”, “execution_count”: 77, “metadata”: {}, “outputs”: [

{

“ename”: “IndexError”, “evalue”: “multiple phases present; must include phase key to set chemical data”, “output_type”: “error”, “traceback”: [

“u001b[1;31m—————————————————————————u001b[0m”, “u001b[1;31mIndexErroru001b[0m Traceback (most recent call last)”, “u001b[1;32m<ipython-input-77-d6cf98178f52>u001b[0m in u001b[0;36m<module>u001b[1;34mu001b[0mnu001b[1;32m—-> 1u001b[1;33m u001b[0ms_equ001b[0mu001b[1;33m.u001b[0mu001b[0mset_flowu001b[0mu001b[1;33m(u001b[0mu001b[1;33m[u001b[0mu001b[1;36m10u001b[0mu001b[1;33m,u001b[0m u001b[1;36m20u001b[0mu001b[1;33m]u001b[0mu001b[1;33m,u001b[0m u001b[1;34m’kg/hr’u001b[0mu001b[1;33m,u001b[0m u001b[1;33m(u001b[0mu001b[1;34m’Water’u001b[0mu001b[1;33m,u001b[0m u001b[1;34m’Ethanol’u001b[0mu001b[1;33m)u001b[0mu001b[1;33m)u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[0m”, “u001b[1;32m~\OneDrive\Code\thermosteam\thermosteam\_multi_stream.pyu001b[0m in u001b[0;36mset_flowu001b[1;34m(self, data, units, key)u001b[0mnu001b[0;32m 308u001b[0m u001b[0mnameu001b[0mu001b[1;33m,u001b[0m u001b[0mfactoru001b[0m u001b[1;33m=u001b[0m u001b[0mselfu001b[0mu001b[1;33m.u001b[0mu001b[0m_get_flow_name_and_factoru001b[0mu001b[1;33m(u001b[0mu001b[0munitsu001b[0mu001b[1;33m)u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[0;32m 309u001b[0m u001b[0mindexeru001b[0m u001b[1;33m=u001b[0m u001b[0mgetattru001b[0mu001b[1;33m(u001b[0mu001b[0mselfu001b[0mu001b[1;33m,u001b[0m u001b[1;34m’i’u001b[0m u001b[1;33m+u001b[0m u001b[0mnameu001b[0mu001b[1;33m)u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[1;32m–> 310u001b[1;33m u001b[0mindexeru001b[0mu001b[1;33m[u001b[0mu001b[0mkeyu001b[0mu001b[1;33m]u001b[0m u001b[1;33m=u001b[0m u001b[0mnpu001b[0mu001b[1;33m.u001b[0mu001b[0masarrayu001b[0mu001b[1;33m(u001b[0mu001b[0mdatau001b[0mu001b[1;33m,u001b[0m u001b[0mdtypeu001b[0mu001b[1;33m=u001b[0mu001b[0mfloatu001b[0mu001b[1;33m)u001b[0m u001b[1;33m/u001b[0m u001b[0mfactoru001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[0mu001b[0;32m 311u001b[0m u001b[1;33mu001b[0mu001b[0mnu001b[0;32m 312u001b[0m u001b[1;31m### Stream data ###u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mn”, “u001b[1;32m~\OneDrive\Code\thermosteam\thermosteam\indexer.pyu001b[0m in u001b[0;36m__setitem__u001b[1;34m(self, key, data)u001b[0mnu001b[0;32m 484u001b[0m u001b[0mindexu001b[0m u001b[1;33m=u001b[0m u001b[0mselfu001b[0mu001b[1;33m.u001b[0mu001b[0mget_indexu001b[0mu001b[1;33m(u001b[0mu001b[0mkeyu001b[0mu001b[1;33m)u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[0;32m 485u001b[0m u001b[1;32mifu001b[0m u001b[0misau001b[0mu001b[1;33m(u001b[0mu001b[0mindexu001b[0mu001b[1;33m,u001b[0m u001b[0mChemicalIndexu001b[0mu001b[1;33m)u001b[0mu001b[1;33m:u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[1;32m–> 486u001b[1;33m raise IndexError("multiple phases present; must include phase key "nu001b[0mu001b[0;32m 487u001b[0m "to set chemical data") nu001b[0;32m 488u001b[0m u001b[0mselfu001b[0mu001b[1;33m.u001b[0mu001b[0m_datau001b[0mu001b[1;33m[u001b[0mu001b[0mindexu001b[0mu001b[1;33m]u001b[0m u001b[1;33m=u001b[0m u001b[0mdatau001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mn”, “u001b[1;31mIndexErroru001b[0m: multiple phases present; must include phase key to set chemical data”

]

}

], “source”: [

“s_eq.set_flow([10, 20], ‘kg/hr’, (‘Water’, ‘Ethanol’))”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Similar to Stream objects, all flow rates can be accessed through the imol, imass, and ivol attributes:”

]

}, {

“cell_type”: “code”, “execution_count”: 78, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“MolarFlowIndexer (kmol/hr):n”, ” (g) Water 3.861n”, ” Ethanol 6.139n”, ” (l) Water 1.11n”, ” Ethanol 0.2171n”

]

}

], “source”: [

“s_eq.imol # Molar flow rates”

]

}, {

“cell_type”: “code”, “execution_count”: 79, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“1.1101687012358397”

]

}, “execution_count”: 79, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Index a single chemical in the liquid phasen”, “s_eq.imol[‘l’, ‘Water’]”

]

}, {

“cell_type”: “code”, “execution_count”: 80, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“array([0.217, 1.11 ])”

]

}, “execution_count”: 80, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Index multiple chemicals in the liquid phasen”, “s_eq.imol[‘l’, (‘Ethanol’, ‘Water’)]”

]

}, {

“cell_type”: “code”, “execution_count”: 81, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“array([3.861, 6.139, 0. ])”

]

}, “execution_count”: 81, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Index the vapor phasen”, “s_eq.imol[‘g’]”

]

}, {

“cell_type”: “code”, “execution_count”: 82, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“array([6.356, 4.972])”

]

}, “execution_count”: 82, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Index flow of chemicals summed across all phasesn”, “s_eq.imol[‘Ethanol’, ‘Water’]”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Because multiple phases are present, overall chemical flows in MultiStream objects cannot be set like in Stream objects:”

]

}, {

“cell_type”: “code”, “execution_count”: 83, “metadata”: {}, “outputs”: [

{

“ename”: “IndexError”, “evalue”: “multiple phases present; must include phase key to set chemical data”, “output_type”: “error”, “traceback”: [

“u001b[1;31m—————————————————————————u001b[0m”, “u001b[1;31mIndexErroru001b[0m Traceback (most recent call last)”, “u001b[1;32m<ipython-input-83-fcb482ddb0a2>u001b[0m in u001b[0;36m<module>u001b[1;34mu001b[0mnu001b[1;32m—-> 1u001b[1;33m u001b[0ms_equ001b[0mu001b[1;33m.u001b[0mu001b[0mimolu001b[0mu001b[1;33m[u001b[0mu001b[1;34m’Ethanol’u001b[0mu001b[1;33m,u001b[0m u001b[1;34m’Water’u001b[0mu001b[1;33m]u001b[0m u001b[1;33m=u001b[0m u001b[1;33m[u001b[0mu001b[1;36m1u001b[0mu001b[1;33m,u001b[0m u001b[1;36m0u001b[0mu001b[1;33m]u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[0m”, “u001b[1;32m~\OneDrive\Code\thermosteam\thermosteam\indexer.pyu001b[0m in u001b[0;36m__setitem__u001b[1;34m(self, key, data)u001b[0mnu001b[0;32m 484u001b[0m u001b[0mindexu001b[0m u001b[1;33m=u001b[0m u001b[0mselfu001b[0mu001b[1;33m.u001b[0mu001b[0mget_indexu001b[0mu001b[1;33m(u001b[0mu001b[0mkeyu001b[0mu001b[1;33m)u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[0;32m 485u001b[0m u001b[1;32mifu001b[0m u001b[0misau001b[0mu001b[1;33m(u001b[0mu001b[0mindexu001b[0mu001b[1;33m,u001b[0m u001b[0mChemicalIndexu001b[0mu001b[1;33m)u001b[0mu001b[1;33m:u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[1;32m–> 486u001b[1;33m raise IndexError("multiple phases present; must include phase key "nu001b[0mu001b[0;32m 487u001b[0m "to set chemical data") nu001b[0;32m 488u001b[0m u001b[0mselfu001b[0mu001b[1;33m.u001b[0mu001b[0m_datau001b[0mu001b[1;33m[u001b[0mu001b[0mindexu001b[0mu001b[1;33m]u001b[0m u001b[1;33m=u001b[0m u001b[0mdatau001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mn”, “u001b[1;31mIndexErroru001b[0m: multiple phases present; must include phase key to set chemical data”

]

}

], “source”: [

“s_eq.imol[‘Ethanol’, ‘Water’] = [1, 0]”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Chemical flows must be set by phase:”

]

}, {

“cell_type”: “code”, “execution_count”: 84, “metadata”: {}, “outputs”: [], “source”: [

“s_eq.imol[‘l’, (‘Ethanol’, ‘Water’)] = [1, 0]”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“One main difference between a [MultiStream](../MultiStream.txt) object and a [Stream](../Stream.txt) object is that the mol attribute no longer stores any data, it simply returns the total flow rate of each chemical. Setting an element of the array raises an error to prevent the wrong assumption that the data is linked:”

]

}, {

“cell_type”: “code”, “execution_count”: 85, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“array([3.861, 7.139, 0. ])”

]

}, “execution_count”: 85, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“s_eq.mol”

]

}, {

“cell_type”: “code”, “execution_count”: 86, “metadata”: {}, “outputs”: [

{

“ename”: “ValueError”, “evalue”: “assignment destination is read-only”, “output_type”: “error”, “traceback”: [

“u001b[1;31m—————————————————————————u001b[0m”, “u001b[1;31mValueErroru001b[0m Traceback (most recent call last)”, “u001b[1;32m<ipython-input-86-632093460ce3>u001b[0m in u001b[0;36m<module>u001b[1;34mu001b[0mnu001b[1;32m—-> 1u001b[1;33m u001b[0ms_equ001b[0mu001b[1;33m.u001b[0mu001b[0mmolu001b[0mu001b[1;33m[u001b[0mu001b[1;36m0u001b[0mu001b[1;33m]u001b[0m u001b[1;33m=u001b[0m u001b[1;36m1u001b[0mu001b[1;33mu001b[0mu001b[1;33mu001b[0mu001b[0mnu001b[0m”, “u001b[1;31mValueErroru001b[0m: assignment destination is read-only”

]

}

], “source”: [

“s_eq.mol[0] = 1”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Note that for both Stream and MultiStream objects, get_flow, imol, and mol return chemical flows across all phases when given only chemical IDs.”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Liquid-liquid equilibrium”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Liquid-liquid equilibrium (LLE) only requires the temperature. Pressure is not a significant variable as liquid fungacity coefficients are not a strong function of pressure. “

]

}, {

“cell_type”: “code”, “execution_count”: 87, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“MultiStream: liquid_mixturen”, ” phases: (‘L’, ‘l’), T: 300 K, P: 101325 Pan”, ” flow (kmol/hr): (L) Water 98.54n”, ” Butanol 1.209n”, ” Octane 0.001977n”, ” (l) Water 1.458n”, ” Butanol 3.791n”, ” Octane 100n”

]

}

], “source”: [

“tmo.settings.set_thermo([‘Water’, ‘Butanol’, ‘Octane’])n”, “liquid_mixture = tmo.Stream(‘liquid_mixture’, Water=100, Octane=100, Butanol=5)n”, “liquid_mixture.lle(T=300)n”, “liquid_mixture.show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Compared to VLE, LLE is several orders of magnitude times slower. This is because differential evolution, a purely stochastic method, is used to find the solution that globally minimizes the gibb’s free energy of both phases. For now, the LLE algorithm may not present completely accurate results and is subject to change in the future.”

]

}

], “metadata”: {

“kernelspec”: {

“display_name”: “Python 3”, “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.7.6”

}

}, “nbformat”: 4, “nbformat_minor”: 2

}

{
“cells”: [
{

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“# Thermodynamic equilibrium”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“It is not necessary to use a Stream object to use thermodynamic equilibrium methods. In fact, thermosteam makes it just as easy to compute vapor-liquid equilibrium, bubble and dew points, and fugacities. “

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Fugacities”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“The easiest way to calculate fugacities is through LiquidFugacities and GasFugacities objects:”

]

}, {

“cell_type”: “code”, “execution_count”: 1, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“array([43274.119, 58056.67 ])”

]

}, “execution_count”: 1, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“import thermosteam as tmon”, “import numpy as npn”, “chemicals = tmo.Chemicals([‘Water’, ‘Ethanol’])n”, “tmo.settings.set_thermo(chemicals)n”, “n”, “# Create a LiquidFugacities objectn”, “F_l = tmo.equilibrium.LiquidFugacities(chemicals)n”, “n”, “# Compute liquid fugacitiesn”, “liquid_molar_composition = np.array([0.72, 0.28])n”, “f_l = F_l(x=liquid_molar_composition, T=355)n”, “f_l “

]

}, {

“cell_type”: “code”, “execution_count”: 2, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“array([43569.75, 57755.25])”

]

}, “execution_count”: 2, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Create a GasFugacities objectn”, “F_g = tmo.equilibrium.GasFugacities(chemicals)n”, “n”, “# Compute gas fugacitiesn”, “gas_molar_composition = np.array([0.43, 0.57])n”, “f_g = F_g(y=gas_molar_composition, T=355, P=101325)n”, “f_g”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Bubble and dew points”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Similarly bubble and dew point can be calculated through BubblePoint and DewPoint objects:”

]

}, {

“cell_type”: “code”, “execution_count”: 3, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“BubblePointValues(T=355.00, P=109755, IDs=(‘Water’, ‘Ethanol’), z=[0.5 0.5], y=[0.343 0.657])”

]

}, “execution_count”: 3, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Create a BubblePoint objectn”, “BP = tmo.equilibrium.BubblePoint(chemicals)n”, “molar_composition = np.array([0.5, 0.5])n”, “n”, “# Solve bubble point at constant temperaturen”, “bp = BP(z=molar_composition, T=355)n”, “bp”

]

}, {

“cell_type”: “code”, “execution_count”: 4, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“(355,n”, ” 109755.45319869411,n”, ” (‘Water’, ‘Ethanol’),n”, ” array([0.5, 0.5]),n”, ” array([0.343, 0.657]))”

]

}, “execution_count”: 4, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Note that the result is a BubblePointValues object which contain all results as attibutesn”, “(bp.T, bp.P, bp.IDs, bp.z, bp.y)”

]

}, {

“cell_type”: “code”, “execution_count”: 5, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“BubblePointValues(T=371.78, P=202650, IDs=(‘Water’, ‘Ethanol’), z=[0.5 0.5], y=[0.35 0.65])”

]

}, “execution_count”: 5, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Solve bubble point at constant pressuren”, “BP(z=molar_composition, P=2*101325)”

]

}, {

“cell_type”: “code”, “execution_count”: 6, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“DewPointValues(T=355.00, P=91970, IDs=(‘Water’, ‘Ethanol’), z=[0.5 0.5], x=[0.851 0.149])”

]

}, “execution_count”: 6, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Create a DewPoint objectn”, “DP = tmo.equilibrium.DewPoint(chemicals)n”, “n”, “# Solve for dew point at constant temperautren”, “dp = DP(z=molar_composition, T=355)n”, “dp”

]

}, {

“cell_type”: “code”, “execution_count”: 7, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“(355,n”, ” 91970.1496840849,n”, ” (‘Water’, ‘Ethanol’),n”, ” array([0.5, 0.5]),n”, ” array([0.851, 0.149]))”

]

}, “execution_count”: 7, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Note that the result is a DewPointValues object which contain all results as attibutesn”, “(dp.T, dp.P, dp.IDs, dp.z, dp.x)”

]

}, {

“cell_type”: “code”, “execution_count”: 8, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“DewPointValues(T=376.26, P=202648, IDs=(‘Water’, ‘Ethanol’), z=[0.5 0.5], x=[0.832 0.168])”

]

}, “execution_count”: 8, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# Solve for dew point at constant pressuren”, “DP(z=molar_composition, P=2*101324)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Vapor liquid equilibrium”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Vapor-liquid equilibrium can be calculated through a VLE object:”

]

}, {

“cell_type”: “code”, “execution_count”: 9, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“VLE(imol=MaterialIndexer(n”, ” g=[(‘Water’, 0.5), (‘Ethanol’, 0.5)],n”, ” l=[(‘Water’, 0.5), (‘Ethanol’, 0.5)]),n”, ” thermal_condition=ThermalCondition(T=298.15, P=101325))”

]

}, “execution_count”: 9, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“# First create a material indexer for the VLE object to manage material datan”, “imol = tmo.indexer.MaterialIndexer(l=[(‘Water’, 0.5), (‘Ethanol’, 0.5)],n”, ” g=[(‘Water’, 0.5), (‘Ethanol’, 0.5)])n”, “n”, “# Create a VLE objectn”, “vle = tmo.equilibrium.VLE(imol)n”, “vle”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“You can call the VLE object by setting 2 degrees of freedom from the following list: n”, “n”, “* T - Temperature [K]n”, “* P - Pressure [Pa]n”, “* V - Molar vapor fractionn”, “* H - Enthalpy [kJ/hr]n”, “* y - Binary molar vapor compositionn”, “* x - Binary molar liquid compositionn”, “n”, “Here are some examples:”

]

}, {

“cell_type”: “code”, “execution_count”: 10, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“VLE(imol=MaterialIndexer(n”, ” g=[(‘Water’, 0.6417), (‘Ethanol’, 0.8607)],n”, ” l=[(‘Water’, 0.3583), (‘Ethanol’, 0.1393)]),n”, ” thermal_condition=ThermalCondition(T=355.00, P=101325))”

]

}, “execution_count”: 10, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“vle(T=355, P=101325)n”, “vle”

]

}, {

“cell_type”: “code”, “execution_count”: 11, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“VLE(imol=MaterialIndexer(n”, ” g=[(‘Water’, 0.6161), (‘Ethanol’, 0.8266)],n”, ” l=[(‘Water’, 0.3839), (‘Ethanol’, 0.1734)]),n”, ” thermal_condition=ThermalCondition(T=373.69, P=202650))”

]

}, “execution_count”: 11, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“mixture_enthalpy = vle.mixture.xH(imol, *vle.thermal_condition)n”, “vle(H=mixture_enthalpy, P=202650)n”, “vle”

]

}, {

“cell_type”: “code”, “execution_count”: 12, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“VLE(imol=MaterialIndexer(n”, ” g=[(‘Water’, 0.3861), (‘Ethanol’, 0.6139)],n”, ” l=[(‘Water’, 0.6139), (‘Ethanol’, 0.3861)]),n”, ” thermal_condition=ThermalCondition(T=353.88, P=101325))”

]

}, “execution_count”: 12, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“vle(V=0.5, P=101325)n”, “vle”

]

}, {

“cell_type”: “code”, “execution_count”: 13, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“VLE(imol=MaterialIndexer(n”, ” g=[(‘Water’, 0.3886), (‘Ethanol’, 0.6114)],n”, ” l=[(‘Water’, 0.6114), (‘Ethanol’, 0.3886)]),n”, ” thermal_condition=ThermalCondition(T=360.00, P=128136))”

]

}, “execution_count”: 13, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“vle(V=0.5, T=360)n”, “vle”

]

}, {

“cell_type”: “code”, “execution_count”: 14, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“VLE(imol=MaterialIndexer(n”, ” g=[(‘Water’, 0.8356), (‘Ethanol’, 0.9589)],n”, ” l=[(‘Water’, 0.1644), (‘Ethanol’, 0.04109)]),n”, ” thermal_condition=ThermalCondition(T=356.25, P=128136))”

]

}, “execution_count”: 14, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“vle(x=np.array([0.8, 0.2]), P=101325)n”, “vle”

]

}, {

“cell_type”: “code”, “execution_count”: 15, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“VLE(imol=MaterialIndexer(n”, ” g=[(‘Water’, 0.4691), (‘Ethanol’, 0.7036)],n”, ” l=[(‘Water’, 0.5309), (‘Ethanol’, 0.2964)]),n”, ” thermal_condition=ThermalCondition(T=356.25, P=126727))”

]

}, “execution_count”: 15, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“vle(y=np.array([0.4, 0.6]), T=360)n”, “vle”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Note that some compositions are infeasible; so it is not advised to pass x or y unless you know what you’re doing:”

]

}, {

“cell_type”: “code”, “execution_count”: 16, “metadata”: {}, “outputs”: [], “source”: [

“vle(x=np.array([0.2, 0.8]), P=101325)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Liquid-liquid equilibrium”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Liquid-liquid equilibrium can be calculated through a LLE object:”

]

}, {

“cell_type”: “code”, “execution_count”: 17, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“LLE(imol=MolarFlowIndexer(n”, ” L=[(‘Water’, 290.7), (‘Octane’, 0.02063), (‘Butanol’, 4.3)],n”, ” l=[(‘Water’, 13.35), (‘Octane’, 99.98), (‘Butanol’, 25.7)]),n”, ” thermal_condition=ThermalCondition(T=360.00, P=101325))”

]

}, “execution_count”: 17, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“tmo.settings.set_thermo([‘Water’, ‘Octane’, ‘Butanol’])n”, “imol = tmo.indexer.MolarFlowIndexer(n”, ” l=[(‘Water’, 304), (‘Butanol’, 30)],n”, ” L=[(‘Octane’, 100)])n”, “lle = tmo.equilibrium.LLE(imol)n”, “lle(T=360)n”, “lle”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Pressure is not a significant factor in liquid-liquid equilibrium, so only temperature is needed.”

]

}

], “metadata”: {

“kernelspec”: {

“display_name”: “Python 3”, “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.7.6”

}

}, “nbformat”: 4, “nbformat_minor”: 2

}

{
“cells”: [
{

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“# Thermo property packages”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“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 the co-production of biodiesel and ethanol from lipid-cane [[1-2]](#References).n”, “n”, “![](./lipidcane_areas.png "Lipid-cane diagram")”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Creating chemicals”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

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

]

}, {

“cell_type”: “code”, “execution_count”: 1, “metadata”: {}, “outputs”: [], “source”: [

“import thermosteam as tmon”, “Biodiesel = tmo.Chemical(‘Biodiesel’,n”, ” search_ID=’Methyl oleate’)n”, “lipidcane_chemicals = tmo.Chemicals(n”, ” [‘Water’, ‘Methanol’, ‘Ethanol’, ‘Glycerol’,n”, ” ‘Glucose’, ‘Sucrose’, ‘H3PO4’, ‘P4O10’, ‘CO2’,n”, ” ‘Octane’, ‘O2’, Biodiesel])n”, “n”, “(Water, Methanol, Ethanol,n”, ” Glycerol, Glucose, Sucrose,n”, ” H3PO4, P4O10, CO2, Octane, O2, Biodiesel) = lipidcane_chemicals”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

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

]

}, {

“cell_type”: “code”, “execution_count”: 2, “metadata”: {}, “outputs”: [], “source”: [

“O2.at_state(phase=’g’)n”, “CO2.at_state(phase=’g’)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

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

]

}, {

“cell_type”: “code”, “execution_count”: 3, “metadata”: {}, “outputs”: [], “source”: [

“H3PO4.at_state(phase=’s’)n”, “P4O10.at_state(phase=’s’)n”, “Glucose.at_state(phase=’s’)n”, “Sucrose.at_state(phase=’s’)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“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:”

]

}, {

“cell_type”: “code”, “execution_count”: 4, “metadata”: {}, “outputs”: [], “source”: [

“def create_new_chemical(ID, phase=’s’, **constants):n”, ” # Create a new solid chemical without any datan”, ” solid = tmo.Chemical.blank(ID, phase=phase, **constants)n”, ” n”, ” # Add chemical to the Chemicals objectn”, ” lipidcane_chemicals.append(solid)n”, ” n”, ” return solidn”, “n”, “# Cellulose and hemicellulose are modeledn”, “# as their monomer minus on H2O.n”, “Cellulose = create_new_chemical(‘Cellulose’,n”, ” formula="C6H10O5", # Hydrolyzed glucose monomern”, ” Hf=-975708.8)n”, “Hemicellulose = create_new_chemical(‘Hemicellulose’,n”, ” formula="C5H8O5", # Hydrolyzed xylose monomern”, ” Hf=-761906.4)n”, “Flocculant = create_new_chemical(‘Flocculant’, MW=1.)n”, “# Lignin is modeled as vanillin.n”, “Lignin = create_new_chemical(‘Lignin’,n”, ” formula=’C8H8O3’, # Vanillinn”, ” Hf=-452909.632)n”, “Ash = create_new_chemical(‘Ash’, MW=1.)n”, “Solids = create_new_chemical(‘Solids’, MW=1.)n”, “DryYeast = create_new_chemical(‘DryYeast’, MW=1., CAS=’Yeast’)n”, “NaOCH3 = create_new_chemical(‘NaOCH3’, formula=’NaOCH3’)n”, “CaO = create_new_chemical(‘CaO’, formula=’CaO’)n”, “HCl = create_new_chemical(‘HCl’, formula=’HCl’)n”, “NaOH = create_new_chemical(‘NaOH’, formula=’NaOH’)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“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:”

]

}, {

“cell_type”: “code”, “execution_count”: 5, “metadata”: {}, “outputs”: [], “source”: [

“Lipid = create_new_chemical(n”, ” ‘Lipid’,n”, ” phase=’l’,n”, ” Hf=-2193.7e3,n”, ” formula = ‘C57H104O6’,n”, “)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“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):”

]

}, {

“cell_type”: “code”, “execution_count”: 6, “metadata”: {}, “outputs”: [], “source”: [

“Tripalmitin = tmo.Chemical(‘Tripalmitin’).at_state(phase=’l’, copy=True)n”, “Lipid.copy_models_from(Tripalmitin, [‘V’, ‘sigma’, ‘kappa’, ‘Cn’])”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“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):”

]

}, {

“cell_type”: “code”, “execution_count”: 7, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“1.1”

]

}, “execution_count”: 7, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“from thermosteam import functional as fnn”, “n”, “# Assume a constant volume for lipidn”, “lipid_molar_volume = fn.rho_to_V(rho=900, MW=Lipid.MW)n”, “Lipid.V.add_model(lipid_molar_volume)n”, “n”, “# Insolubles occupy a significant volumen”, “insoluble_solids = (Ash, Cellulose, Hemicellulose,n”, ” Flocculant, Lignin, Solids, DryYeast, P4O10)n”, “for chemical in insoluble_solids:n”, ” V = fn.rho_to_V(rho=1540, MW=chemical.MW)n”, ” chemical.V.add_model(V, top_priority=True)n”, “n”, “# Solubles don’t occupy much volumen”, “soluble_solids = (CaO, HCl, NaOH, H3PO4, Sucrose, Glucose) n”, “for chemical in soluble_solids:n”, ” V = fn.rho_to_V(rho=1e5, MW=chemical.MW)n”, ” chemical.V.add_model(V, top_priority=True)n”, “n”, “n”, “# Assume sodium methoxide has some of the same properities as methanoln”, “LiquidMethanol = Methanol.at_state(phase=’l’, copy=True)n”, “NaOCH3.copy_models_from(LiquidMethanol, [‘V’, ‘sigma’,’kappa’, ‘Cn’])n”, “n”, “# Add constant models for molar heat capacity of solidsn”, “Ash.Cn.add_model(0.09 * 4.184 * Ash.MW) n”, “CaO.Cn.add_model(1.02388 * CaO.MW) n”, “Cellulose.Cn.add_model(1.364 * Cellulose.MW) n”, “Hemicellulose.Cn.add_model(1.364 * Hemicellulose.MW)n”, “Flocculant.Cn.add_model(4.184 * Flocculant.MW)n”, “Lignin.Cn.add_model(1.364 * Lignin.MW)n”, “Solids.Cn.add_model(1.100 * Solids.MW)n”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“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:”

]

}, {

“cell_type”: “code”, “execution_count”: 8, “metadata”: {}, “outputs”: [], “source”: [

“for chemical in lipidcane_chemicals: chemical.default()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Finalize the chemicals by compiling:”

]

}, {

“cell_type”: “code”, “execution_count”: 9, “metadata”: {}, “outputs”: [], “source”: [

“lipidcane_chemicals.compile()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“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:”

]

}, {

“cell_type”: “code”, “execution_count”: 10, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“array([2., 0., 2., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,n”, ” 0., 0., 0., 0., 0., 0., 0.])”

]

}, “execution_count”: 10, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“lipidcane_chemicals.array([‘Water’, ‘Ethanol’], [2, 2])”

]

}, {

“cell_type”: “code”, “execution_count”: 11, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“[0, 2]”

]

}, “execution_count”: 11, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“lipidcane_chemicals.get_index((‘Water’, ‘Ethanol’))”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

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

]

}, {

“cell_type”: “code”, “execution_count”: 12, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Watern”

]

}

], “source”: [

“lipidcane_chemicals.set_synonym(‘Water’, ‘H2O’)n”, “print(lipidcane_chemicals.H2O)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Mixture objects”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“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:”

]

}, {

“cell_type”: “code”, “execution_count”: 13, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Mixture(n”, ” rule=’ideal mixing’, …n”, ” include_excess_energies=Falsen”, “)n”

]

}

], “source”: [

“mixture = tmo.mixture.ideal_mixture(lipidcane_chemicals,n”, ” include_excess_energies=False)n”, “mixture.show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“You can use the mixture for estimating mixture properties:”

]

}, {

“cell_type”: “code”, “execution_count”: 14, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“694.8277782515652”

]

}, “execution_count”: 14, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“array = lipidcane_chemicals.arrayn”, “mol = array([‘Water’, ‘Ethanol’], [2, 2])n”, “mixture.H(‘l’, mol, 300, 101325)”

]

}, {

“cell_type”: “code”, “execution_count”: 15, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“376.3037481383151”

]

}, “execution_count”: 15, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“mol = array([‘Water’, ‘Ethanol’], [2, 2])n”, “mixture.Cn(‘l’, mol, 300)”

]

}, {

“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”: 16, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“574.6441052202636”

]

}, “execution_count”: 16, “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”, “mixture.xCn(phase_data, T=300)”

]

}, {

“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:”

]

}, {

“cell_type”: “code”, “execution_count”: 17, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Thermo(n”, ” 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]),n”, ” mixture=Mixture(n”, ” rule=’ideal mixing’, …n”, ” include_excess_energies=Falsen”, ” ),n”, ” Gamma=DortmundActivityCoefficients,n”, ” Phi=IdealFugacityCoefficients,n”, ” PCF=IdealPoyintingCorrectionFactorsn”, “)n”

]

}

], “source”: [

“thermo = tmo.Thermo(lipidcane_chemicals, mixture)n”, “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, 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:”

]

}, {

“cell_type”: “code”, “execution_count”: 18, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Mixture(n”, ” rule=’ideal mixing’, …n”, ” include_excess_energies=Falsen”, “)n”

]

}

], “source”: [

“thermo = tmo.Thermo(lipidcane_chemicals)n”, “thermo.mixture.show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### References”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“<a id=’References’></a>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.1640n”, “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-2n”, “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-6n

]

}

], “metadata”: {

“kernelspec”: {

“display_name”: “Python 3”, “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.7.6”

}

}, “nbformat”: 4, “nbformat_minor”: 2

}

{
“cells”: [
{

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“# Stoichiometric reactions”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Thermosteam provides array based objects that can model stoichiometric reactions given a conversion.”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Single reaction”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Create a Reaction object based on the transesterification reaction:n”, “n”, “|Reaction|Reactant|% Converted|n”, “|---||---|n”, “|Lipid + 3Methanol -> 3Biodiesel + Glycerol|Lipid|90|n”

]

}, {

“cell_type”: “code”, “execution_count”: 1, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Reaction (by mol):n”, ” stoichiometry reactant X[%]n”, ” 3 Methanol + Lipid -> 3 Biodiesel + 1 Glycerol Lipid 90.00n”

]

}

], “source”: [

“import thermosteam as tmon”, “from biorefineries import lipidcane as lcn”, “n”, “tmo.settings.set_thermo(lc.chemicals)n”, “transesterification = tmo.Reaction(n”, ” ‘Lipid + Methanol -> Biodiesel + Glycerol’, # Reactionn”, ” correct_atomic_balance=True, # Corrects stoichiometric coefficients by atomic balancen”, ” reactant=’Lipid’, # Limiting reactantn”, ” X=0.9, # Conversionn”, “)n”, “transesterification.show()”

]

}, {

“cell_type”: “code”, “execution_count”: 2, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“CompiledChemicals([Water, Ethanol, Glucose, Sucrose, H3PO4, P4O10, CO2, Octane, O2, CH4, Ash, Cellulose, Hemicellulose, Flocculant, Lignin, Solids, Yeast, CaO, Biodiesel, Methanol, Glycerol, HCl, NaOH, NaOCH3, Lipid])n”

]

}

], “source”: [

“transesterification.chemicals”

]

}, {

“cell_type”: “code”, “execution_count”: 3, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,n”, ” 0., 0., 0., 0., 0., 3., -3., 1., 0., 0., 0., -1.])”

]

}, “execution_count”: 3, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“transesterification.stoichiometry”

]

}, {

“cell_type”: “code”, “execution_count”: 4, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“‘Lipid’”

]

}, “execution_count”: 4, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“transesterification.reactant”

]

}, {

“cell_type”: “code”, “execution_count”: 5, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“0.9”

]

}, “execution_count”: 5, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“transesterification.X”

]

}, {

“cell_type”: “code”, “execution_count”: 6, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“51641.999999999585”

]

}, “execution_count”: 6, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“transesterification.dH # Heat of reaction J / mol-reactant (accounts for conversion); latent heats are not included”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“When a Reaction object is called with a stream, it updates the material data to reflect the reaction:”

]

}, {

“cell_type”: “code”, “execution_count”: 7, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“BEFORE REACTIONn”, “Stream: s1n”, ” phase: ‘l’, T: 298.15 K, P: 101325 Pan”, ” flow (kmol/hr): Methanol 600n”, ” Lipid 100n”, “AFTER REACTIONn”, “Stream: s1n”, ” phase: ‘l’, T: 298.15 K, P: 101325 Pan”, ” flow (kmol/hr): Biodiesel 270n”, ” Methanol 330n”, ” Glycerol 90n”, ” Lipid 10n”

]

}

], “source”: [

“feed = tmo.Stream(Lipid=100, Methanol=600)n”, “print(‘BEFORE REACTION’)n”, “feed.show(N=100)n”, “n”, “# React feed molar flow raten”, “transesterification(feed)n”, “n”, “print(‘AFTER REACTION’)n”, “feed.show(N=100)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Let’s change the basis of the reaction:”

]

}, {

“cell_type”: “code”, “execution_count”: 8, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Reaction (by wt):n”, ” stoichiometry reactant X[%]n”, ” 0.109 Methanol + Lipid -> 1 Biodiesel + 0.104 Glycerol Lipid 90.00n”

]

}

], “source”: [

“transesterification.basis = ‘wt’n”, “transesterification.show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Notice that the stoichiometry also adjusted. If we react a stream, we should see the same result, regardless of basis:”

]

}, {

“cell_type”: “code”, “execution_count”: 9, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“BEFORE REACTIONn”, “Stream: s2n”, ” phase: ‘l’, T: 298.15 K, P: 101325 Pan”, ” flow (kmol/hr): Methanol 600n”, ” Lipid 100n”, “AFTER REACTIONn”, “Stream: s2n”, ” phase: ‘l’, T: 298.15 K, P: 101325 Pan”, ” flow (kmol/hr): Biodiesel 270n”, ” Methanol 330n”, ” Glycerol 90n”, ” Lipid 10n”

]

}

], “source”: [

“feed = tmo.Stream(Lipid=100, Methanol=600)n”, “print(‘BEFORE REACTION’)n”, “feed.show(N=100)n”, “n”, “# React feed molar flow raten”, “transesterification(feed)n”, “n”, “print(‘AFTER REACTION’)n”, “feed.show(N=100)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“The heat of reaction is now in units of J/kg-reactant:”

]

}, {

“cell_type”: “code”, “execution_count”: 10, “metadata”: {}, “outputs”: [

{
“data”: {
“text/plain”: [

“58.32406836499681”

]

}, “execution_count”: 10, “metadata”: {}, “output_type”: “execute_result”

}

], “source”: [

“transesterification.dH # Accounts for conversion too”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Note that these reactions are carried out isothermally, but it is also possible to do so adiabatically:”

]

}, {

“cell_type”: “code”, “execution_count”: 11, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“BEFORE REACTIONn”, “Stream: s3n”, ” phase: ‘l’, T: 298.15 K, P: 101325 Pan”, ” flow (kmol/hr): Water 5.55n”, ” Glucose 0.0555n”, “AFTER REACTIONn”, “Stream: s3n”, ” phase: ‘l’, T: 306.19 K, P: 101325 Pan”, ” flow (kmol/hr): Water 5.55n”, ” Ethanol 0.0999n”, ” Glucose 0.00555n”, ” CO2 0.0999n”

]

}

], “source”: [

“feed = tmo.Stream(Glucose=10, H2O=100, units=’kg/hr’)n”, “print(‘BEFORE REACTION’)n”, “feed.show(N=100)n”, “n”, “# React feed adiabatically (temperature should increase)n”, “fermentation = tmo.Reaction(‘Glucose + O2 -> Ethanol + CO2’,n”, ” reactant=’Glucose’, X=0.9,n”, ” correct_atomic_balance=True)n”, “fermentation.adiabatic_reaction(feed)n”, “n”, “print(‘AFTER REACTION’)n”, “feed.show(N=100)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Phases changes can be included in the reaction: “

]

}, {

“cell_type”: “code”, “execution_count”: 12, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Reaction (by mol):n”, ” stoichiometry reactant X[%]n”, ” Glucose,l -> 2 CO2,g + 2 Ethanol,l Glucose,l 90.00n”

]

}

], “source”: [

“fermentation = tmo.Reaction(‘Glucose,l -> Ethanol,l + CO2,g’,n”, ” reactant=’Glucose’, X=0.9,n”, ” correct_atomic_balance=True)n”, “fermentation.show()”

]

}, {

“cell_type”: “code”, “execution_count”: 13, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“BEFORE ADIABATIC REACTIONn”, “MultiStream: s4n”, ” phases: (‘g’, ‘l’), T: 298.15 K, P: 101325 Pan”, ” flow (kmol/hr): (l) Water 5.551n”, ” Glucose 0.05551n”, “AFTER ADIABATIC REACTIONn”, “MultiStream: s4n”, ” phases: (‘g’, ‘l’), T: 306.19 K, P: 101325 Pan”, ” flow (kmol/hr): (g) CO2 0.09991n”, ” (l) Water 5.551n”, ” Ethanol 0.09991n”, ” Glucose 0.005551n”

]

}

], “source”: [

“feed = tmo.Stream(Glucose=10, H2O=100, units=’kg/hr’)n”, “feed.phases = ‘gl’n”, “print(‘BEFORE ADIABATIC REACTION’)n”, “feed.show(N=100)n”, “n”, “# React feed adiabatically (temperature should increase)n”, “fermentation.adiabatic_reaction(feed)n”, “n”, “print(‘AFTER ADIABATIC REACTION’)n”, “feed.show(N=100)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Lastly, when working with positive ions, simply pass a dictionary of stoichiometric coefficients instead of the equation:”

]

}, {

“cell_type”: “code”, “execution_count”: 14, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“Reaction (by mol):n”, ” stoichiometry reactant X[%]n”, ” NaCl -> Na+ + Cl- NaCl 100.00n”

]

}

], “source”: [

“# First let’s define a new set of chemicalsn”, “chemicals = NaCl, SodiumIon, ChlorideIon = tmo.Chemicals([‘NaCl’, ‘Na+’, ‘Cl-‘])n”, “n”, “# We set the state to completely ignore other possible phasesn”, “NaCl.at_state(‘s’)n”, “SodiumIon.at_state(‘l’)n”, “ChlorideIon.at_state(‘l’)n”, “n”, “# Molar volume doesn’t matter in this scenario, but itsn”, “# required to compile the chemicals. We can assume n”, “# a very low volume since its in solution.n”, “NaCl.V.add_model(1e-6)n”, “SodiumIon.V.add_model(1e-6)n”, “ChlorideIon.V.add_model(1e-6)n”, “n”, “# We can pass a Chemicals object to not have to override n”, “# the lipidcane chemicals we set earlier.n”, “dissociation = tmo.Reaction({‘NaCl’:-1, ‘Na+’:1, ‘Cl-’: 1},n”, ” reactant=’NaCl’, X=1.,n”, ” chemicals=chemicals)n”, “dissociation.show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Parallel reactions”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Create a ParallelReaction from Reaction objects:”

]

}, {

“cell_type”: “code”, “execution_count”: 15, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“ParallelReaction (by mol):n”, “index stoichiometry reactant X[%]n”, “[0] Water + Glucan -> Glucose Glucan 9.90n”, “[1] Water + Glucan -> GlucoseOligomer Glucan 0.30n”, “[2] Glucan -> 2 Water + HMF Glucan 0.30n”, “[3] Sucrose -> 2 Water + HMF + Glucose Sucrose 0.30n”, “[4] Water + Xylan -> Xylose Xylan 90.00n”, “[5] Water + Xylan -> XyloseOligomer Xylan 0.24n”, “[6] Xylan -> 2 Water + Furfural Xylan 0.50n”, “[7] Acetate -> AceticAcid Acetate 100.00n”, “[8] Lignin -> SolubleLignin Lignin 0.50n”

]

}

], “source”: [

“from biorefineries import cornstover as csn”, “n”, “# Set chemicals as defined in [1-4]n”, “tmo.settings.set_thermo(cs.chemicals)n”, “n”, “# Create reactionsn”, “pretreatment_parallel_rxn = tmo.ParallelReaction([n”, ” # Reaction definition Reactant Conversionn”, ” tmo.Reaction(‘Glucan + H2O -> Glucose’, ‘Glucan’, 0.0990),n”, ” tmo.Reaction(‘Glucan + H2O -> GlucoseOligomer’, ‘Glucan’, 0.0030),n”, ” tmo.Reaction(‘Glucan -> HMF + 2 H2O’, ‘Glucan’, 0.0030),n”, ” tmo.Reaction(‘Sucrose -> HMF + Glucose + 2H2O’, ‘Sucrose’, 0.0030),n”, ” tmo.Reaction(‘Xylan + H2O -> Xylose’, ‘Xylan’, 0.9000),n”, ” tmo.Reaction(‘Xylan + H2O -> XyloseOligomer’, ‘Xylan’, 0.0024),n”, ” tmo.Reaction(‘Xylan -> Furfural + 2 H2O’, ‘Xylan’, 0.0050),n”, ” tmo.Reaction(‘Acetate -> AceticAcid’, ‘Acetate’, 1.0000),n”, ” tmo.Reaction(‘Lignin -> SolubleLignin’, ‘Lignin’, 0.0050)])n”, “n”, “pretreatment_parallel_rxn.show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Model the reaction:”

]

}, {

“cell_type”: “code”, “execution_count”: 16, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“BEFORE REACTIONn”, “Stream: s5n”, ” phase: ‘l’, T: 298.15 K, P: 101325 Pan”, ” flow (kmol/hr): Water 2.07e+05n”, ” Ethanol 18n”, ” Furfural 172n”, ” H2SO4 1.84e+03n”, ” Sucrose 1.87n”, ” Extract 67.8n”, ” Acetate 25.1n”, ” Ash 4.11e+03n”, ” Lignin 1.31e+04n”, ” Protein 108n”, ” Glucan 180n”, ” Xylan 123n”, ” Arabinan 9.02n”, ” Mannan 3.08n”, “AFTER REACTIONn”, “Stream: s5n”, ” phase: ‘l’, T: 298.15 K, P: 101325 Pan”, ” flow (kmol/hr): Water 2.07e+05n”, ” Ethanol 18n”, ” AceticAcid 25.1n”, ” Furfural 173n”, ” H2SO4 1.84e+03n”, ” HMF 0.546n”, ” Glucose 17.8n”, ” Xylose 111n”, ” Sucrose 1.86n”, ” Extract 67.8n”, ” Ash 4.11e+03n”, ” Lignin 1.3e+04n”, ” SolubleLignin 65.5n”, ” GlucoseOligomer 0.54n”, ” XyloseOligomer 0.295n”, ” Protein 108n”, ” Glucan 161n”, ” Xylan 11.4n”, ” Arabinan 9.02n”, ” Mannan 3.08n”

]

}

], “source”: [

“feed = tmo.Stream(H2O=2.07e+05,n”, ” Ethanol=18,n”, ” H2SO4=1.84e+03,n”, ” Sucrose=1.87,n”, ” Extract=67.8,n”, ” Acetate=25.1,n”, ” Ash=4.11e+03,n”, ” Lignin=1.31e+04,n”, ” Protein=108,n”, ” Glucan=180,n”, ” Xylan=123,n”, ” Arabinan=9.02,n”, ” Mannan=3.08,n”, ” Furfural=172)n”, “print(‘BEFORE REACTION’)n”, “feed.show(N=100)n”, “n”, “# React feed molar flow raten”, “pretreatment_parallel_rxn(feed)n”, “n”, “print(‘AFTER REACTION’)n”, “feed.show(N=100)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Reactions in series”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“SeriesReaction objects work the same way, but in series:”

]

}, {

“cell_type”: “code”, “execution_count”: 17, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“SeriesReaction (by mol):n”, “index stoichiometry reactant X[%]n”, “[0] Water + Glucan -> Glucose Glucan 9.90n”, “[1] Water + Glucan -> GlucoseOligomer Glucan 0.30n”, “[2] Glucan -> 2 Water + HMF Glucan 0.30n”, “[3] Sucrose -> 2 Water + HMF + Glucose Sucrose 0.30n”, “[4] Water + Xylan -> Xylose Xylan 90.00n”, “[5] Water + Xylan -> XyloseOligomer Xylan 0.24n”, “[6] Xylan -> 2 Water + Furfural Xylan 0.50n”, “[7] Acetate -> AceticAcid Acetate 100.00n”, “[8] Lignin -> SolubleLignin Lignin 0.50n”

]

}

], “source”: [

“pretreatment_series_rxn = tmo.SeriesReaction(pretreatment_parallel_rxn)n”, “pretreatment_series_rxn.show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Net conversion in parallel:”

]

}, {

“cell_type”: “code”, “execution_count”: 18, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“ChemicalIndexer:n”, ” Sucrose 0.003n”, ” Acetate 1n”, ” Lignin 0.005n”, ” Glucan 0.105n”, ” Xylan 0.9074n”

]

}

], “source”: [

“pretreatment_parallel_rxn.X_net”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Net conversion in series:”

]

}, {

“cell_type”: “code”, “execution_count”: 19, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“ChemicalIndexer:n”, ” Sucrose 0.003n”, ” Acetate 1n”, ” Lignin 0.005n”, ” Glucan 0.1044n”, ” Xylan 0.9007n”

]

}

], “source”: [

“# Notice how the conversion isn”, “# slightly lower for some reactantsn”, “pretreatment_series_rxn.X_net”

]

}, {

“cell_type”: “code”, “execution_count”: 20, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“BEFORE REACTIONn”, “Stream: s6n”, ” phase: ‘l’, T: 298.15 K, P: 101325 Pan”, ” flow (kmol/hr): Water 2.07e+05n”, ” Ethanol 18n”, ” Furfural 172n”, ” H2SO4 1.84e+03n”, ” Sucrose 1.87n”, ” Extract 67.8n”, ” Acetate 25.1n”, ” Ash 4.11e+03n”, ” Lignin 1.31e+04n”, ” Protein 108n”, ” Glucan 180n”, ” Xylan 123n”, ” Arabinan 9.02n”, ” Mannan 3.08n”, “AFTER REACTIONn”, “Stream: s6n”, ” phase: ‘l’, T: 298.15 K, P: 101325 Pan”, ” flow (kmol/hr): Water 2.07e+05n”, ” Ethanol 18n”, ” AceticAcid 25.1n”, ” Furfural 172n”, ” H2SO4 1.84e+03n”, ” HMF 0.491n”, ” Glucose 17.8n”, ” Xylose 111n”, ” Sucrose 1.86n”, ” Extract 67.8n”, ” Ash 4.11e+03n”, ” Lignin 1.3e+04n”, ” SolubleLignin 65.5n”, ” GlucoseOligomer 0.487n”, ” XyloseOligomer 0.0295n”, ” Protein 108n”, ” Glucan 161n”, ” Xylan 12.2n”, ” Arabinan 9.02n”, ” Mannan 3.08n”

]

}

], “source”: [

“feed = tmo.Stream(H2O=2.07e+05,n”, ” Ethanol=18,n”, ” H2SO4=1.84e+03,n”, ” Sucrose=1.87,n”, ” Extract=67.8,n”, ” Acetate=25.1,n”, ” Ash=4.11e+03,n”, ” Lignin=1.31e+04,n”, ” Protein=108,n”, ” Glucan=180,n”, ” Xylan=123,n”, ” Arabinan=9.02,n”, ” Mannan=3.08,n”, ” Furfural=172)n”, “print(‘BEFORE REACTION’)n”, “feed.show(N=100)n”, “n”, “# React feed molar flow raten”, “pretreatment_series_rxn(feed)n”, “n”, “print(‘AFTER REACTION’)n”, “feed.show(N=100)”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Indexing reactions”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Both SeriesReaction, and ParallelReaction objects are indexable:”

]

}, {

“cell_type”: “code”, “execution_count”: 21, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“ParallelReaction (by mol):n”, “index stoichiometry reactant X[%]n”, “[0] Water + Glucan -> Glucose Glucan 9.90n”, “[1] Water + Glucan -> GlucoseOligomer Glucan 0.30n”

]

}

], “source”: [

“# Index a slicen”, “pretreatment_parallel_rxn[0:2].show()”

]

}, {

“cell_type”: “code”, “execution_count”: 22, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“ReactionItem (by mol):n”, ” stoichiometry reactant X[%]n”, ” Water + Glucan -> Glucose Glucan 9.90n”

]

}

], “source”: [

“# Index an itemn”, “pretreatment_parallel_rxn[0].show()”

]

}, {

“cell_type”: “code”, “execution_count”: 23, “metadata”: {}, “outputs”: [], “source”: [

“# Change conversion through the itemn”, “pretreatment_parallel_rxn[0].X = 0.10”

]

}, {

“cell_type”: “code”, “execution_count”: 24, “metadata”: {}, “outputs”: [

{

“name”: “stdout”, “output_type”: “stream”, “text”: [

“ParallelReaction (by mol):n”, “index stoichiometry reactant X[%]n”, “[0] Water + Glucan -> Glucose Glucan 10.00n”, “[1] Water + Glucan -> GlucoseOligomer Glucan 0.30n”, “[2] Glucan -> 2 Water + HMF Glucan 0.30n”, “[3] Sucrose -> 2 Water + HMF + Glucose Sucrose 0.30n”, “[4] Water + Xylan -> Xylose Xylan 90.00n”, “[5] Water + Xylan -> XyloseOligomer Xylan 0.24n”, “[6] Xylan -> 2 Water + Furfural Xylan 0.50n”, “[7] Acetate -> AceticAcid Acetate 100.00n”, “[8] Lignin -> SolubleLignin Lignin 0.50n”

]

}

], “source”: [

“pretreatment_parallel_rxn.show()”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“Notice how changing conversion of a ReactionItem object changes the conversion in the ParallelReaction object.”

]

}, {

“cell_type”: “markdown”, “metadata”: {}, “source”: [

“### Referencesn”, “n”, “<a id=’References’></a>n”, “n”, “1. Humbird, D., Davis, R., Tao, L., Kinchin, C., Hsu, D., Aden, A., Dudgeon, D. (2011). Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol: Dilute-Acid Pretreatment and Enzymatic Hydrolysis of Corn Stover (No. NREL/TP-5100-47764, 1013269). https://doi.org/10.2172/1013269n”, “n”, “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-2n”, “n”, “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-6n”, “n”, “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.”

]

}

], “metadata”: {

“kernelspec”: {

“display_name”: “Python 3”, “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.7.6”

}

}, “nbformat”: 4, “nbformat_minor”: 2

}

Chemical

class thermosteam.Chemical(ID, cache=None, *, search_ID=None, eos=None, phase_ref=None, CAS=None, default=False, phase=None, search_db=True, V=None, Cn=None, mu=None, Cp=None, rho=None, sigma=None, kappa=None, epsilon=None, Psat=None, Hvap=None, **data)[source]

Creates a Chemical object which contains constant chemical properties, as well as thermodynamic and transport properties as a function of temperature and pressure.

Parameters
  • ID (str) –

    One of the following [-]:
    • Name, in IUPAC form or common form or a synonym registered in PubChem

    • InChI name, prefixed by ‘InChI=1S/’ or ‘InChI=1/’

    • InChI key, prefixed by ‘InChIKey=’

    • PubChem CID, prefixed by ‘PubChem=’

    • SMILES (prefix with ‘SMILES=’ to ensure smiles parsing)

    • CAS number

  • cache (optional) – Whether or not to use cached chemicals and cache new chemicals.

  • search_ID (str, optional) – ID to search through database. Pass this key-word argument when you’d like to give a custom name to the chemical, but cannot find the chemical with that name.

  • eos (GCEOS subclass, optional) – Equation of state class for solving thermodynamic properties. Defaults to Peng-Robinson.

  • phase_ref ({'s', 'l', 'g'}, optional) – Reference phase of chemical.

  • CAS (str, optional) – CAS number of chemical.

  • phase ({'s', 'l' or 'g'}, optional) – Phase to set state of chemical.

  • search_db=True (bool, optional) – Whether to search the data base for chemical.

  • Cn (float or function(T), optional) – Molar heat capacity model [J/mol] as a function of temperature [K].

  • sigma (float or function(T), optional) – Surface tension model [N/m] as a function of temperature [K].

  • epsilon (float or function(T), optional) – Relative permitivity model [-] as a function of temperature [K].

  • Psat (float or function(T), optional) – Vapor pressure model [N/m] as a function of temperature [K].

  • Hvap (float or function(T), optional) – Heat of vaporization model [J/mol] as a function of temperature [K].

  • V (float or function(T, P), optional) – Molar volume model [mol/m3] as a function of temperature [K] and pressure [Pa].

  • mu (float or function(T, P), optional) – Dynamic viscosity model [Pa*s] as a function of temperature [K] and pressure [Pa].

  • kappa (float or function(T, P), optional) – Thermal conductivity model [W/m/K] as a function of temperature [K] and pressure [Pa].

  • Cp (float, optional) – Constant heat capacity model [J/g].

  • rho (float, optional) – Constant density model [kg/m3].

  • default=False (bool, optional) – Whether to default any missing chemical properties such as molar volume, heat capacity, surface tension, thermal conductivity, and molecular weight to that of water (on a weight basis).

  • **data (float or str) – User data (e.g. Tb, formula, etc.).

Examples

Chemical objects contain pure component properties:

>>> import thermosteam as tmo
>>> # Initialize with an identifier
>>> # (e.g. by name, CAS, InChI...)
>>> Water = tmo.Chemical('Water')
>>> Water.show()
Chemical: Water (phase_ref='l')
[Names]  CAS: 7732-18-5
         InChI: H2O/h1H2
         InChI_key: XLYOFNOQVPJJNP-U...
         common_name: water
         iupac_name: ('oxidane',)
         pubchemid: 962
         smiles: O
         formula: H2O
[Groups] Dortmund: <1H2O>
         UNIFAC: <1H2O>
         PSRK: <1H2O>
[Data]   MW: 18.015 g/mol
         Tm: 273.15 K
         Tb: 373.12 K
         Tt: 273.15 K
         Tc: 647.14 K
         Pt: 610.88 Pa
         Pc: 2.2048e+07 Pa
         Vc: 5.6e-05 m^3/mol
         Hf: -2.8582e+05 J/mol
         S0: 70 J/K/mol
         LHV: 44011 J/mol
         HHV: 0 J/mol
         Hfus: 6010 J/mol
         Sfus: None
         omega: 0.344
         dipole: 1.85 Debye
         similarity_variable: 0.16653
         iscyclic_aliphatic: 0
         combustion: {'H2O': 1.0}

All fields shown are accessible:

>>> Water.CAS
'7732-18-5'

Functional group identifiers (e.g. Dortmund, UNIFAC, PSRK) allow for the estimation of activity coefficients through group contribution methods. In other words, these attributes define the functional groups for thermodynamic equilibrium calculations:

>>> Water.Dortmund
<DortmundGroupCounts: 1H2O>

Temperature (in Kelvin) and pressure (in Pascal) dependent properties can be computed:

>>> # Vapor pressure (Pa)
>>> Water.Psat(T=373.15)
101284.55...
>>> # Surface tension (N/m)
>>> Water.sigma(T=298.15)
0.07197220523...
>>> # Molar volume (m^3/mol)
>>> Water.V(phase='l', T=298.15, P=101325)
1.80692...e-05

Note that the reference state of all chemicals is 25 degC and 1 atm:

>>> (Water.T_ref, Water.P_ref)
(298.15, 101325.0)
>>> # Enthalpy at reference conditions (J/mol; without excess energies)
>>> Water.H(T=298.15, phase='l')
0.0

Constant pure component properties are also available:

>>> # Molecular weight (g/mol)
>>> Water.MW
18.01528
>>> # Boiling point (K)
>>> Water.Tb
373.124

Temperature dependent properties are managed by model handles:

>>> Water.Psat.show()
TDependentModelHandle(T, P=None) -> Psat [Pa]
[0] Wagner original
[1] Antoine
[2] EQ101
[3] Wagner
[4] boiling critical relation
[5] Lee Kesler
[6] Ambrose Walton
[7] Sanjari
[8] Edalat

Phase dependent properties have attributes with model handles for each phase:

>>> Water.V
<PhaseTPHandle(phase, T, P) -> V [m^3/mol]>
>>> (Water.V.l, Water.V.g)
(<TPDependentModelHandle(T, P) -> V.l [m^3/mol]>, <TPDependentModelHandle(T, P) -> V.g [m^3/mol]>)

A model handle contains a series of models applicable to a certain domain:

>>> Water.Psat[0].show()
TDependentModel(T, P=None) -> Psat [Pa]
 name: Wagner original
 Tmin: 275 K
 Tmax: 647.35 K

When called, the model handle searches through each model until it finds one with an applicable domain. If none are applicable, a value error is raised:

>>> Water.Psat(373.15)
101284.55179999319
>>> # Water.Psat(1000.0) ->
>>> # ValueError: Water has no valid saturated vapor pressure model at T=1000.00 K

Model handles as well as the models themselves have tabulation and plotting methods to help visualize how properties depend on temperature and pressure.

>>> # import matplotlib.pyplot as plt
>>> # Water.Psat.plot_vs_T([Water.Tm, Water.Tb], 'degC', 'atm', label="Water")
>>> # plt.show()
_images/Water_Psat_vs_T.png
>>> # Plot all models
>>> # Water.Psat.plot_models_vs_T([Water.Tm, Water.Tb], 'degC', 'atm')
>>> # plt.show()
_images/Water_all_models_Psat_vs_T.png

Each model may contain either a function or a functor (a function with stored data) to compute the property:

>>> functor = Water.Psat[0].evaluate
>>> functor.show()
Functor: Wagner_original(T, P=None) -> Psat [Pa]
 Tc: 647.35 K
 Pc: 2.2122e+07 Pa
 a: -7.7645
 b: 1.4584
 c: -2.7758
 d: -1.233

Note

All chemical property functors are available in the thermosteam.properties subpackage. You can also use help(<functor>) for further information on the math and equations used in the functor.

A new model can be added easily to a model handle through the add_model method, for example:

>>> # Set top_priority=True to place model in postion [0]
>>> @Water.Psat.add_model(Tmin=273.20, Tmax=473.20, top_priority=True)
... def User_antoine_model(T):
...     return 10.0**(10.116 -  1687.537 / (T - 42.98))
>>> Water.Psat.show()
TDependentModelHandle(T, P=None) -> Psat [Pa]
[0] User antoine model
[1] Wagner original
[2] Antoine
[3] EQ101
[4] Wagner
[5] boiling critical relation
[6] Lee Kesler
[7] Ambrose Walton
[8] Sanjari
[9] Edalat

The add_model method is a high level interface that even lets you create a constant model:

>>> value = Water.V.l.add_model(1.687e-05, name='User constant')
... # Model is appended at the end by default
>>> added_model = Water.V.l[-1]
>>> added_model.show()
ConstantThermoModel(T=None, P=None) -> V.l [m^3/mol]
 name: User constant
 value: 1.687e-05
 Tmin: 0 K
 Tmax: inf K
 Pmin: 0 Pa
 Pmax: inf Pa

Note

Because no bounds were given, the model assumes it is valid across all temperatures and pressures.

Manage the model order with the set_model_priority and move_up_model_priority methods:

>>> # Note: In this case, we pass the model name, but its
>>> # also possible to pass the current index, or the model itself.
>>> Water.Psat.move_up_model_priority('Wagner original')
>>> Water.Psat.show()
TDependentModelHandle(T, P=None) -> Psat [Pa]
[0] Wagner original
[1] Antoine
[2] EQ101
[3] Wagner
[4] boiling critical relation
[5] Lee Kesler
[6] Ambrose Walton
[7] Sanjari
[8] Edalat
[9] User antoine model
>>> Water.Psat.set_model_priority('Antoine')
>>> Water.Psat.show()
TDependentModelHandle(T, P=None) -> Psat [Pa]
[0] Antoine
[1] Wagner original
[2] EQ101
[3] Wagner
[4] boiling critical relation
[5] Lee Kesler
[6] Ambrose Walton
[7] Sanjari
[8] Edalat
[9] User antoine model

The default priority is 0 (or top priority), but you can choose any priority:

>>> Water.Psat.set_model_priority('Antoine', 1)
>>> Water.Psat.show()
TDependentModelHandle(T, P=None) -> Psat [Pa]
[0] Wagner original
[1] Antoine
[2] EQ101
[3] Wagner
[4] boiling critical relation
[5] Lee Kesler
[6] Ambrose Walton
[7] Sanjari
[8] Edalat
[9] User antoine model

Note

All models are stored as a deque in the models attribute (e.g. Water.Psat.models).

mu(phase, T, P)

Dynamic viscosity [Pa*s].

kappa(phase, T, P)

Thermal conductivity [W/m/K].

V(phase, T, P)

Molar volume [m^3/mol].

Cn(phase, T)

Molar heat capacity [J/mol/K].

Psat(T)

Vapor pressure [Pa].

Hvap(T)

Heat of vaporization [J/mol]

sigma(T)

Surface tension [N/m].

epsilon(T)

Relative permitivity [-]

S(phase, T, P)

Entropy [J/mol].

H(phase, T)

Enthalpy [J/mol].

S_excess(T, P)

Excess entropy [J/mol].

H_excess(T, P)

Excess enthalpy [J/mol].

T_ref = 298.15

[float] Reference temperature in Kelvin

P_ref = 101325.0

[float] Reference pressure in Pascal

H_ref = 0.0

[float] Reference enthalpy in J/mol

chemical_cache = {}

dict[str, Chemical] Cached chemicals

cache = False

[bool] Wheather or not to search cache by default

classmethod new(ID, CAS, eos=<class 'thermosteam.eos.PR'>, phase_ref=None, phase=None, **data)[source]

Create a new chemical from data without searching through the data base, and load all possible models from given data.

classmethod blank(ID, CAS=None, phase_ref=None, phase=None, formula=None, **data)[source]

Return a new Chemical object without any thermodynamic models or data (unless provided).

Parameters
  • ID (str) – Chemical identifier.

  • CAS (str, optional) – CAS number. If none provided, it defaults to the ID.

  • phase_ref (str, optional) – Phase at the reference state (T=298.15, P=101325).

  • phase (str, optional) – Phase to set state as a single phase chemical.

  • **data – Any data to fill chemical with.

Examples

>>> from thermosteam import Chemical
>>> Substance = Chemical.blank('Substance')
>>> Substance.show()
Chemical: Substance (phase_ref=None)
[Names]  CAS: Substance
         InChI: None
         InChI_key: None
         common_name: None
         iupac_name: None
         pubchemid: None
         smiles: None
         formula: None
[Groups] Dortmund: <Empty>
         UNIFAC: <Empty>
         PSRK: <Empty>
[Data]   MW: None
         Tm: None
         Tb: None
         Tt: None
         Tc: None
         Pt: None
         Pc: None
         Vc: None
         Hf: None
         S0: None
         LHV: None
         HHV: None
         Hfus: None
         Sfus: None
         omega: None
         dipole: None
         similarity_variable: None
         iscyclic_aliphatic: None
         combustion: None
copy(ID, CAS=None, **data)[source]

Return a copy of the chemical with a new ID.

Examples

>>> import thermosteam as tmo
>>> Glucose = tmo.Chemical('Glucose')
>>> Mannose = Glucose.copy('Mannose')
>>> Mannose.show()
Chemical: Mannose (phase_ref='l')
[Names]  CAS: Mannose
         InChI: C6H12O6/c7-1-3(9)5(1...
         InChI_key: GZCGUPFRVQAUEE-S...
         common_name: D-Glucose
         iupac_name: ('(2R,3S,4R,5R)...
         pubchemid: 1.0753e+05
         smiles: O=C[C@H](O)[C@@H](O...
         formula: C6H12O6
[Groups] Dortmund: {2: 1, 3: 4, 14: ...
         UNIFAC: {2: 1, 3: 4, 14: 5,...
         PSRK: {2: 1, 3: 4, 14: 5, 2...
[Data]   MW: 180.16 g/mol
         Tm: None
         Tb: 616.29 K
         Tt: None
         Tc: 755 K
         Pt: None
         Pc: 4.82e+06 Pa
         Vc: 0.000414 m^3/mol
         Hf: -1.2711e+06 J/mol
         S0: 0 J/K/mol
         LHV: -2.5406e+06 J/mol
         HHV: -2.8047e+06 J/mol
         Hfus: 0 J/mol
         Sfus: None
         omega: 2.387
         dipole: None
         similarity_variable: 0.13322
         iscyclic_aliphatic: 0
         combustion: {'CO2': 6, 'O2'...
property phase_ref

{‘s’, ‘l’, ‘g’} Phase at 298 K and 101325 Pa.

property ID

[str] Identification of chemical.

property CAS

[str] CAS number of chemical.

property InChI

[str] IUPAC International Chemical Identifier.

property InChI_key

[str] IUPAC International Chemical Identifier shorthand.

property common_name

[str] Common name identifier.

property iupac_name

[str] Standard name as defined by IUPAC.

property pubchemid

[str] Chemical identifier as defined by PubMed.

property smiles

[str] Chemical SMILES formula.

property formula

[str] Chemical atomic formula.

property Dortmund

[DortmundGroupCounts] Dictionary-like object with functional group numerical identifiers as keys and the number of groups as values.

property UNIFAC

[UNIFACGroupCounts] Dictionary-like object with functional group numerical identifiers as keys and the number of groups as values.

property PSRK

[PSRKGroupCounts] Dictionary-like object with functional group numerical identifiers as keys and the number of groups as values.

property eos

[object] Instance for solving equations of state.

property eos_1atm

[object] Instance for solving equations of state at 1 atm.

property mu

Dynamic viscosity [Pa*s].

property kappa

Thermal conductivity [W/m/K].

property V

Molar volume [m^3/mol].

property Cn

Molar heat capacity [J/mol/K].

property Psat

Vapor pressure [Pa].

property Hvap

Heat of vaporization [J/mol].

property sigma

Surface tension [N/m].

property epsilon

Relative permitivity [-].

property S

Entropy [J/mol].

property H

Enthalpy [J/mol].

property S_excess

Excess entropy [J/mol].

property H_excess

Excess enthalpy [J/mol].

property MW

Molecular weight [g/mol].

property Tm

Normal melting temperature [K].

property Tb

Normal boiling temperature [K].

property Pt

Triple point pressure [Pa].

property Tt

Triple point temperature [K].

property Tc

Critical point temperature [K].

property Pc

Critical point pressure [Pa].

property Vc

Critical point molar volume [m^3/mol].

property Hfus

Heat of fusion [J/mol].

property Sfus

Entropy of fusion [J/mol].

property omega

Accentric factor [-].

property dipole

Dipole moment [Debye].

property similarity_variable

Similarity variable [-].

property iscyclic_aliphatic

Whether the chemical is cyclic-aliphatic.

property Hf

Heat of formation at reference phase and temperature [J/mol].

property LHV

Lower heating value [J/mol].

property HHV

Higher heating value [J/mol].

property combustion

dict[str, int] Combustion reaction.

property Stiel_Polar

[float] Stiel Polar factor for computing surface tension.

property Zc

[float] Compressibility factor.

property has_hydroxyl

[bool] Whether or not chemical contains a hydroxyl functional group, as determined by the Dortmund/UNIFAC/PSRK functional groups.

property atoms

int] Atom-count pairs based on formula.

Type

dict[str

get_combustion_reaction(chemicals=None)[source]

Return a Reaction object defining the combustion of this chemical. If no combustion stoichiometry is available, return None.

get_phase(T=298.15, P=101325.0)[source]

Return phase of chemical at given thermal condition.

Examples

>>> from thermosteam import Chemical
>>> Water = Chemical('Water', cache=True)
>>> Water.get_phase(T=400, P=101325)
'g'
Tsat(P, Tguess=None, Tmin=None, Tmax=None)[source]

Return the saturated temperature (in Kelvin) given the pressure (in Pascal).

reset(CAS, eos=<class 'thermosteam.eos.PR'>, phase_ref=None, smiles=None, InChI=None, InChI_key=None, pubchemid=None, iupac_name=None, common_name=None, formula=None, MW=None, Tm=None, Tb=None, Tc=None, Pc=None, Vc=None, omega=None, Tt=None, Pt=None, Hf=None, S0=None, LHV=None, combustion=None, HHV=None, Hfus=None, dipole=None, similarity_variable=None, iscyclic_aliphatic=None, *, metadata=None, phase=None)[source]

Reset all chemical properties.

Parameters
  • CAS (str) – CAS number of chemical to load.

  • eos (optional) – Equation of state. The default is Peng Robinson.

  • phase_ref (str, optional) – Phase reference. Defaults to the phase at 298.15 K and 101325 Pa.

reset_combustion_data(method='Stoichiometry')[source]

Reset combustion data (LHV, HHV, and combution attributes) based on the molecular formula and the heat of formation.

reset_free_energies()[source]

Reset the H, S, H_excess, and S_excess functors.

get_key_property_names()[source]

Return the attribute names of key properties required to model a process.

default(properties=None)[source]

Default all properties with the chemical properties of water. If no properties given, all essential chemical properties that are missing are defaulted. properties which are still missing are returned as set.

Parameters

properties (Iterable[str], optional) – Names of chemical properties to default.

Returns

missing_properties – Names of chemical properties that are still missing.

Return type

set[str]

Examples

>>> from thermosteam import Chemical
>>> Substance = Chemical.blank('Substance')
>>> missing_properties = Substance.default()
>>> sorted(missing_properties)
['Dortmund', 'Hfus', 'Hvap', 'PSRK', 'Pc', 'Psat', 'Pt', 'Sfus', 'Tb', 'Tc', 'Tm', 'Tt', 'UNIFAC', 'V', 'Vc', 'dipole', 'iscyclic_aliphatic', 'omega', 'similarity_variable']

Note that missing properties does not include essential properties volume, heat capacity, and conductivity.

get_missing_properties(properties=None)[source]

Return a list all missing thermodynamic properties.

Examples

>>> from thermosteam import Chemical
>>> Substance = Chemical.blank('Substance', phase_ref='l')
>>> sorted(Substance.get_missing_properties())
['Cn', 'Dortmund', 'H', 'HHV', 'H_excess', 'Hf', 'Hfus', 'Hvap', 'LHV', 'MW', 'PSRK', 'Pc', 'Psat', 'Pt', 'S', 'S_excess', 'Sfus', 'Tb', 'Tc', 'Tm', 'Tt', 'UNIFAC', 'V', 'Vc', 'combustion', 'dipole', 'epsilon', 'iscyclic_aliphatic', 'kappa', 'mu', 'omega', 'sigma', 'similarity_variable']
copy_models_from(other, names=None)[source]

Copy models from other.

property locked_state

[str] Constant phase of chemical.

at_state(phase, copy=False)[source]

Set the state of chemical.

Examples

>>> from thermosteam import Chemical
>>> N2 = Chemical('N2')
>>> N2.at_state(phase='g')
>>> N2.H(298.15) # No longer a function of phase
0.0
show()[source]

Print all specifications

Chemicals

class thermosteam.Chemicals(chemicals, cache=False)[source]

Create a Chemicals object that contains Chemical objects as attributes.

Parameters
  • chemicals (Iterable[str or Chemical]) –

    Strings should be one of the following [-]:
    • Name, in IUPAC form or common form or a synonym registered in PubChem

    • InChI name, prefixed by ‘InChI=1S/’ or ‘InChI=1/’

    • InChI key, prefixed by ‘InChIKey=’

    • PubChem CID, prefixed by ‘PubChem=’

    • SMILES (prefix with ‘SMILES=’ to ensure smiles parsing)

    • CAS number

  • cache (bool, optional) – Wheather or not to use cached chemicals.

Examples

Create a Chemicals object from chemical identifiers:

>>> from thermosteam import Chemicals
>>> chemicals = Chemicals(['Water', 'Ethanol'], cache=True)
>>> chemicals
Chemicals([Water, Ethanol])

All chemicals are stored as attributes:

>>> chemicals.Water, chemicals.Ethanol
(Chemical('Water'), Chemical('Ethanol'))

Chemicals can also be accessed as items:

>>> chemicals = Chemicals(['Water', 'Ethanol', 'Propane'], cache=True)
>>> chemicals['Ethanol']
Chemical('Ethanol')
>>> chemicals['Propane', 'Water']
[Chemical('Propane'), Chemical('Water')]

A Chemicals object can be extended with more chemicals:

>>> from thermosteam import Chemical
>>> Methanol = Chemical('Methanol')
>>> chemicals.append(Methanol)
>>> chemicals
Chemicals([Water, Ethanol, Propane, Methanol])
>>> new_chemicals = Chemicals(['Hexane', 'Octanol'], cache=True)
>>> chemicals.extend(new_chemicals)
>>> chemicals
Chemicals([Water, Ethanol, Propane, Methanol, Hexane, Octanol])

Chemical objects cannot be repeated:

>>> chemicals.append(chemicals.Water)
Traceback (most recent call last):
ValueError: Water already defined in chemicals
>>> chemicals.extend(chemicals['Ethanol', 'Octanol'])
Traceback (most recent call last):
ValueError: Ethanol already defined in chemicals

A Chemicals object can only contain Chemical objects:

>>> chemicals.append(10)
Traceback (most recent call last):
TypeError: only 'Chemical' objects can be appended, not 'int'

You can check whether a Chemicals object contains a given chemical:

>>> 'Water' in chemicals
True
>>> chemicals.Water in chemicals
True
>>> 'Butane' in chemicals
False

An attempt to access a non-existent chemical raises an UndefinedChemical error:

>>> chemicals['Butane']
Traceback (most recent call last):
UndefinedChemical: 'Butane'
copy()[source]

Return a copy.

append(chemical)[source]

Append a Chemical.

extend(chemicals)[source]

Extend with more Chemical objects.

subgroup(IDs)[source]

Create a new subgroup of chemicals.

Parameters

IDs (Iterable[str]) – Chemical identifiers.

Examples

>>> chemicals = Chemicals(['Water', 'Ethanol', 'Propane'])
>>> chemicals.subgroup(['Propane', 'Water'])
Chemicals([Propane, Water])
compile(skip_checks=False)[source]

Cast as a CompiledChemicals object.

Parameters

skip_checks (bool, optional) – Whether to skip checks for missing or invalid properties.

Warning

If checks are skipped, certain features in thermosteam (e.g. phase equilibrium) cannot be guaranteed to function properly.

Examples

Compile ethanol and water chemicals:

>>> import thermosteam as tmo
>>> chemicals = tmo.Chemicals(['Water', 'Ethanol'])
>>> chemicals.compile()
>>> chemicals
CompiledChemicals([Water, Ethanol])

Attempt to compile chemicals with missing properties:

>>> Substance = tmo.Chemical('Substance', search_db=False)
>>> chemicals = tmo.Chemicals([Substance])
>>> chemicals.compile()
Traceback (most recent call last):
RuntimeError: Substance is missing key thermodynamic properties
(V, S, H, Cn, Psat, Tb and Hvap); use the `<Chemical>.get_missing_properties()`
to check all missing properties

Compile chemicals with missing properties (skipping checks) and note how certain features do not work:

>>> chemicals.compile(skip_checks=True)
>>> tmo.settings.set_thermo(chemicals)
>>> s = tmo.Stream('s', Substance=10)
>>> s.rho
Traceback (most recent call last):
DomainError: Substance (CAS: Substance) has no valid liquid molar
volume model at T=298.15 K and P=101325 Pa
class thermosteam.CompiledChemicals(chemicals, cache=None)[source]

Create a CompiledChemicals object that contains Chemical objects as attributes.

Parameters
  • chemicals (Iterable[str or Chemical]) –

    Strings should be one of the following [-]:
    • Name, in IUPAC form or common form or a synonym registered in PubChem

    • InChI name, prefixed by ‘InChI=1S/’ or ‘InChI=1/’

    • InChI key, prefixed by ‘InChIKey=’

    • PubChem CID, prefixed by ‘PubChem=’

    • SMILES (prefix with ‘SMILES=’ to ensure smiles parsing)

    • CAS number

  • cache (optional) – Whether or not to use cached chemicals.

tuple

All compiled chemicals.

Type

tuple[Chemical]

size

Number of chemicals.

Type

int

IDs

IDs of all chemicals.

Type

tuple[str]

CASs

CASs of all chemicals

Type

tuple[str]

MW

MWs of all chemicals.

Type

1d ndarray

Hf

Heats of formation of all chemicals.

Type

1d ndarray

Hc

Heats of combustion of all chemicals.

Type

1d ndarray

vle_chemicals

Chemicals that may have vapor and liquid phases.

Type

tuple[Chemical]

lle_chemicals

Chemicals that may have two liquid phases.

Type

tuple[Chemical]

heavy_chemicals

Chemicals that are only present in liquid or solid phases.

Type

tuple[Chemical]

light_chemicals

IDs of chemicals that are only present in gas phases.

Type

tuple[Chemical]

Examples

Create a CompiledChemicals object from chemical identifiers

>>> from thermosteam import CompiledChemicals, Chemical
>>> chemicals = CompiledChemicals(['Water', 'Ethanol'], cache=True)
>>> chemicals
CompiledChemicals([Water, Ethanol])

All chemicals are stored as attributes:

>>> chemicals.Water, chemicals.Ethanol
(Chemical('Water'), Chemical('Ethanol'))

Note that because they are compiled, the append and extend methods do not work:

>>> Propane = Chemical('Propane', cache=True)
>>> chemicals.append(Propane)
Traceback (most recent call last):
TypeError: 'CompiledChemicals' object is read-only

You can check whether a Chemicals object contains a given chemical:

>>> 'Water' in chemicals
True
>>> chemicals.Water in chemicals
True
>>> 'Butane' in chemicals
False
compile()[source]

Do nothing, CompiledChemicals objects are already compiled.

refresh_constants()[source]

Refresh constant arrays according to their chemical values, including the molecular weight, heats of formation, and heats of combustion.

get_combustion_reactions()[source]

Return a ParallelReactions object with all defined combustion reactions.

Examples

>>> chemicals = CompiledChemicals(['H2O', 'Methanol', 'Ethanol', 'CO2', 'O2'], cache=True)
>>> rxns = chemicals.get_combustion_reactions()
>>> rxns.show()
ParallelReaction (by mol):
index  stoichiometry                     reactant    X[%]
[0]    Methanol + 1.5 O2 -> 2 H2O + CO2  Methanol  100.00
[1]    Ethanol + 3 O2 -> 3 H2O + 2 CO2   Ethanol   100.00
property formula_array

An array describing the formulas of all chemicals. Each column is a chemical and each row an element. Rows are ordered by atomic number.

Examples

>>> chemicals = CompiledChemicals(['Water', 'Ethanol', 'Propane'], cache=True)
>>> chemicals.formula_array
array([[2., 6., 8.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 2., 3.],
       [0., 0., 0.],
       [1., 1., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.]])
subgroup(IDs)[source]

Create a new subgroup of chemicals.

Parameters

IDs (Iterable[str]) – Chemical identifiers.

Examples

>>> chemicals = CompiledChemicals(['Water', 'Ethanol', 'Propane'], cache=True)
>>> chemicals.subgroup(['Propane', 'Water'])
CompiledChemicals([Propane, Water])
get_synonyms(ID)[source]

Get all synonyms of a chemical.

Parameters

ID (str) – Chemical identifier.

Examples

Get all synonyms of water:

>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water'], cache=True)
>>> chemicals.get_synonyms('Water')
['7732-18-5', 'Water']
set_synonym(ID, synonym)[source]

Set a new synonym for a chemical.

Parameters
  • ID (str) – Chemical identifier.

  • synonym (str) – New identifier for chemical.

Examples

Set new synonym for water:

>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Ethanol'], cache=True)
>>> chemicals.set_synonym('Water', 'H2O')
>>> chemicals.H2O is chemicals.Water
True

Note that you cannot use one synonym for two chemicals:

>>> chemicals.set_synonym('Ethanol', 'H2O')
Traceback (most recent call last):
ValueError: synonym 'H2O' already in use by Chemical('Water')
zeros()[source]

Return an array of zeros with entries that correspond to the orded chemical IDs.

Examples

>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Ethanol'], cache=True)
>>> chemicals.zeros()
array([0., 0.])
ones()[source]

Return an array of ones with entries that correspond to the orded chemical IDs.

Examples

>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Ethanol'], cache=True)
>>> chemicals.ones()
array([1., 1.])
kwarray(ID_data)[source]

Return an array with entries that correspond to the orded chemical IDs.

Parameters

ID_data (dict) – ID-data pairs.

Examples

>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Ethanol'], cache=True)
>>> chemicals.kwarray(dict(Water=2))
array([2., 0.])
array(IDs, data)[source]

Return an array with entries that correspond to the ordered chemical IDs.

Parameters
  • IDs (iterable) – Compound IDs.

  • data (array_like) – Data corresponding to IDs.

Examples

>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Ethanol'], cache=True)
>>> chemicals.array(['Water'], [2])
array([2., 0.])
iarray(IDs, data)[source]

Return a chemical indexer.

Parameters
  • IDs (iterable) – Chemical IDs.

  • data (array_like) – Data corresponding to IDs.

Examples

Create a chemical indexer from chemical IDs and data:

>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Methanol', 'Ethanol'], cache=True)
>>> chemical_indexer = chemicals.iarray(['Water', 'Ethanol'], [2., 1.])
>>> chemical_indexer.show()
ChemicalIndexer:
 Water    2
 Ethanol  1

Note that indexers allow for computationally efficient indexing using identifiers:

>>> chemical_indexer['Ethanol', 'Water']
array([1., 2.])
>>> chemical_indexer['Ethanol']
1.0
ikwarray(ID_data)[source]

Return a chemical indexer.

Parameters

ID_data (Dict[str: float]) – Chemical ID-value pairs.

Examples

Create a chemical indexer from chemical IDs and data:

>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Methanol', 'Ethanol'], cache=True)
>>> chemical_indexer = chemicals.ikwarray(dict(Water=2., Ethanol=1.))
>>> chemical_indexer.show()
ChemicalIndexer:
 Water    2
 Ethanol  1

Note that indexers allow for computationally efficient indexing using identifiers:

>>> chemical_indexer['Ethanol', 'Water']
array([1., 2.])
>>> chemical_indexer['Ethanol']
1.0
isplit(split, order=None)[source]

Create a chemical indexer that represents chemical splits.

Parameters
  • split (Should be one of the following) –

    • [float] Split fraction

    • [array_like] Componentwise split

    • [dict] ID-split pairs

  • order=None (Iterable[str], options) – Chemical order of split. Defaults to biosteam.settings.chemicals.IDs

Examples

From a dictionary:

>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Methanol', 'Ethanol'], cache=True)
>>> chemical_indexer = chemicals.isplit(dict(Water=0.5, Ethanol=1.))
>>> chemical_indexer.show()
ChemicalIndexer:
 Water    0.5
 Ethanol  1

From iterable given the order:

>>> chemical_indexer = chemicals.isplit([0.5, 1], ['Water', 'Ethanol'])
>>> chemical_indexer.show()
ChemicalIndexer:
 Water    0.5
 Ethanol  1

From a fraction:

>>> chemical_indexer = chemicals.isplit(0.75)
>>> chemical_indexer.show()
ChemicalIndexer:
 Water     0.75
 Methanol  0.75
 Ethanol   0.75

From an iterable (assuming same order as the Chemicals object):

>>> chemical_indexer = chemicals.isplit([0.5, 0, 1])
>>> chemical_indexer.show()
ChemicalIndexer:
 Water    0.5
 Ethanol  1
index(ID)[source]

Return index of specified chemical.

Parameters

ID (str) – Chemical identifier.

Examples

Index by ID:

>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Ethanol'])
>>> chemicals.index('Water')
0

Indices by CAS number:

>>> chemicals.index('7732-18-5')
0
indices(IDs)[source]

Return indices of specified chemicals.

Parameters

IDs (iterable) – Chemical indentifiers.

Examples

Indices by ID:

>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Ethanol'])
>>> chemicals.indices(['Water', 'Ethanol'])
[0, 1]

Indices by CAS number:

>>> chemicals.indices(['7732-18-5', '64-17-5'])
[0, 1]
get_index(IDs)[source]

Return index/indices of specified chemicals.

Parameters

IDs (iterable[str] or str) – Chemical identifiers.

Notes

CAS numbers are also supported.

Examples

Get multiple indices with a tuple of IDs:

>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Ethanol'], cache=True)
>>> IDs = ('Water', 'Ethanol')
>>> chemicals.get_index(IDs)
[0, 1]

Get a single index with a string:

>>> chemicals.get_index('Ethanol')
1

An Ellipsis returns a slice:

>>> chemicals.get_index(...)
slice(None, None, None)

Anything else returns an error:

>>> chemicals.get_index(['Water', 'Ethanol'])
Traceback (most recent call last):
TypeError: only strings, tuples, and ellipsis are valid index keys
get_vle_indices(nonzeros)[source]

Return indices of species in vapor-liquid equilibrium given an array dictating whether or not the chemicals are present.

Examples

>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Methanol', 'Ethanol'])
>>> data = chemicals.kwarray(dict(Water=2., Ethanol=1.))
>>> chemicals.get_vle_indices(data!=0)
[0, 2]
get_lle_indices(nonzeros)[source]

Return indices of species in liquid-liquid equilibrium given an array dictating whether or not the chemicals are present.

Examples

>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Methanol', 'Ethanol'])
>>> data = chemicals.kwarray(dict(Water=2., Ethanol=1.))
>>> chemicals.get_lle_indices(data!=0)
[0, 2]
append(*args, **kwargs)

Append a Chemical.

extend(*args, **kwargs)

Extend with more Chemical objects.

Thermo

class thermosteam.Thermo(chemicals, mixture=None, Gamma=<class 'thermosteam.equilibrium.activity_coefficients.DortmundActivityCoefficients'>, Phi=<class 'thermosteam.equilibrium.fugacity_coefficients.IdealFugacityCoefficients'>, PCF=<class 'thermosteam.equilibrium.poyinting_correction_factors.IdealPoyintingCorrectionFactors'>, cache=None)[source]

Create a Thermo object that defines a thermodynamic property package

Parameters
  • chemicals (Chemicals or Iterable[str]) – Pure component chemical data.

  • mixture (Mixture, optional) – Calculates mixture properties.

  • Gamma (ActivityCoefficients subclass, optional) – Class for computing activity coefficients.

  • Phi (FugacityCoefficients subclass, optional) – Class for computing fugacity coefficients.

  • PCF (PoyntingCorrectionFactor subclass, optional) – Class for computing poynting correction factors.

  • cache (optional) – Whether or not to use cached chemicals.

Examples

Create a property package for water and ethanol:

>>> import thermosteam as tmo
>>> thermo = tmo.Thermo(['Ethanol', 'Water'], cache=True)
>>> thermo
Thermo(chemicals=CompiledChemicals([Ethanol, Water]), mixture=Mixture(rule='ideal mixing', ..., include_excess_energies=False), Gamma=DortmundActivityCoefficients, Phi=IdealFugacityCoefficients, PCF=IdealPoyintingCorrectionFactors)
>>> thermo.show() # May be easier to read
Thermo(
    chemicals=CompiledChemicals([Ethanol, Water]),
    mixture=Mixture(
        rule='ideal mixing', ...
        include_excess_energies=False
    ),
    Gamma=DortmundActivityCoefficients,
    Phi=IdealFugacityCoefficients,
    PCF=IdealPoyintingCorrectionFactors
)

Note that the Dortmund-UNIFAC is the default activity coefficient model. The ideal-equilibrium property package (which assumes a value of 1 for all activity coefficients) is also available:

>>> ideal = thermo.ideal()
>>> ideal.show()
Thermo(
    chemicals=CompiledChemicals([Ethanol, Water]),
    mixture=Mixture(
        rule='ideal mixing', ...
        include_excess_energies=False
    ),
    Gamma=IdealActivityCoefficients,
    Phi=IdealFugacityCoefficients,
    PCF=IdealPoyintingCorrectionFactors
)

Thermodynamic equilibrium results are affected by the choice of property package:

>>> # Ideal
>>> tmo.settings.set_thermo(ideal)
>>> stream = tmo.Stream('stream', Water=100, Ethanol=100)
>>> stream.vle(T=361, P=101325)
>>> stream.show()
MultiStream: stream
 phases: ('g', 'l'), T: 361 K, P: 101325 Pa
 flow (kmol/hr): (g) Ethanol  32.2
                     Water    17.3
                 (l) Ethanol  67.8
                     Water    82.7
>>> # Modified Roult's law:
>>> tmo.settings.set_thermo(thermo)
>>> stream = tmo.Stream('stream', Water=100, Ethanol=100)
>>> stream.vle(T=360, P=101325)
>>> stream.show()
MultiStream: stream
 phases: ('g', 'l'), T: 360 K, P: 101325 Pa
 flow (kmol/hr): (g) Ethanol  100
                     Water    100

Thermodynamic property packages are pickleable:

>>> tmo.utils.save(thermo, "Ethanol-Water Property Package")
>>> thermo = tmo.utils.load("Ethanol-Water Property Package")
>>> thermo.show()
Thermo(
    chemicals=CompiledChemicals([Ethanol, Water]),
    mixture=Mixture(
        rule='ideal mixing', ...
        include_excess_energies=False
    ),
    Gamma=DortmundActivityCoefficients,
    Phi=IdealFugacityCoefficients,
    PCF=IdealPoyintingCorrectionFactors
)
chemicals

Pure component chemical data.

Type

Chemicals or Iterable[str]

mixture

Calculates mixture properties.

Type

Mixture, optional

Gamma

Class for computing activity coefficients.

Type

ActivityCoefficients subclass, optional

Phi

Class for computing fugacity coefficients.

Type

FugacityCoefficients subclass, optional

PCF

Class for computing poynting correction factors.

Type

PoyntingCorrectionFactor subclass, optional

ideal()[source]

Ideal thermodynamic property package.

as_chemical(chemical)[source]

Return chemical as a Chemical object.

Parameters

chemical (str or Chemical) – Name of chemical being retrieved.

Examples

>>> import thermosteam as tmo
>>> thermo = tmo.Thermo(['Ethanol', 'Water'], cache=True)
>>> thermo.as_chemical('Water') is thermo.chemicals.Water
True
>>> thermo.as_chemical('Octanol') # Chemical not defined, so it will be created
Chemical('Octanol')
subgroup(IDs)[source]

Create a Thermo object from a subset of chemicals.

Parameters

IDs (Iterable[str]) – Names of chemicals.

Examples

>>> import thermosteam as tmo
>>> thermo = tmo.Thermo(['Ethanol', 'Water'], cache=True)
>>> thermo_water = thermo.subgroup(['Water'])
>>> thermo_water.show()
Thermo(
    chemicals=CompiledChemicals([Water]),
    mixture=Mixture(
        rule='ideal mixing', ...
        include_excess_energies=False
    ),
    Gamma=DortmundActivityCoefficients,
    Phi=IdealFugacityCoefficients,
    PCF=IdealPoyintingCorrectionFactors
)

Stream

class thermosteam.Stream(ID='', flow=(), phase='l', T=298.15, P=101325.0, units='kmol/hr', price=0.0, thermo=None, **chemical_flows)[source]

Create a Stream object that defines material flow rates along with its thermodynamic state. Thermodynamic and transport properties of a stream are available as properties, while thermodynamic equilbrium (e.g. VLE, and bubble and dew points) are available as methods.

Parameters
  • ID='' (str) – A unique identification. If ID is None, stream will not be registered. If no ID is given, stream will be registered with a unique ID.

  • flow=() (Iterable[float]) – All flow rates corresponding to chemical IDs.

  • phase='l' ('l', 'g', or 's') – Either gas (g), liquid (l), or solid (s).

  • T=298.15 (float) – Temperature [K].

  • P=101325 (float) – Pressure [Pa].

  • units='kmol/hr' (str) – Flow rate units of measure (only mass, molar, and volumetric flow rates are valid).

  • price=0 (float) – Price per unit mass [USD/kg].

  • thermo=None (Thermo) – Thermo object to initialize input and output streams. Defaults to biosteam.settings.get_thermo().

  • **chemical_flows (float) – ID - flow pairs.

Examples

Before creating a stream, first set the chemicals:

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)

Create a stream, defining the thermodynamic condition and flow rates:

>>> s1 = tmo.Stream(ID='s1',
...                 Water=20, Ethanol=10, units='kg/hr',
...                 T=298.15, P=101325, phase='l')
>>> s1.show(flow='kg/hr') # Use the show method to select units of display
Stream: s1
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): Water    20
               Ethanol  10
>>> s1.show(composition=True, flow='kg/hr') # Its also possible to show by composition
Stream: s1
 phase: 'l', T: 298.15 K, P: 101325 Pa
 composition: Water    0.667
              Ethanol  0.333
              -------  30 kg/hr

All flow rates are stored as an array in the mol attribute:

>>> s1.mol # Molar flow rates [kmol/hr]
array([1.11 , 0.217])

Mass and volumetric flow rates are available as property arrays:

>>> s1.mass
property_array([<Water: 20 kg/hr>, <Ethanol: 10 kg/hr>])
>>> s1.vol
property_array([<Water: 0.02006 m^3/hr>, <Ethanol: 0.012722 m^3/hr>])

These arrays work just like ordinary arrays, but the data is linked to the molar flows:

>>> # Mass flows are always up to date with molar flows
>>> s1.mol[0] = 1
>>> s1.mass[0]
<Water: 18.015 kg/hr>
>>> # Changing mass flows changes molar flows
>>> s1.mass[0] *= 2
>>> s1.mol[0]
2.0
>>> # Property arrays act just like normal arrays
>>> s1.mass + 2
array([38.031, 12.   ])

The temperature, pressure and phase are attributes as well:

>>> (s1.T, s1.P, s1.phase)
(298.15, 101325.0, 'l')

The most convinient way to get and set flow rates is through the get_flow and set_flow methods:

>>> # Set flow
>>> s1.set_flow(1, 'gpm', 'Water')
>>> s1.get_flow('gpm', 'Water')
1.0
>>> # Set multiple flows
>>> s1.set_flow([10, 20], 'kg/hr', ('Ethanol', 'Water'))
>>> s1.get_flow('kg/hr', ('Ethanol', 'Water'))
array([10., 20.])

It is also possible to index using IDs through the imol, imass, and ivol indexers:

>>> s1.imol.show()
ChemicalMolarFlowIndexer (kmol/hr):
 (l) Water    1.11
     Ethanol  0.2171
>>> s1.imol['Water']
1.1101687012358397
>>> s1.imol['Ethanol', 'Water']
array([0.217, 1.11 ])

Thermodynamic properties are available as stream properties:

>>> s1.H # Enthalpy (kJ/hr)
0.0

Note that the reference enthalpy is 0.0 at the reference temperature of 298.15 K, and pressure of 101325 Pa. Retrive the enthalpy at a 10 degC above the reference.

>>> s1.T += 10
>>> s1.H
1083.467954...

Other thermodynamic properties are temperature and pressure dependent as well:

>>> s1.rho # Density [kg/m3]
908.8914226...

It may be more convinient to get properties with different units:

>>> s1.get_property('rho', 'g/cm3')
0.90889142...

It is also possible to set some of the properties in different units:

>>> s1.set_property('T', 40, 'degC')
>>> s1.T
313.15

Bubble point and dew point computations can be performed through stream methods:

>>> bp = s1.bubble_point_at_P() # Bubble point at constant pressure
>>> bp
BubblePointValues(T=357.09, P=101325, IDs=('Water', 'Ethanol'), z=[0.836 0.164], y=[0.49 0.51])

The bubble point results contain all results as attributes:

>>> bp.T # Temperature [K]
357.088...
>>> bp.y # Vapor composition
array([0.49, 0.51])

Vapor-liquid equilibrium can be performed by setting 2 degrees of freedom from the following list: T [Temperature; in K], P [Pressure; in Pa], V [Vapor fraction], H [Enthalpy; in kJ/hr].

Set vapor fraction and pressure of the stream:

>>> s1.vle(P=101325, V=0.5)
>>> s1.show()
MultiStream: s1
 phases: ('g', 'l'), T: 364.8 K, P: 101325 Pa
 flow (kmol/hr): (g) Water    0.472
                     Ethanol  0.192
                 (l) Water    0.638
                     Ethanol  0.0255

Note that the stream is a now a MultiStream object to manage multiple phases. Each phase can be accessed separately too:

>>> s1['l'].show()
Stream:
 phase: 'l', T: 364.8 K, P: 101325 Pa
 flow (kmol/hr): Water    0.638
                 Ethanol  0.0255
>>> s1['g'].show()
Stream:
 phase: 'g', T: 364.8 K, P: 101325 Pa
 flow (kmol/hr): Water    0.472
                 Ethanol  0.192

We can convert a MultiStream object back to a Stream object by setting the phase:

>>> s1.phase = 'l'
>>> s1.show(flow='kg/hr')
Stream: s1
 phase: 'l', T: 364.8 K, P: 101325 Pa
 flow (kg/hr): Water    20
               Ethanol  10
display_units = DisplayUnits(T='K', P='Pa', flow='kmol/hr', composition=False, N=7)

[DisplayUnits] Units of measure for IPython display (class attribute)

as_stream()[source]

Does nothing.

property price

[float] Price of stream per unit mass [USD/kg.

isempty()[source]

Return whether or not stream is empty.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water'], cache=True)
>>> stream = tmo.Stream()
>>> stream.isempty()
True
property vapor_fraction

Molar vapor fraction.

property liquid_fraction

Molar liquid fraction.

property solid_fraction

Molar solid fraction.

isfeed()[source]

Return whether stream has a sink but no source.

isproduct()[source]

Return whether stream has a source but no sink.

property main_chemical

[str] ID of chemical with the largest mol fraction in stream.

disconnect()[source]

Disconnect stream from unit operations.

get_atomic_flow(symbol)[source]

Return flow rate of atom in kmol / hr given the atomic symbol.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water'], cache=True)
>>> stream = tmo.Stream(Water=1)
>>> stream.get_atomic_flow('H') # kmol/hr of H
2.0
>>> stream.get_atomic_flow('O') # kmol/hr of O
1.0
get_atomic_flows()[source]

Return dictionary of atomic flow rates in kmol / hr.

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water'], cache=True)
>>> stream = tmo.Stream(Water=1)
>>> stream.get_atomic_flows()
{'H': 2.0, 'O': 1.0}
get_flow(units, key=Ellipsis)[source]

Return an flow rates in requested units.

Parameters
  • units (str) – Units of measure.

  • key (tuple[str] or str, optional) – Chemical identifiers.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, units='kg/hr')
>>> s1.get_flow('kg/hr', 'Water')
20.0
set_flow(data, units, key=Ellipsis)[source]

Set flow rates in given units.

Parameters
  • data (1d ndarray or float) – Flow rate data.

  • units (str) – Units of measure.

  • key (Iterable[str] or str, optional) – Chemical identifiers.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream(ID='s1', Water=20, Ethanol=10, units='kg/hr')
>>> s1.set_flow(10, 'kg/hr', 'Water')
>>> s1.get_flow('kg/hr', 'Water')
10.0
get_total_flow(units)[source]

Get total flow rate in given units.

Parameters

units (str) – Units of measure.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, units='kg/hr')
>>> s1.get_total_flow('kg/hr')
30.0
set_total_flow(value, units)[source]

Set total flow rate in given units keeping the composition constant.

Parameters
  • value (float) – New total flow rate.

  • units (str) – Units of measure.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, units='kg/hr')
>>> s1.set_total_flow(1.0,'kg/hr')
>>> s1.get_total_flow('kg/hr')
0.9999999999999999
get_property(name, units)[source]

Return property in requested units.

Parameters
  • name (str) – Name of stream property.

  • units (str) – Units of measure.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, units='kg/hr')
>>> s1.get_property('sigma', 'N/m') # Surface tension
0.063780393
set_property(name, value, units)[source]

Set property in given units.

Parameters
  • name (str) – Name of stream property.

  • value (str) – New value of stream property.

  • units (str) – Units of measure.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, units='kg/hr')
>>> s1.set_property('P', 2, 'atm')
>>> s1.P
202650.0
property source

[Unit] Outlet location.

property sink

[Unit] Inlet location.

property thermal_condition

[ThermalCondition] Contains the temperature and pressure conditions of the stream.

property T

[float] Temperature in Kelvin.

property P

[float] Pressure in Pascal.

property phase

Phase of stream.

property mol

[array] Molar flow rates in kmol/hr.

property mass

[property_array] Mass flow rates in kg/hr.

property vol

[property_array] Volumetric flow rates in m3/hr.

property imol

[Indexer] Flow rate indexer with data in kmol/hr.

property imass

[Indexer] Flow rate indexer with data in kg/hr.

property ivol

[Indexer] Flow rate indexer with data in m3/hr.

property cost

[float] Total cost of stream in USD/hr.

property F_mol

[float] Total molar flow rate in kmol/hr.

property F_mass

[float] Total mass flow rate in kg/hr.

property F_vol

[float] Total volumetric flow rate in m3/hr.

property H

[float] Enthalpy flow rate in kJ/hr.

property S

[float] Absolute entropy flow rate in kJ/hr.

property Hnet

[float] Total enthalpy flow rate (including heats of formation) in kJ/hr.

property Hf

[float] Enthalpy of formation flow rate in kJ/hr.

property LHV

[float] Lower heating value flow rate in kJ/hr.

property HHV

[float] Higher heating value flow rate in kJ/hr.

property Hvap

[float] Enthalpy of vaporization flow rate in kJ/hr.

property C

[float] Heat capacity flow rate in kJ/K/hr.

property z_mol

[1d array] Molar composition.

property z_mass

[1d array] Mass composition.

property z_vol

[1d array] Volumetric composition.

property MW

[float] Overall molecular weight.

property V

[float] Molar volume [m^3/mol].

property kappa

[float] Thermal conductivity [W/m/k].

property Cn

[float] Molar heat capacity [J/mol/K].

property mu

[float] Hydrolic viscosity [Pa*s].

property sigma

[float] Surface tension [N/m].

property epsilon

[float] Relative permittivity [-].

property Cp

[float] Heat capacity [J/g/K].

property alpha

[float] Thermal diffusivity [m^2/s].

property rho

[float] Density [kg/m^3].

property nu

[float] Kinematic viscosity [-].

property Pr

[float] Prandtl number [-].

property available_chemicals

list[Chemical] All chemicals with nonzero flow.

in_thermal_equilibrium(other)[source]

Return whether or not stream is in thermal equilibrium with another stream.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> stream = Stream(Water=1, T=300)
>>> other = Stream(Water=1, T=300)
>>> stream.in_thermal_equilibrium(other)
True
classmethod sum(streams, ID=None, thermo=None)[source]

Return a new Stream object that represents the sum of all given streams.

Examples

Sum two streams:

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, units='kg/hr')
>>> s_sum = tmo.Stream.sum([s1, s1], 's_sum')
>>> s_sum.show(flow='kg/hr')
Stream: s_sum
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): Water    40
               Ethanol  20

Sum two streams with new property package:

>>> thermo = tmo.Thermo(['Water', 'Ethanol', 'Methanol'], cache=True)
>>> s_sum = tmo.Stream.sum([s1, s1], 's_sum', thermo)
>>> s_sum.show(flow='kg/hr')
Stream: s_sum
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): Water    40
               Ethanol  20
mix_from(others)[source]

Mix all other streams into this one, ignoring its initial contents.

Examples

Mix two streams with the same thermodynamic property package:

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, units='kg/hr')
>>> s2 = s1.copy('s2')
>>> s1.mix_from([s1, s2])
>>> s1.show(flow='kg/hr')
Stream: s1
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): Water    40
               Ethanol  20

It’s also possible to mix streams with different property packages so long as all chemicals are defined in the mixed stream’s property package:

>>> tmo.settings.set_thermo(['Water'], cache=True)
>>> s1 = tmo.Stream('s1', Water=40, units='kg/hr')
>>> tmo.settings.set_thermo(['Ethanol'], cache=True)
>>> s2 = tmo.Stream('s2', Ethanol=20, units='kg/hr')
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s_mix = tmo.Stream('s_mix')
>>> s_mix.mix_from([s1, s2])
>>> s_mix.show(flow='kg/hr')
Stream: s_mix
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): Water    40
               Ethanol  20
split_to(s1, s2, split)[source]

Split molar flow rate from this stream to two others given the split fraction or an array of split fractions.

Examples

>>> import thermosteam as tmo
>>> chemicals = tmo.Chemicals(['Water', 'Ethanol'], cache=True)
>>> tmo.settings.set_thermo(chemicals)
>>> s = tmo.Stream('s', Water=20, Ethanol=10, units='kg/hr')
>>> s1 = tmo.Stream('s1')
>>> s2 = tmo.Stream('s2')
>>> split = chemicals.kwarray(dict(Water=0.5, Ethanol=0.1))
>>> s.split_to(s1, s2, split)
>>> s1.show(flow='kg/hr')
Stream: s1
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): Water    10
               Ethanol  1
>>> s2.show(flow='kg/hr')
Stream: s2
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): Water    10
               Ethanol  9

Link with another stream.

Parameters
  • other (Stream) –

  • flow (bool, defaults to True) – Whether to link the flow rate data.

  • phase (bool, defaults to True) – Whether to link the phase.

  • TP (bool, defaults to True) – Whether to link the temperature and pressure.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, units='kg/hr')
>>> s2 = tmo.Stream('s2')
>>> s2.link_with(s1)
>>> s1.mol is s2.mol
True
>>> s2.thermal_condition is s1.thermal_condition
True
>>> s1.phase = 'g'
>>> s2.phase
'g'

Unlink stream from other streams.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, units='kg/hr')
>>> s2 = tmo.Stream('s2')
>>> s2.link_with(s1)
>>> s1.unlink()
>>> s2.mol is s1.mol
False
copy_like(other)[source]

Copy all conditions of another stream.

Examples

Copy data from another stream with the same property package:

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, units='kg/hr')
>>> s2 = tmo.Stream('s2', Water=2, units='kg/hr')
>>> s1.copy_like(s2)
>>> s1.show(flow='kg/hr')
Stream: s1
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): Water  2

Copy data from another stream with a different property package:

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, units='kg/hr')
>>> tmo.settings.set_thermo(['Water'], cache=True)
>>> s2 = tmo.Stream('s2', Water=2, units='kg/hr')
>>> s1.copy_like(s2)
>>> s1.show(flow='kg/hr')
Stream: s1
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): Water  2
copy_thermal_condition(other)[source]

Copy thermal conditions (T and P) of another stream.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=2, units='kg/hr')
>>> s2 = tmo.Stream('s2', Water=1, units='kg/hr', T=300.00)
>>> s1.copy_thermal_condition(s2)
>>> s1.show(flow='kg/hr')
Stream: s1
 phase: 'l', T: 300 K, P: 101325 Pa
 flow (kg/hr): Water  2
copy_flow(stream, IDs=Ellipsis, *, remove=False, exclude=False)[source]

Copy flow rates of stream to self.

Parameters
  • stream (Stream) – Flow rates will be copied from here.

  • IDs=... (Iterable[str], defaults to all chemicals.) – Chemical IDs.

  • remove=False (bool, optional) – If True, copied chemicals will be removed from stream.

  • exclude=False (bool, optional) – If True, exclude designated chemicals when copying.

Examples

Initialize streams:

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, units='kg/hr')
>>> s2 = tmo.Stream('s2')

Copy all flows:

>>> s2.copy_flow(s1)
>>> s2.show(flow='kg/hr')
Stream: s2
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): Water    20
               Ethanol  10

Reset and copy just water flow:

>>> s2.empty()
>>> s2.copy_flow(s1, 'Water')
>>> s2.show(flow='kg/hr')
Stream: s2
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): Water  20

Reset and copy all flows except water:

>>> s2.empty()
>>> s2.copy_flow(s1, 'Water', exclude=True)
>>> s2.show(flow='kg/hr')
Stream: s2
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): Ethanol  10

Cut and paste flows:

>>> s2.copy_flow(s1, remove=True)
>>> s2.show(flow='kg/hr')
Stream: s2
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): Water    20
               Ethanol  10
>>> s1.show()
Stream: s1
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow: 0

Its also possible to copy flows from a multistream:

>>> s1.phases = ('g', 'l')
>>> s1.imol['g', 'Water'] = 10
>>> s2.copy_flow(s1, remove=True)
>>> s2.show()
Stream: s2
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kmol/hr): Water  10
>>> s1.show()
MultiStream: s1
 phases: ('g', 'l'), T: 298.15 K, P: 101325 Pa
 flow: 0
copy(ID=None)[source]

Return a copy of the stream.

Examples

Create a copy of a new stream:

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, units='kg/hr')
>>> s1_copy = s1.copy('s1_copy')
>>> s1_copy.show(flow='kg/hr')
Stream: s1_copy
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): Water    20
               Ethanol  10

Warning

Prices are not copied.

flow_proxy(ID=None)[source]

Return a new stream that shares flow rate data with this one.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, units='kg/hr')
>>> s2 = s1.flow_proxy()
>>> s2.mol is s1.mol
True
proxy(ID=None)[source]

Return a new stream that shares all data with this one.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, units='kg/hr')
>>> s2 = s1.proxy()
>>> s2.imol is s1.imol and s2.thermal_condition is s1.thermal_condition
True
empty()[source]

Empty stream flow rates.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, units='kg/hr')
>>> s1.empty()
>>> s1.F_mol
0.0
property vle

[VLE] An object that can perform vapor-liquid equilibrium on the stream.

property lle

[LLE] An object that can perform liquid-liquid equilibrium on the stream.

property sle

[SLE] An object that can perform solid-liquid equilibrium on the stream.

property vle_chemicals

list[Chemical] Chemicals cabable of liquid-liquid equilibrium.

property lle_chemicals

list[Chemical] Chemicals cabable of vapor-liquid equilibrium.

get_bubble_point(IDs=None)[source]

Return a BubblePoint object capable of computing bubble points.

Parameters

IDs (Iterable[str], optional) – Chemicals that participate in equilibrium. Defaults to all chemicals in equilibrium.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, T=350, units='kg/hr')
>>> s1.get_bubble_point()
BubblePoint([Water, Ethanol])
get_dew_point(IDs=None)[source]

Return a DewPoint object capable of computing dew points.

Parameters

IDs (Iterable[str], optional) – Chemicals that participate in equilibrium. Defaults to all chemicals in equilibrium.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, T=350, units='kg/hr')
>>> s1.get_dew_point()
DewPoint([Water, Ethanol])
bubble_point_at_T(T=None, IDs=None)[source]

Return a BubblePointResults object with all data on the bubble point at constant temperature.

Parameters

IDs (Iterable[str], optional) – Chemicals that participate in equilibrium. Defaults to all chemicals in equilibrium.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, T=350, units='kg/hr')
>>> s1.bubble_point_at_T()
BubblePointValues(T=350.00, P=76622, IDs=('Water', 'Ethanol'), z=[0.836 0.164], y=[0.486 0.514])
bubble_point_at_P(P=None, IDs=None)[source]

Return a BubblePointResults object with all data on the bubble point at constant pressure.

Parameters

IDs (Iterable[str], optional) – Chemicals that participate in equilibrium. Defaults to all chemicals in equilibrium.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, T=350, units='kg/hr')
>>> s1.bubble_point_at_P()
BubblePointValues(T=357.09, P=101325, IDs=('Water', 'Ethanol'), z=[0.836 0.164], y=[0.49 0.51])
dew_point_at_T(T=None, IDs=None)[source]

Return a DewPointResults object with all data on the dew point at constant temperature.

Parameters

IDs (Iterable[str], optional) – Chemicals that participate in equilibrium. Defaults to all chemicals in equilibrium.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, T=350, units='kg/hr')
>>> s1.dew_point_at_T()
DewPointValues(T=350.00, P=48991, IDs=('Water', 'Ethanol'), z=[0.836 0.164], x=[0.984 0.016])
dew_point_at_P(P=None, IDs=None)[source]

Return a DewPointResults object with all data on the dew point at constant pressure.

Parameters

IDs (Iterable[str], optional) – Chemicals that participate in equilibrium. Defaults to all chemicals in equilibrium.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, T=350, units='kg/hr')
>>> s1.dew_point_at_P()
DewPointValues(T=368.66, P=101325, IDs=('Water', 'Ethanol'), z=[0.836 0.164], x=[0.984 0.016])
get_normalized_mol(IDs)[source]

Return normalized molar fractions of given chemicals. The sum of the result is always 1.

Parameters

IDs (tuple[str]) – IDs of chemicals to be normalized.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', 'Methanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, Methanol=10, units='kmol/hr')
>>> s1.get_normalized_mol(('Water', 'Ethanol'))
array([0.667, 0.333])
get_normalized_mass(IDs)[source]

Return normalized mass fractions of given chemicals. The sum of the result is always 1.

Parameters

IDs (tuple[str]) – IDs of chemicals to be normalized.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', 'Methanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, Methanol=10, units='kg/hr')
>>> s1.get_normalized_mass(('Water', 'Ethanol'))
array([0.667, 0.333])
get_normalized_vol(IDs)[source]

Return normalized mass fractions of given chemicals. The sum of the result is always 1.

Parameters

IDs (tuple[str]) – IDs of chemicals to be normalized.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', 'Methanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, Methanol=10, units='m3/hr')
>>> s1.get_normalized_vol(('Water', 'Ethanol'))
array([0.667, 0.333])
get_molar_composition(IDs)[source]

Return molar composition of given chemicals.

Parameters

IDs (tuple[str]) – IDs of chemicals.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', 'Methanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, Methanol=10, units='kmol/hr')
>>> s1.get_molar_composition(('Water', 'Ethanol'))
array([0.5 , 0.25])
get_mass_composition(IDs)[source]

Return mass composition of given chemicals.

Parameters

IDs (tuple[str]) – IDs of chemicals.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', 'Methanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, Methanol=10, units='kg/hr')
>>> s1.get_mass_composition(('Water', 'Ethanol'))
array([0.5 , 0.25])
get_volumetric_composition(IDs)[source]

Return volumetric composition of given chemicals.

Parameters

IDs (tuple[str]) – IDs of chemicals.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', 'Methanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, Methanol=10, units='m3/hr')
>>> s1.get_volumetric_composition(('Water', 'Ethanol'))
array([0.5 , 0.25])
property ID

Unique identification (str). If set as ‘’, it will choose a default ID.

get_concentration(IDs)[source]

Return concentration of given chemicals in kmol/m3.

Parameters

IDs (tuple[str]) – IDs of chemicals.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', 'Methanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, Methanol=10, units='m3/hr')
>>> s1.get_concentration(('Water', 'Ethanol'))
array([27.671,  4.266])
property P_vapor

Vapor pressure of liquid.

receive_vent(other, accumulate=False, fraction=1.0)[source]

Receive vapors from another stream as if in equilibrium.

Notes

Disregards energy balance. Assumes liquid composition will not change significantly.

Examples

>>> import thermosteam as tmo
>>> chemicals = tmo.Chemicals(['Water', 'Ethanol', 'Methanol', tmo.Chemical('N2', phase='g')], cache=True)
>>> tmo.settings.set_thermo(chemicals)
>>> s1 = tmo.Stream('s1', N2=10, units='m3/hr', phase='g', T=330)
>>> s2 = tmo.Stream('s2', Water=10, Ethanol=2, T=330)
>>> s1.receive_vent(s2, accumulate=True)
>>> s1.show(flow='kmol/hr')
Stream: s1
 phase: 'g', T: 330 K, P: 101325 Pa
 flow (kmol/hr): Water    0.0557
                 Ethanol  0.0616
                 N2       0.369

[Stream] Data on the thermal condition and material flow rates may be shared with this stream.

property phases

tuple[str] All phases present.

show(T=None, P=None, flow=None, composition=None, N=None)[source]

Print all specifications.

Parameters
  • T (str, optional) – Temperature units.

  • P (str, optional) – Pressure units.

  • flow (str, optional) – Flow rate units.

  • composition (bool, optional) – Whether to show composition.

  • N (int, optional) – Number of compounds to display.

Notes

Default values are stored in Stream.display_units.

print(units=None)[source]

Print in a format that you can use recreate the stream.

Parameters

units (str, optional) – Units of measure for material flow rates. Defaults to ‘kmol/hr’

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream(ID='s1',
...                 Water=20, Ethanol=10, units='kg/hr',
...                 T=298.15, P=101325, phase='l')
>>> s1.print(units='kg/hr')
Stream(ID='s1', phase='l', T=298.15, P=101325, Water=20, Ethanol=10, units='kg/hr')
>>> s1.print() # Units default to kmol/hr
Stream(ID='s1', phase='l', T=298.15, P=101325, Water=1.11, Ethanol=0.2171, units='kmol/hr')

MultiStream

A MultiStream object represents a material flow with multiple phases in a chemical process.

class thermosteam.MultiStream(ID='', flow=(), T=298.15, P=101325.0, phases=('g', 'l'), units=None, thermo=None, price=0, **phase_flows)[source]

Create a MultiStream object that defines material flow rates for multiple phases along with its thermodynamic state. Thermodynamic and transport properties of a stream are available as properties, while thermodynamic equilbrium (e.g. VLE, and bubble and dew points) are available as methods.

Parameters
  • ID='' (str) – A unique identification. If ID is None, stream will not be registered. If no ID is given, stream will be registered with a unique ID.

  • flow=() (2d array) – All flow rates corresponding to phases by row and chemical IDs by column.

  • thermo=None (Thermo) – Thermodynamic equilibrium package. Defaults to thermosteam.settings.get_thermo().

  • units='kmol/hr' (str) – Flow rate units of measure (only mass, molar, and volumetric flow rates are valid).

  • phases (tuple['g', 'l', 's', 'G', 'L', 'S']) – Tuple denoting the phases present. Defaults to (‘g’, ‘l’).

  • T=298.15 (float) – Temperature [K].

  • P=101325 (float) – Pressure [Pa].

  • price=0 (float) – Price per unit mass [USD/kg].

  • **phase_flow (tuple[str, float]) – phase-(ID, flow) pairs.

Examples

Before creating streams, first set the chemicals:

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)

Create a multi phase stream, defining the thermodynamic condition and flow rates:

>>> s1 = tmo.MultiStream(ID='s1',T=298.15, P=101325,
...                      l=[('Water', 20), ('Ethanol', 10)], units='kg/hr')
>>> s1.show(flow='kg/hr') # Use the show method to select units of display
MultiStream: s1
 phases: ('g', 'l'), T: 298.15 K, P: 101325 Pa
 flow (kg/hr): (l) Water    20
                   Ethanol  10

The temperature and pressure are stored as attributes:

>>> (s1.T, s1.P)
(298.15, 101325.0)

Unlike Stream objects, the mol attribute does not store data, it simply returns the total flow rate of each chemical. Setting an element of the array raises an error to prevent the wrong assumption that the data is linked:

>>> s1.mol
array([1.11 , 0.217])
>>> s1.mol[0] = 1
Traceback (most recent call last):
ValueError: assignment destination is read-only

All flow rates are stored in the imol attribute:

>>> s1.imol.show() # Molar flow rates [kmol/hr]
MolarFlowIndexer (kmol/hr):
 (l) Water     1.11
     Ethanol   0.2171
>>> # Index a single chemical in the liquid phase
>>> s1.imol['l', 'Water']
1.1101687012358397
>>> # Index multiple chemicals in the liquid phase
>>> s1.imol['l', ('Ethanol', 'Water')]
array([0.217, 1.11 ])
>>> # Index the vapor phase
>>> s1.imol['g']
array([0., 0.])
>>> # Index flow of chemicals summed across all phases
>>> s1.imol['Ethanol', 'Water']
array([0.217, 1.11 ])

Note that overall chemical flows in MultiStream objects cannot be set like with Stream objects:

>>> # s1.imol['Ethanol', 'Water'] = [1, 0]
Traceback (most recent call last):
IndexError: multiple phases present; must include phase key to set chemical data

Chemical flows must be set by phase:

>>> s1.imol['l', ('Ethanol', 'Water')] = [1, 0]

The most convinient way to get and set flow rates is through the get_flow and set_flow methods:

>>> # Set flow
>>> key = ('l', 'Water')
>>> s1.set_flow(1, 'gpm', key)
>>> s1.get_flow('gpm', key)
1.0
>>> # Set multiple flows
>>> key = ('l', ('Ethanol', 'Water'))
>>> s1.set_flow([10, 20], 'kg/hr', key)
>>> s1.get_flow('kg/hr', key)
array([10., 20.])

Chemical flows across all phases can be retrieved if no phase is given:

>>> s1.get_flow('kg/hr', ('Ethanol', 'Water'))
array([10., 20.])

However, setting chemical data requires the phase to be specified:

>>> s1.set_flow([10, 20], 'kg/hr', ('Ethanol', 'Water'))
Traceback (most recent call last):
IndexError: multiple phases present; must include phase key to set chemical data

Note that for both Stream and MultiStream objects, mol, imol, and get_flow return chemical flows across all phases when given only chemical IDs.

Vapor-liquid equilibrium can be performed by setting 2 degrees of freedom from the following list:

  • T - Temperature [K]

  • P - Pressure [Pa]

  • V - Vapor fraction

  • H - Enthalpy [kJ/hr]

>>> s1.vle(P=101325, T=365)

Each phase can be accessed separately too:

>>> s1['l'].show()
Stream:
 phase: 'l', T: 365 K, P: 101325 Pa
 flow (kmol/hr): Water    0.619
                 Ethanol  0.0238
>>> s1['g'].show()
Stream:
 phase: 'g', T: 365 K, P: 101325 Pa
 flow (kmol/hr): Water    0.491
                 Ethanol  0.193

Note that the phase cannot be changed:

>>> s1['g'].phase = 'l'
Traceback (most recent call last):
AttributeError: phase is locked
get_flow(units, key=Ellipsis)[source]

Return an array of flow rates in requested units.

Parameters
  • units (str) – Units of measure.

  • key (tuple(phase, IDs), phase, or IDs) –

    • phase: str, ellipsis, or missing.

    • IDs: str, tuple(str), ellipisis, or missing.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.MultiStream('s1', l=[('Water', 20), ('Ethanol', 10)], units='kg/hr')
>>> s1.get_flow('kg/hr', ('l', 'Water'))
20.0
set_flow(data, units, key=Ellipsis)[source]

Set flow rates in given units.

Parameters
  • data (1d ndarray or float) – Flow rate data.

  • units (str) – Units of measure.

  • key (tuple(phase, IDs), phase, or IDs) –

    • phase: str, ellipsis, or missing.

    • IDs: str, tuple(str), ellipisis, or missing.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.MultiStream('s1', l=[('Water', 20), ('Ethanol', 10)], units='kg/hr')
>>> s1.set_flow(10, 'kg/hr', ('l', 'Water'))
>>> s1.get_flow('kg/hr', ('l', 'Water'))
10.0
property phases

tuple[str] All phases avaiable.

property mol

[Array] Chemical molar flow rates (total of all phases).

property mass

[Array] Chemical mass flow rates (total of all phases).

property vol

[Array] Chemical volumetric flow rates (total of all phases).

property H

[float] Enthalpy flow rate in kJ/hr.

property S

[float] Absolute entropy flow rate in kJ/hr.

property C

[float] Heat capacity flow rate in kJ/hr.

property F_vol

[float] Total volumetric flow rate in m3/hr.

property Hvap

[float] Enthalpy of vaporization flow rate in kJ/hr.

property vapor_fraction

Molar vapor fraction.

property liquid_fraction

Molar liquid fraction.

property solid_fraction

Molar solid fraction.

property V

[float] Molar volume [m^3/mol].

property kappa

[float] Thermal conductivity [W/m/k].

property Cn

[float] Molar heat capacity [J/mol/K].

property mu

[float] Hydrolic viscosity [Pa*s].

property sigma

[float] Surface tension [N/m].

property epsilon

[float] Relative permittivity [-].

copy_flow(other, phase=Ellipsis, IDs=Ellipsis, *, remove=False, exclude=False)[source]

Copy flow rates of another stream to self.

Parameters
  • other (Stream) – Flow rates will be copied from here.

  • phase (str or Ellipsis) –

  • IDs=None (iterable[str], defaults to all chemicals.) – Chemical IDs.

  • remove=False (bool, optional) – If True, copied chemicals will be removed from stream.

  • exclude=False (bool, optional) – If True, exclude designated chemicals when copying.

Notes

Works just like <Stream>.copy_flow, but the phase must be specified.

Examples

Initialize streams:

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.MultiStream('s1', l=[('Water', 20), ('Ethanol', 10)], units='kg/hr')
>>> s2 = tmo.MultiStream('s2')

Copy all flows:

>>> s2.copy_flow(s1)
>>> s2.show(flow='kg/hr')
MultiStream: s2
 phases: ('g', 'l'), T: 298.15 K, P: 101325 Pa
 flow (kg/hr): (l) Water    20
                   Ethanol  10

Reset and copy just water flow:

>>> s2.empty()
>>> s2.copy_flow(s1, IDs='Water')
>>> s2.show(flow='kg/hr')
MultiStream: s2
 phases: ('g', 'l'), T: 298.15 K, P: 101325 Pa
 flow (kg/hr): (l) Water  20

Reset and copy all flows except water:

>>> s2.empty()
>>> s2.copy_flow(s1, IDs='Water', exclude=True)
>>> s2.show(flow='kg/hr')
MultiStream: s2
 phases: ('g', 'l'), T: 298.15 K, P: 101325 Pa
 flow (kg/hr): (l) Ethanol  10

Cut and paste flows:

>>> s2.copy_flow(s1, remove=True)
>>> s2.show(flow='kg/hr')
MultiStream: s2
 phases: ('g', 'l'), T: 298.15 K, P: 101325 Pa
 flow (kg/hr): (l) Water    20
                   Ethanol  10
>>> s1.show()
MultiStream: s1
 phases: ('g', 'l'), T: 298.15 K, P: 101325 Pa
 flow: 0

The other stream can also be a single phase stream (doesn’t have to be a MultiStream object):

Initialize streams:

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.Stream('s1', Water=20, Ethanol=10, units='kg/hr')
>>> s2 = tmo.MultiStream('s2')

Copy all flows:

>>> s2.copy_flow(s1)
>>> s2.show(flow='kg/hr')
MultiStream: s2
 phases: ('g', 'l'), T: 298.15 K, P: 101325 Pa
 flow (kg/hr): (l) Water    20
                   Ethanol  10

Reset and copy just water flow:

>>> s2.empty()
>>> s2.copy_flow(s1, IDs='Water')
>>> s2.show(flow='kg/hr')
MultiStream: s2
 phases: ('g', 'l'), T: 298.15 K, P: 101325 Pa
 flow (kg/hr): (l) Water  20

Reset and copy all flows except water:

>>> s2.empty()
>>> s2.copy_flow(s1, IDs='Water', exclude=True)
>>> s2.show(flow='kg/hr')
MultiStream: s2
 phases: ('g', 'l'), T: 298.15 K, P: 101325 Pa
 flow (kg/hr): (l) Ethanol  10

Cut and paste flows:

>>> s2.copy_flow(s1, remove=True)
>>> s2.show(flow='kg/hr')
MultiStream: s2
 phases: ('g', 'l'), T: 298.15 K, P: 101325 Pa
 flow (kg/hr): (l) Water    20
                   Ethanol  10
>>> s1.show()
Stream: s1
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow: 0
get_normalized_mol(IDs)[source]

Return normalized molar fractions of given chemicals. The sum of the result is always 1.

Parameters

IDs (tuple[str]) – IDs of chemicals to be normalized.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', 'Methanol'], cache=True)
>>> s1 = tmo.MultiStream('s1', l=[('Water', 20), ('Ethanol', 10), ('Methanol', 10)], units='kmol/hr')
>>> s1.get_normalized_mol(('Water', 'Ethanol'))
array([0.667, 0.333])
get_normalized_vol(IDs)[source]

Return normalized mass fractions of given chemicals. The sum of the result is always 1.

Parameters

IDs (tuple[str]) – IDs of chemicals to be normalized.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', 'Methanol'], cache=True)
>>> s1 = tmo.MultiStream('s1', l=[('Water', 20), ('Ethanol', 10), ('Methanol', 10)], units='m3/hr')
>>> s1.get_normalized_vol(('Water', 'Ethanol'))
array([0.667, 0.333])
get_normalized_mass(IDs)[source]

Return normalized mass fractions of given chemicals. The sum of the result is always 1.

Parameters

IDs (tuple[str]) – IDs of chemicals to be normalized.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', 'Methanol'], cache=True)
>>> s1 = tmo.MultiStream('s1', l=[('Water', 20), ('Ethanol', 10), ('Methanol', 10)], units='kg/hr')
>>> s1.get_normalized_mass(('Water', 'Ethanol'))
array([0.667, 0.333])
get_molar_composition(IDs)[source]

Return molar composition of given chemicals.

Parameters

IDs (tuple[str]) – IDs of chemicals.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', 'Methanol'], cache=True)
>>> s1 = tmo.MultiStream('s1', l=[('Water', 20), ('Ethanol', 10), ('Methanol', 10)], units='kmol/hr')
>>> s1.get_molar_composition(('Water', 'Ethanol'))
array([0.5 , 0.25])
get_mass_composition(IDs)[source]

Return mass composition of given chemicals.

Parameters

IDs (tuple[str]) – IDs of chemicals.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', 'Methanol'], cache=True)
>>> s1 = tmo.MultiStream('s1', l=[('Water', 20), ('Ethanol', 10), ('Methanol', 10)], units='kg/hr')
>>> s1.get_mass_composition(('Water', 'Ethanol'))
array([0.5 , 0.25])
get_volumetric_composition(IDs)[source]

Return volumetric composition of given chemicals.

Parameters

IDs (tuple[str]) – IDs of chemicals.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', 'Methanol'], cache=True)
>>> s1 = tmo.MultiStream('s1', l=[('Water', 20), ('Ethanol', 10), ('Methanol', 10)], units='m3/hr')
>>> s1.get_volumetric_composition(('Water', 'Ethanol'))
array([0.5 , 0.25])
get_concentration(phase, IDs)[source]

Return concentration of given chemicals in kmol/m3.

Parameters

IDs (tuple[str]) – IDs of chemicals.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', 'Methanol'], cache=True)
>>> s1 = tmo.MultiStream('s1', l=[('Water', 20), ('Ethanol', 10), ('Methanol', 10)], units='m3/hr')
>>> s1.get_concentration('l', ('Water', 'Ethanol'))
array([27.671,  4.266])
property vle

[VLE] An object that can perform vapor-liquid equilibrium on the stream.

property lle

[LLE] An object that can perform liquid-liquid equilibrium on the stream.

property sle

[SLE] An object that can perform solid-liquid equilibrium on the stream.

as_stream()[source]

Convert MultiStream to Stream.

property phase

Phase of stream.

print()[source]

Print in a format that you can use recreate the stream.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> s1 = tmo.MultiStream(ID='s1',T=298.15, P=101325,
...                      l=[('Water', 20), ('Ethanol', 10)], units='kg/hr')
>>> s1.print()
MultiStream(ID='s1', phases=('g', 'l'), T=298.15, P=101325, l=[('Water', 1.11), ('Ethanol', 0.2171)])

ThermalCondition

class thermosteam.ThermalCondition(T, P)[source]

Create a ThermalCondition object that contains temperature and pressure values.

Parameters
  • T (float) – Temperature [K].

  • P (float) – Pressure [Pa].

property T

[float] Temperature in Kelvin.

property P

[float] Pressure in Pascal.

in_equilibrium(other)[source]

Return whether thermal condition is in equilibrium with another (i. e. same temperature and pressure).

copy()[source]

Return a copy.

copy_like(other)[source]

Copy the specifications of another ThermalCondition object.

ThermoData

class thermosteam.ThermoData(data: dict)[source]

Create a ThermoData object for creating thermodynamic property packages and streams.

Parameters

data (dict) –

Examples

>>> import thermosteam as tmo
>>> data = {
... 'Chemicals': {
...     'Water': {},
...     'Ethanol': {},
...     'O2': {'phase': 'gas'},
...     'Cellulose': {
...          'search_db': False,
...          'phase': 'solid',
...          'formula': 'C6H10O5',
...          'Hf': -975708.8,
...          'default': True
...          },
...     'Octane': {}
...     },
... 'Synonyms': {
...     'Water': 'H2O',
...     'Ethanol': [
...         'CH3CH2OH',
...         'EthylAlcohol',
...         ]
...     },
... 'Streams': {
...     'process_water': {
...         'Water': 500,
...         'units': 'kg/hr',
...         'price': 0.00035,
...         },
...     'gasoline': {
...         'Octane': 400,
...         'units': 'kg/hr',
...         'price': 0.756,
...         },
...     }
... }
>>> thermo_data = tmo.ThermoData(data)
>>> chemicals = thermo_data.create_chemicals()
>>> chemicals
CompiledChemicals([Water, Ethanol, O2, Cellulose, Octane])
>>> tmo.settings.set_thermo(chemicals)
>>> thermo_data.create_streams()
[<Stream: process_water>, <Stream: gasoline>]

It is also possible to create a ThermoData object from json or yaml files For example, lets say we have a yaml file that looks like this:

# File name: example_chemicals.yaml
Chemicals:
  Water:
  Ethanol:
  O2:
    phase: gas
  Cellulose:
    search_db: False
    phase: solid
    formula: C6H10O5
    Hf: -975708.8
    default: True
  Octane:
Synonyms:
  Water: H2O
  Ethanol:
    - CH3CH2OH
    - EthylAlcohol

Then we could create the chemicals in just a few lines:

>>> # thermo_data = tmo.ThermoData.from_yaml('example_chemicals.yaml')
>>> # thermo_data.create_chemicals()
>>> # CompiledChemicals([Water, Ethanol, O2, Cellulose, Octane])
classmethod from_yaml(file)[source]

Create a ThermoData object from a yaml file given its directory.

classmethod from_json(file)[source]

Create a ThermoData object from a json file given its directory.

create_stream(ID)[source]

Create stream from data.

Parameters

ID=None (str) – ID of stream to create.

create_streams(IDs=None)[source]

Create streams from data.

Parameters

IDs=None (Iterable[str], optional) – IDs of streams to create. Defaults to all streams.

create_chemicals(IDs=None)[source]

Create chemicals from data.

Parameters

IDs=None (Iterable[str] or str, optional) – IDs of chemicals to create. Defaults to all chemicals.

set_synonyms(chemicals)[source]

Set synonyms to chemicals.

Parameters

chemicals (CompiledChemicals) –

functor

thermosteam.functor(f=None, var=None, units=None)[source]

Decorate a function of temperature, or both temperature and pressure to have an attribute, functor, that serves to create its functor counterpart.

Parameters
  • f (function(T, *args) or function(T, P, *args)) – Function that calculates a thermodynamic property based on temperature, or both temperature and pressure.

  • var (str, optional) – Name of variable returned (useful for bookkeeping).

  • units (dict, optional) – Units of measure for functor signature.

Returns

f – Same function, but with an attribute functor that can create its Functor counterpart.

Return type

function(T, *args) or function(T, P, *args)

Notes

The functor decorator checks the signature of the function to find the names of the parameters that should be stored as data.

Examples

Create a functor of temperature that returns the vapor pressure in Pascal:

>>> # Describe the return value with `var`.
>>> # Thermosteam's chemical units of measure are always assumed.
>>> import thermosteam as tmo
>>> @tmo.functor(var='Psat')
... def Antoine(T, a, b, c):
...     return 10.0**(a - b / (T + c))
>>> Antoine(T=373.15, a=10.116, b=1687.5, c=-42.98) # functional
101157.148
>>> f = Antoine.functor(a=10.116, b=1687.5, c=-42.98) # as functor object
>>> f.show()
Functor: Antoine(T, P=None) -> Psat [Pa]
 a: 10.116
 b: 1687.5
 c: -42.98
>>> f(T=373.15)
101157.148

All functors are saved in the functors module:

>>> tmo.functors.Antoine
<class 'thermosteam.functors.Antoine'>

exceptions

exception thermosteam.exceptions.UndefinedChemical(ID)[source]

Exception regarding undefined chemicals.

exception thermosteam.exceptions.UndefinedPhase(phase)[source]

Exception regarding undefined phases.

exception thermosteam.exceptions.DimensionError[source]

ValueError regarding wrong dimensions.

exception thermosteam.exceptions.InfeasibleRegion(region)[source]

Runtime error regarding infeasible processes.

exception thermosteam.exceptions.DomainError(msg, **data)[source]

ValueError regarding an attempt to evaluate a model out of its domain.

exception thermosteam.exceptions.InvalidMethod(method)[source]

ValueError regarding an attempt to evaluate an invalid method.

functional

thermosteam.functional.normalize(array, minimum=1e-16)[source]

Return a normalized array to a magnitude of 1. If magnitude is zero, all fractions will have equal value.

thermosteam.functional.mixing_simple(z, y)[source]

Return a weighted average of y given the weights, z.

Examples

>>> import numpy as np
>>> mixing_simple(np.array([0.1, 0.9]), np.array([0.01, 0.02]))
0.019000000000000003
thermosteam.functional.mixing_logarithmic(z, y)[source]

Return the logarithmic weighted average y given weights, z.

\[y = \sum_i z_i \cdot \log(y_i)\]

Notes

Does not work on negative values.

Examples

>>> import numpy as np
>>> mixing_logarithmic(np.array([0.1, 0.9]), np.array([0.01, 0.02]))
0.01866065983073615
thermosteam.functional.mu_to_nu(mu, rho)[source]

Return the kinematic viscosity (nu) given the dynamic viscosity (mu) and density (rho).

\[\nu = \frac{\mu}{\rho}\]

Examples

>>> mu_to_nu(0.000998, 998.)
1.0e-06
thermosteam.functional.V_to_rho(V, MW)[source]

Return the density (rho) in kg/m^3 given the molar volume (V) in m^3/mol and molecular weight (MW) in g/mol.

\[\rho = \frac{MW}{1000\cdot V}\]
Parameters
  • V (float) – Molar volume, [m^3/mol]

  • MW (float) – Molecular weight, [g/mol]

Returns

rho – Density, [kg/m^3]

Return type

float

Examples

>>> V_to_rho(0.000132, 86.18)
652.878...
thermosteam.functional.rho_to_V(rho, MW)[source]

Return the molar volume (V) in m^3/mol given the density (rho) in kg/m^3 and molecular weight (MW) in g/mol.

\[V = \left(\frac{1000 \rho}{MW}\right)^{-1}\]
Parameters
  • rho (float) – Density, [kg/m^3]

  • MW (float) – Molecular weight, [g/mol]

Returns

V – Molar volume, [m^3/mol]

Return type

float

Examples

>>> rho_to_V(652.9, 86.18)
0.0001319957...

equilibrium

Use the equilibrium subpackage to perform equilibrium calculations.

VLE

class thermosteam.equilibrium.VLE(imol=None, thermal_condition=None, thermo=None, bubble_point_cache=None, dew_point_cache=None)[source]

Create a VLE object that performs vapor-liquid equilibrium when called.

Parameters
  • imol=None (MaterialIndexer, optional) – Molar chemical phase data is stored here.

  • thermal_condition=None (ThermalCondition, optional) – Temperature and pressure results are stored here.

  • thermo=None (Thermo, optional) – Themodynamic property package for equilibrium calculations. Defaults to thermosteam.settings.get_thermo().

  • bubble_point_cache=None (thermosteam.utils.Cache, optional) – Cache to retrieve bubble point object.

  • dew_point_cache=None (thermosteam.utils.Cache, optional) – Cache to retrieve dew point object

Examples

First create a VLE object:

>>> from thermosteam import indexer, equilibrium, settings
>>> settings.set_thermo(['Water', 'Ethanol', 'Methanol', 'Propanol'], cache=True)
>>> imol = indexer.MolarFlowIndexer(
...             l=[('Water', 304), ('Ethanol', 30)],
...             g=[('Methanol', 40), ('Propanol', 1)])
>>> vle = equilibrium.VLE(imol)
>>> vle
VLE(imol=MolarFlowIndexer(
        g=[('Methanol', 40), ('Propanol', 1)],
        l=[('Water', 304), ('Ethanol', 30)]),
    thermal_condition=ThermalCondition(T=298.15, P=101325))

Equilibrium given vapor fraction and pressure:

>>> vle(V=0.5, P=101325)
>>> vle
VLE(imol=MolarFlowIndexer(
        g=[('Water', 126.7), ('Ethanol', 26.4), ('Methanol', 33.49), ('Propanol', 0.896)],
        l=[('Water', 177.3), ('Ethanol', 3.598), ('Methanol', 6.509), ('Propanol', 0.104)]),
    thermal_condition=ThermalCondition(T=363.88, P=101325))

Equilibrium given temperature and pressure:

>>> vle(T=363.88, P=101325)
>>> vle
VLE(imol=MolarFlowIndexer(
        g=[('Water', 126.7), ('Ethanol', 26.4), ('Methanol', 33.49), ('Propanol', 0.8959)],
        l=[('Water', 177.3), ('Ethanol', 3.601), ('Methanol', 6.513), ('Propanol', 0.1041)]),
    thermal_condition=ThermalCondition(T=363.88, P=101325))

Equilibrium given enthalpy and pressure:

>>> H = vle.thermo.mixture.xH(vle.imol, T=363.88, P=101325)
>>> vle(H=H, P=101325)
>>> vle
VLE(imol=MolarFlowIndexer(
        g=[('Water', 126.7), ('Ethanol', 26.4), ('Methanol', 33.49), ('Propanol', 0.8959)],
        l=[('Water', 177.3), ('Ethanol', 3.601), ('Methanol', 6.513), ('Propanol', 0.1041)]),
    thermal_condition=ThermalCondition(T=363.88, P=101325))

Equilibrium given vapor fraction and temperature:

>>> vle(V=0.5, T=363.88)
>>> vle
VLE(imol=MolarFlowIndexer(
        g=[('Water', 126.7), ('Ethanol', 26.4), ('Methanol', 33.49), ('Propanol', 0.896)],
        l=[('Water', 177.3), ('Ethanol', 3.598), ('Methanol', 6.509), ('Propanol', 0.104)]),
    thermal_condition=ThermalCondition(T=363.88, P=101317))

Equilibrium given enthalpy and temperature:

>>> vle(H=H, T=363.88)
>>> vle
VLE(imol=MolarFlowIndexer(
        g=[('Water', 126.7), ('Ethanol', 26.4), ('Methanol', 33.49), ('Propanol', 0.8959)],
        l=[('Water', 177.3), ('Ethanol', 3.601), ('Methanol', 6.513), ('Propanol', 0.1041)]),
    thermal_condition=ThermalCondition(T=363.88, P=101325))
__call__(P=None, H=None, T=None, V=None, x=None, y=None)[source]

Perform vapor-liquid equilibrium.

Parameters
  • P=None (float) – Operating pressure [Pa].

  • H=None (float) – Enthalpy [kJ/hr].

  • T=None (float) – Operating temperature [K].

  • V=None (float) – Molar vapor fraction.

  • x=None (float) – Molar composition of liquid (for binary mixtures).

  • y=None (float) – Molar composition of vapor (for binary mixtures).

Notes

You may only specify two of the following parameters: P, H, T, V, x, and y. Additionally, If x or y is specified, the other parameter must be either P or T (e.g., x and V is invalid).

property imol

[MaterialIndexer] Chemical phase data.

property thermal_condition

[ThermalCondition] Temperature and pressure data.

LLE

class thermosteam.equilibrium.LLE(imol=None, thermal_condition=None, thermo=None, composition_cache_tolerance=1e-06, temperature_cache_tolerance=1e-06)[source]

Create a LLE object that performs liquid-liquid equilibrium when called. Differential evolution is used to find the solution that globally minimizes the gibb’s free energy of both phases.

Parameters
  • imol=None (MaterialIndexer, optional) – Molar chemical phase data is stored here.

  • thermal_condition=None (ThermalCondition, optional) – The temperature and pressure used in calculations are stored here.

  • thermo=None (Thermo, optional) – Themodynamic property package for equilibrium calculations. Defaults to thermosteam.settings.get_thermo().

Examples

>>> from thermosteam import indexer, equilibrium, settings
>>> settings.set_thermo(['Water', 'Ethanol', 'Octane', 'Hexane'], cache=True)
>>> imol = indexer.MolarFlowIndexer(
...             l=[('Water', 304), ('Ethanol', 30)],
...             L=[('Octane', 40), ('Hexane', 1)])
>>> lle = equilibrium.LLE(imol)
>>> lle(T=360)
>>> lle
LLE(imol=MolarFlowIndexer(
        L=[('Water', 2.67), ('Ethanol', 2.28), ('Octane', 39.9), ('Hexane', 0.988)],
        l=[('Water', 301.), ('Ethanol', 27.7), ('Octane', 0.0788), ('Hexane', 0.0115)]),
    thermal_condition=ThermalCondition(T=360.00, P=101325))
__call__(T, P=None, top_chemical=None)[source]

Perform liquid-liquid equilibrium.

Parameters
  • T (float) – Operating temperature [K].

  • P (float, optional) – Operating pressure [Pa].

  • top_chemical (str, optional) – Identifier of chemical that will be favored in the “liquid” phase.

BubblePoint

class thermosteam.equilibrium.BubblePoint(chemicals=(), thermo=None)[source]

Create a BubblePoint object that returns bubble point values when called with a composition and either a temperture (T) or pressure (P).

Parameters
  • chemicals=() (Iterable[Chemical], optional) –

  • thermo=None (Thermo, optional) –

Examples

>>> import thermosteam as tmo
>>> chemicals = tmo.Chemicals(['Water', 'Ethanol'], cache=True)
>>> tmo.settings.set_thermo(chemicals)
>>> BP = tmo.equilibrium.BubblePoint(chemicals)
>>> molar_composition = (0.5, 0.5)
>>> # Solve bubble point at constant temperature
>>> bp = BP(z=molar_composition, T=355)
>>> bp
BubblePointValues(T=355.00, P=109755, IDs=('Water', 'Ethanol'), z=[0.5 0.5], y=[0.343 0.657])
>>> # Note that the result is a BubblePointValues object which contain all results as attibutes
>>> (bp.T, round(bp.P), bp.IDs, bp.z, bp.y)
(355, 109755, ('Water', 'Ethanol'), array([0.5, 0.5]), array([0.343, 0.657]))
>>> # Solve bubble point at constant pressure
>>> BP(z=molar_composition, P=101325)
BubblePointValues(T=352.95, P=101325, IDs=('Water', 'Ethanol'), z=[0.5 0.5], y=[0.342 0.658])
__call__(z, *, T=None, P=None)[source]

Call self as a function.

solve_Ty(z, P)[source]

Bubble point at given composition and pressure.

Parameters
  • z (ndarray) – Molar composotion.

  • P (float) – Pressure [Pa].

Returns

  • T (float) – Bubble point temperature [K].

  • y (ndarray) – Vapor phase molar composition.

Examples

>>> import thermosteam as tmo
>>> import numpy as np
>>> chemicals = tmo.Chemicals(['Water', 'Ethanol'], cache=True)
>>> tmo.settings.set_thermo(chemicals)
>>> BP = tmo.equilibrium.BubblePoint(chemicals)
>>> BP.solve_Ty(z=np.array([0.6, 0.4]), P=101325)
(353.7543, array([0.381, 0.619]))
solve_Py(z, T)[source]

Bubble point at given composition and temperature.

Parameters
  • z (ndarray) – Molar composotion.

  • T (float) – Temperature [K].

Returns

  • P (float) – Bubble point pressure [Pa].

  • y (ndarray) – Vapor phase molar composition.

Examples

>>> import thermosteam as tmo
>>> import numpy as np
>>> chemicals = tmo.Chemicals(['Water', 'Ethanol'], cache=True)
>>> tmo.settings.set_thermo(chemicals)
>>> BP = tmo.equilibrium.BubblePoint(chemicals)
>>> BP.solve_Py(z=np.array([0.703, 0.297]), T=352.28)
(91830.9798, array([0.419, 0.581]))

DewPoint

class thermosteam.equilibrium.DewPoint(chemicals=(), thermo=None)[source]

Create a DewPoint object that returns dew point values when called with a composition and either a temperture (T) or pressure (P).

Parameters
  • chemicals=None (Iterable[Chemical], optional) –

  • thermo=None (Thermo, optional) –

Examples

>>> import thermosteam as tmo
>>> chemicals = tmo.Chemicals(['Water', 'Ethanol'], cache=True)
>>> tmo.settings.set_thermo(chemicals)
>>> DP = tmo.equilibrium.DewPoint(chemicals)
>>> # Solve for dew point at constant temperautre
>>> molar_composition = (0.5, 0.5)
>>> dp = DP(z=molar_composition, T=355)
>>> dp
DewPointValues(T=355.00, P=91970, IDs=('Water', 'Ethanol'), z=[0.5 0.5], x=[0.851 0.149])
>>> # Note that the result is a DewPointValues object which contain all results as attibutes
>>> (dp.T, round(dp.P), dp.IDs, dp.z, dp.x)
(355, 91970, ('Water', 'Ethanol'), array([0.5, 0.5]), array([0.851, 0.149]))
>>> # Solve for dew point at constant pressure
>>> DP(z=molar_composition, P=2*101324)
DewPointValues(T=376.26, P=202648, IDs=('Water', 'Ethanol'), z=[0.5 0.5], x=[0.832 0.168])
__call__(z, *, T=None, P=None)[source]

Call self as a function.

solve_Tx(z, P)[source]

Dew point given composition and pressure.

Parameters
  • z (ndarray) – Molar composition.

  • P (float) – Pressure [Pa].

Returns

  • T (float) – Dew point temperature [K].

  • x (numpy.ndarray) – Liquid phase molar composition.

Examples

>>> import thermosteam as tmo
>>> import numpy as np
>>> chemicals = tmo.Chemicals(['Water', 'Ethanol'], cache=True)
>>> tmo.settings.set_thermo(chemicals)
>>> DP = tmo.equilibrium.DewPoint(chemicals)
>>> DP.solve_Tx(z=np.array([0.5, 0.5]), P=101325)
(357.451847, array([0.849, 0.151]))
solve_Px(z, T)[source]

Dew point given composition and temperature.

Parameters
  • z (ndarray) – Molar composition.

  • T (float) – Temperature (K).

Returns

  • P (float) – Dew point pressure (Pa).

  • x (ndarray) – Liquid phase molar composition.

Examples

>>> import thermosteam as tmo
>>> import numpy as np
>>> chemicals = tmo.Chemicals(['Water', 'Ethanol'], cache=True)
>>> tmo.settings.set_thermo(chemicals)
>>> DP = tmo.equilibrium.DewPoint(chemicals)
>>> DP.solve_Px(z=np.array([0.5, 0.5]), T=352.28)
(82444.29876, array([0.853, 0.147]))

activity_coefficients

class thermosteam.equilibrium.activity_coefficients.ActivityCoefficients[source]

Abstract class for the estimation of activity coefficients. Non-abstract subclasses should implement the following methods:

__init__(self, chemicals: Iterable[Chemicals]):

Should use pure component data from chemicals to setup future calculations of activity coefficients.

__call__(self, x: 1d array, T: float):

Should accept an array of liquid molar compositions x, and temperature T (in Kelvin), and return an array of activity coefficients. Note that the molar compositions must be in the same order as the chemicals defined when creating the ActivityCoefficients object.

property chemicals

tuple[Chemical] All chemicals involved in the calculation of activity coefficients.

class thermosteam.equilibrium.activity_coefficients.IdealActivityCoefficients(chemicals)[source]

Create an IdealActivityCoefficients object that estimates all activity coefficients to be 1 when called with a composition and a temperature (K).

Parameters

chemicals (Iterable[Chemical]) –

__call__(xs, T)[source]

Call self as a function.

class thermosteam.equilibrium.activity_coefficients.GroupActivityCoefficients(chemicals)[source]

Abstract class for the estimation of activity coefficients using group contribution methods.

Parameters

chemicals (Iterable[Chemical]) –

activity_coefficients(x, T)[source]

Return activity coefficients of chemicals with defined functional groups.

Parameters
  • x (array_like) – Molar fractions

  • T (float) – Temperature [K]

__call__(x, T)[source]

Return activity coefficients.

Parameters
  • x (array_like) – Molar fractions

  • T (float) – Temperature [K]

Notes

Activity coefficients of chemicals with missing groups are default to 1.

class thermosteam.equilibrium.activity_coefficients.DortmundActivityCoefficients(chemicals)[source]

Create a DortmundActivityCoefficients that estimates activity coefficients using the Dortmund UNIFAC group contribution method when called with a composition and a temperature (K).

Parameters

chemicals (Iterable[Chemical]) –

Examples

>>> import thermosteam as tmo
>>> chemicals = tmo.Chemicals(['Water', 'Ethanol'], cache=True)
>>> Gamma = tmo.equilibrium.DortmundActivityCoefficients(chemicals)
>>> composition = [0.5, 0.5]
>>> T = 350.
>>> Gamma(composition, T)
array([1.475, 1.242])
>>> chemicals = tmo.Chemicals(['Dodecane', 'Tridecane'], cache=True)
>>> Gamma = tmo.equilibrium.DortmundActivityCoefficients(chemicals)
>>> # Note how both hydrocarbons have similar lenghts and structure,
>>> # so activities should be very close
>>> Gamma([0.5, 0.5], 350.)
array([1., 1.])
>>> chemicals = tmo.Chemicals(['Water', 'O2'], cache=True)
>>> Gamma = tmo.equilibrium.DortmundActivityCoefficients(chemicals)
>>> # The following warning is issued because O2 does not have Dortmund groups
>>> # RuntimeWarning: O2 has no defined Dortmund groups;
>>> # functional group interactions are ignored
>>> Gamma([0.5, 0.5], 350.)
array([1., 1.])
class thermosteam.equilibrium.activity_coefficients.UNIFACActivityCoefficients(chemicals)[source]

Create a UNIFACActivityCoefficients that estimates activity coefficients using the UNIFAC group contribution method when called with a composition and a temperature (K).

Parameters

chemicals (Iterable[Chemical]) –

fugacity_coefficients

class thermosteam.equilibrium.fugacity_coefficients.FugacityCoefficients(chemicals)[source]

Abstract class for the estimation of fugacity coefficients. Non-abstract subclasses should implement the following methods:

__init__(self, chemicals: Iterable[Chemicals]):

Should use pure component data from chemicals to setup future calculations of fugacity coefficients.

__call__(self, y: 1d array, T: float, P:float):

Should accept an array of vapor molar compositions y, temperature T (in Kelvin), and pressure P (in Pascal), and return an array of fugacity coefficients. Note that the molar compositions must be in the same order as the chemicals defined when creating the FugacityCoefficients object.

class thermosteam.equilibrium.fugacity_coefficients.IdealFugacityCoefficients(chemicals)[source]

Create an IdealFugacityCoefficients object that estimates all fugacity coefficients to be 1 when called with composition, temperature (K), and pressure (Pa).

Parameters

chemicals (Iterable[Chemical]) –

property chemicals

tuple[Chemical] All chemicals involved in the calculation of fugacity coefficients.

__call__(y, T, P)[source]

Call self as a function.

poyinting_correction_factors

class thermosteam.equilibrium.poyinting_correction_factors.PoyintingCorrectionFactors(chemicals)[source]

Abstract class for the estimation of Poyinting correction factors. Non-abstract subclasses should implement the following methods:

__init__(self, chemicals: Iterable[Chemicals]):

Should use pure component data from chemicals to setup future calculations of Poyinting correction factors.

__call__(self, y: 1d array, T: float):

Should accept an array of vapor molar compositions y, and temperature T (in Kelvin), and return an array of Poyinting correction factors. Note that the molar compositions must be in the same order as the chemicals defined when creating the PoyintingCorrectionFactors object.

class thermosteam.equilibrium.poyinting_correction_factors.IdealPoyintingCorrectionFactors(chemicals)[source]

Create an IdealPoyintingCorrectionFactor object that estimates all poyinting correction factors to be 1 when called with composition and temperature (K).

Parameters

chemicals (Iterable[Chemical]) –

__call__(y, T)[source]

Call self as a function.

plot_equilibrium

thermosteam.equilibrium.plot_equilibrium.plot_vle_binary_phase_envelope(chemicals, T=None, P=None, color=None, thermo=None)[source]

Plot the binary phase envelope of two chemicals at a given temperature or pressure.

Parameters
  • chemicals (Iterable[Chemical or str]) – Chemicals in equilibrium.

  • T (float, optional) – Temperature [K].

  • P (float, optional) – Pressure [Pa].

  • color (str, optional) – Color of line plot.

  • thermo (Thermo, optional) – Thermodynamic property package.

Examples

>>> # from thermosteam import equilibrium as eq
>>> # eq.plot_vle_binary_phase_envelope(['Ethanol', 'Water'], P=101325)
_images/water_ethanol_binary_phase_envelope.png
thermosteam.equilibrium.plot_equilibrium.plot_lle_ternary_diagram(carrier, solvent, solute, T, P=101325, thermo=None, color=None, tie_line_points=None, tie_color=None, N_tie_lines=15, N_equilibrium_grids=15)[source]

Plot the ternary phase diagram of chemicals in liquid-liquid equilibrium.

Parameters
  • carrier (Chemical) –

  • solvent (Chemical) –

  • solute (Chemical) –

  • T (float, optional) – Temperature [K].

  • P (float, optional) – Pressure [Pa]. Defaults to 101325.

  • thermo (Thermo, optional) – Thermodynamic property package.

  • color (str, optional) – Color of equilibrium line.

  • tie_line_points (1d array(size=3), optional) – Additional composition points to create tie lines.

  • tie_color (str, optional) – Color of tie lines.

  • N_tie_lines (int, optional) – Number of tie lines. The default is 15.

  • N_equilibrium_grids (int, optional) – Number of solute composition points to plot. The default is 15.

Examples

>>> # from thermosteam import equilibrium as eq
>>> # eq.plot_lle_ternary_diagram('Water', 'Ethanol', 'EthylAcetate', T=298.15)
_images/water_ethanol_ethyl_acetate_ternary_diagram.png

reaction

Use the reaction subpackage to perform reactions on chemical arrays.

Reaction

class thermosteam.reaction.Reaction(reaction, reactant, X, chemicals=None, basis='mol', *, phases=None, check_mass_balance=False, check_atomic_balance=False, correct_atomic_balance=False, correct_mass_balance=False)[source]

Create a Reaction object which defines a stoichiometric reaction and conversion. A Reaction object is capable of reacting the material flow rates of a thermosteam.Stream object.

Parameters
  • reaction (dict or str) – A dictionary of stoichiometric coefficients or a stoichiometric equation written as: i1 R1 + … + in Rn -> j1 P1 + … + jm Pm

  • reactant (str) – ID of reactant compound.

  • X (float) – Reactant conversion (fraction).

  • chemicals=None (Chemicals, defaults to settings.chemicals.) – Chemicals corresponing to each entry in the stoichiometry array.

  • basis='mol' ({'mol', 'wt'}) – Basis of reaction.

  • check_mass_balance=False (bool) – Whether to check if mass is not created or destroyed.

  • correct_mass_balance=False (bool) – Whether to make sure mass is not created or destroyed by varying the reactant stoichiometric coefficient.

  • check_atomic_balance=False (bool) – Whether to check if stoichiometric balance by atoms cancel out.

  • correct_atomic_balance=False (bool) – Whether to correct the stoichiometry according to the atomic balance.

Notes

A reaction object can react either a stream or an array. When a stream is passed, it reacts either the mol or mass flow rate according to the basis of the reaction object. When an array is passed, the array elements are reacted regardless of what basis they are associated with.

Warning

Negative conversions and conversions above 1.0 are fair game (allowed), but may lead to odd/infeasible values when reacting a stream.

Examples

Electrolysis of water to molecular hydrogen and oxygen:

>>> import thermosteam as tmo
>>> chemicals = tmo.Chemicals(['H2O', 'H2', 'O2'], cache=True)
>>> tmo.settings.set_thermo(chemicals)
>>> reaction = tmo.Reaction('2H2O,l -> 2H2,g + O2,g', reactant='H2O', X=0.7)
>>> reaction.show() # Note that the default basis is by 'mol'
Reaction (by mol):
 stoichiometry             reactant    X[%]
 H2O,l -> H2,g + 0.5 O2,g  H2O,l       70.00
>>> reaction.reactant # The reactant is a tuple of phase and chemical ID
('l', 'H2O')
>>> feed = tmo.Stream('feed', H2O=100)
>>> feed.phases = ('g', 'l') # Gas and liquid phases must be available
>>> reaction(feed) # Call to run reaction on molar flow
>>> feed.show() # Notice how 70% of water was converted to product
MultiStream: feed
 phases: ('g', 'l'), T: 298.15 K, P: 101325 Pa
 flow (kmol/hr): (g) H2   70
                     O2   35
                 (l) H2O  30

Let’s change to a per ‘wt’ basis:

>>> reaction.basis = 'wt'
>>> reaction.show()
Reaction (by wt):
 stoichiometry                     reactant    X[%]
 H2O,l -> 0.112 H2,g + 0.888 O2,g  H2O,l       70.00

Although we changed the basis, the end result is the same if we pass a stream:

>>> feed = tmo.Stream('feed', H2O=100)
>>> feed.phases = ('g', 'l')
>>> reaction(feed) # Call to run reaction on mass flow
>>> feed.show() # Notice how 70% of water was converted to product
MultiStream: feed
 phases: ('g', 'l'), T: 298.15 K, P: 101325 Pa
 flow (kmol/hr): (g) H2   70
                     O2   35
                 (l) H2O  30

If chemicals phases are not specified, Reaction objects can react a any single phase Stream object (regardless of phase):

>>> reaction = tmo.Reaction('2H2O -> 2H2 + O2', reactant='H2O', X=0.7)
>>> feed = tmo.Stream('feed', H2O=100, phase='g')
>>> reaction(feed)
>>> feed.show()
Stream: feed
 phase: 'g', T: 298.15 K, P: 101325 Pa
 flow (kmol/hr): H2O  30
                 H2   70
                 O2   35

Alternatively, it’s also possible to react an array (instead of a stream):

>>> import numpy as np
>>> array = np.array([100., 0. , 0.])
>>> reaction(array)
>>> array
array([30., 70., 35.])

Reaction objects with the same reactant can be added together:

>>> tmo.settings.set_thermo(['Glucose', 'Ethanol', 'H2O', 'O2', 'CO2'])
>>> fermentation = tmo.Reaction('Glucose + O2 -> Ethanol + CO2', reactant='Glucose', X=0.7)
>>> combustion = tmo.Reaction('Glucose + O2 -> H2O + CO2', reactant='Glucose', X=0.2)
>>> mixed_reaction = fermentation + combustion
>>> mixed_reaction.show()
Reaction (by mol):
 stoichiometry                                    reactant    X[%]
 Glucose + O2 -> 0.778 Ethanol + 0.222 H2O + CO2  Glucose    90.00

Note how conversions are added and the stoichiometry rescales to a per reactant basis. Conversly, reaction objects may be substracted as well:

>>> combustion = mixed_reaction - fermentation
>>> combustion.show()
Reaction (by mol):
 stoichiometry                reactant    X[%]
 Glucose + O2 -> H2O + CO2  Glucose    20.00

When a Reaction object is multiplied (or divided), a new Reaction object with the conversion multiplied (or divided) is returned:

>>> combustion_multiplied = 2 * combustion
>>> combustion_multiplied.show()
Reaction (by mol):
 stoichiometry                reactant    X[%]
 Glucose + O2 -> H2O + CO2  Glucose    40.00
>>> fermentation_divided = fermentation / 2
>>> fermentation_divided.show()
Reaction (by mol):
 stoichiometry                  reactant    X[%]
 Glucose + O2 -> Ethanol + CO2  Glucose    35.00
copy(basis=None)[source]

Return copy of Reaction object.

force_reaction(material)[source]

React material ignoring feasibility checks.

product_yield(product, basis=None)[source]

Return yield of product per reactant.

adiabatic_reaction(stream)[source]

React stream material adiabatically, accounting for the change in enthalpy due to the heat of reaction.

Examples

Note how the stream temperature changed after the reaction due to the heat of reaction:

>>> import thermosteam as tmo
>>> chemicals = tmo.Chemicals(['H2', 'O2', 'H2O'], cache=True)
>>> tmo.settings.set_thermo(chemicals)
>>> reaction = tmo.Reaction('2H2 + O2 -> 2H2O', reactant='H2', X=0.7)
>>> s1 = tmo.Stream('s1', H2=10, O2=20, H2O=1000, T=373.15, phase='g')
>>> s2 = tmo.Stream('s2')
>>> s2.copy_like(s1) # s1 and s2 are the same
>>> s1.show() # Before reaction
Stream: s1
 phase: 'g', T: 373.15 K, P: 101325 Pa
 flow (kmol/hr): H2   10
                 O2   20
                 H2O  1e+03
>>> reaction.show()
Reaction (by mol):
 stoichiometry       reactant    X[%]
 H2 + 0.5 O2 -> H2O  H2         70.00
>>> reaction(s1)
>>> s1.show() # After isothermal reaction
Stream: s1
 phase: 'g', T: 373.15 K, P: 101325 Pa
 flow (kmol/hr): H2   3
                 O2   16.5
                 H2O  1.01e+03
>>> reaction.adiabatic_reaction(s2)
>>> s2.show() # After adiabatic reaction
Stream: s2
 phase: 'g', T: 421.6 K, P: 101325 Pa
 flow (kmol/hr): H2   3
                 O2   16.5
                 H2O  1.01e+03
property dH

Heat of reaction at given conversion. Units are in either J/mol-reactant or J/g-reactant; depending on basis.

Warning

Latents heats of vaporization are not accounted for; only heats of formation are included in this term. Note that heats of vaporization are temperature dependent and cannot be calculated using a Reaction object.

property X

[float] Reaction converion as a fraction.

property stoichiometry

[array] Stoichiometry coefficients.

property istoichiometry

[ChemicalIndexer] Stoichiometry coefficients.

property reactant

[str] Reactant associated to conversion.

property MWs

[1d array] Molecular weights of all chemicals [g/mol].

property basis

{‘mol’, ‘wt’} Basis of reaction

check_mass_balance(tol=0.001)[source]

Check that stoichiometric mass balance is correct.

check_atomic_balance(tol=0.001)[source]

Check that stoichiometric atomic balance is correct.

correct_mass_balance(variable=None)[source]

Make sure mass is not created or destroyed by varying the reactant stoichiometric coefficient.

correct_atomic_balance(constants=None)[source]

Correct stoichiometry coffecients to satisfy atomic balance.

Parameters

constants (str, optional) – IDs of chemicals for which stoichiometric coefficients are held constant.

Examples

Balance glucose fermentation to ethanol:

>>> import thermosteam as tmo
>>> from biorefineries import lipidcane as lc
>>> tmo.settings.set_thermo(lc.chemicals)
>>> fermentation = tmo.Reaction('Glucose + O2 -> Ethanol + CO2',
...                             reactant='Glucose',  X=0.9)
>>> fermentation.correct_atomic_balance()
>>> fermentation.show()
Reaction (by mol):
 stoichiometry                 reactant    X[%]
 Glucose -> 2 Ethanol + 2 CO2  Glucose    90.00

Balance methane combustion:

>>> combustion = tmo.Reaction('CH4 + O2 -> Water + CO2',
...                           reactant='CH4', X=1)
>>> combustion.correct_atomic_balance()
>>> combustion.show()
Reaction (by mol):
 stoichiometry                reactant    X[%]
 2 O2 + CH4 -> 2 Water + CO2  CH4       100.00

Balance electrolysis of water (with chemical phases specified):

>>> electrolysis = tmo.Reaction('H2O,l -> H2,g + O2,g',
...                             chemicals=tmo.Chemicals(['H2O', 'H2', 'O2']),
...                             reactant='H2O', X=1)
>>> electrolysis.correct_atomic_balance()
>>> electrolysis.show()
Reaction (by mol):
 stoichiometry             reactant    X[%]
 H2O,l -> H2,g + 0.5 O2,g  H2O,l     100.00

Note that if the reaction is underspecified, there are infinite ways to balance the reaction and a runtime error is raised:

>>> rxn_underspecified = tmo.Reaction('CH4 + Glucose + O2 -> Water + CO2',
...                                   reactant='CH4', X=1)
>>> rxn_underspecified.correct_atomic_balance()
Traceback (most recent call last):
RuntimeError: reaction stoichiometry is underspecified; pass the
`constants` argument to the `<Reaction>.correct_atomic_balance` method
to specify which stoichiometric coefficients to hold constant

Chemical coefficients can be held constant to prevent this error:

>>> rxn_underspecified = tmo.Reaction('CH4 + Glucose + O2 -> Water + CO2',
...                                   reactant='CH4', X=1)
>>> rxn_underspecified.correct_atomic_balance(['Glucose', 'CH4'])
>>> rxn_underspecified.show()
Reaction (by mol):
 stoichiometry                            reactant    X[%]
 Glucose + 8 O2 + CH4 -> 8 Water + 7 CO2  CH4       100.00

ParallelReaction

class thermosteam.reaction.ParallelReaction(reactions)[source]

Create a ParallelReaction object from Reaction objects. When called, it returns the change in material due to all parallel reactions.

Parameters

reactions (Iterable[Reaction]) –

Examples

Run two reactions in parallel:

>>> import thermosteam as tmo
>>> chemicals = tmo.Chemicals(['H2', 'Ethanol', 'CH4', 'O2', 'CO2', 'H2O'], cache=True)
>>> tmo.settings.set_thermo(chemicals)
>>> kwargs = dict(phases='lg', correct_atomic_balance=True)
>>> reaction = tmo.ParallelReaction([
...    #            Reaction definition                    Reactant             Conversion
...    tmo.Reaction('H2,g + O2,g -> 2H2O,g',               reactant='H2',       X=0.7, **kwargs),
...    tmo.Reaction('Ethanol,l + O2,g -> CO2,g + 2H2O,g',  reactant='Ethanol',  X=0.1, **kwargs)
... ])
>>> reaction.reactants # Note that reactants are tuples of phase and ID pairs.
(('g', 'H2'), ('l', 'Ethanol'))
>>> reaction.show()
ParallelReaction (by mol):
index  stoichiometry                            reactant     X[%]
[0]    H2,g + 0.5 O2,g -> H2O,g                 H2,g        70.00
[1]    3 O2,g + Ethanol,l -> 2 CO2,g + 3 H2O,g  Ethanol,l   10.00
>>> s1 = tmo.MultiStream('s1', T=373.15,
...                      l=[('Ethanol', 10)],
...                      g=[('H2', 10), ('CH4', 5), ('O2', 100), ('H2O', 10)])
>>> s1.show() # Before reaction
MultiStream: s1
 phases: ('g', 'l'), T: 373.15 K, P: 101325 Pa
 flow (kmol/hr): (g) H2       10
                     CH4      5
                     O2       100
                     H2O      10
                 (l) Ethanol  10
>>> reaction(s1)
>>> s1.show() # After isothermal reaction
MultiStream: s1
 phases: ('g', 'l'), T: 373.15 K, P: 101325 Pa
 flow (kmol/hr): (g) H2       3
                     CH4      5
                     O2       93.5
                     CO2      2
                     H2O      20
                 (l) Ethanol  9

Reaction items are accessible:

>>> reaction[0].show()
ReactionItem (by mol):
 stoichiometry             reactant    X[%]
 H2,g + 0.5 O2,g -> H2O,g  H2,g       70.00

Note that changing the conversion of a reaction item changes the conversion of its parent reaction set:

>>> reaction[0].X = 0.5
>>> reaction.show()
ParallelReaction (by mol):
index  stoichiometry                            reactant     X[%]
[0]    H2,g + 0.5 O2,g -> H2O,g                 H2,g        50.00
[1]    3 O2,g + Ethanol,l -> 2 CO2,g + 3 H2O,g  Ethanol,l   10.00

Reactions subsets can be made as well:

>>> reaction[:1].show()
ParallelReaction (by mol):
index  stoichiometry             reactant    X[%]
[0]    H2,g + 0.5 O2,g -> H2O,g  H2,g       50.00

Get net reaction conversion of reactants as a material indexer:

>>> mi = reaction.X_net
>>> mi.show()
MaterialIndexer:
 (g) H2        0.5
 (l) Ethanol   0.1
>>> mi['g', 'H2']
0.5

If no phases are specified for a reaction set, the X_net property returns a ChemicalIndexer:

>>> kwargs = dict(correct_atomic_balance=True)
>>> reaction = tmo.ParallelReaction([
...    #            Reaction definition            Reactant             Conversion
...    tmo.Reaction('H2 + O2 -> 2H2O',             reactant='H2',       X=0.7, **kwargs),
...    tmo.Reaction('Ethanol + O2 -> CO2 + 2H2O',  reactant='Ethanol',  X=0.1, **kwargs)
... ])
>>> ci = reaction.X_net
>>> ci.show()
ChemicalIndexer:
 H2       0.7
 Ethanol  0.1
>>> ci['H2']
0.7
force_reaction(material)[source]

React material ignoring feasibility checks.

adiabatic_reaction(stream)[source]

React stream material adiabatically, accounting for the change in enthalpy due to the heat of reaction.

Examples

Note how the stream temperature changed after the reaction due to the heat of reaction:

>>> import thermosteam as tmo
>>> chemicals = tmo.Chemicals(['H2', 'CH4', 'O2', 'CO2', 'H2O'], cache=True)
>>> tmo.settings.set_thermo(chemicals)
>>> reaction = tmo.ParallelReaction([
...    #            Reaction definition          Reactant    Conversion
...    tmo.Reaction('2H2 + O2 -> 2H2O',        reactant='H2',  X=0.7),
...    tmo.Reaction('CH4 + O2 -> CO2 + 2H2O',  reactant='CH4', X=0.1)
... ])
>>> s1 = tmo.Stream('s1', H2=10, CH4=5, O2=100, H2O=100, T=373.15, phase='g')
>>> s2 = tmo.Stream('s2')
>>> s1.show() # Before reaction
Stream: s1
 phase: 'g', T: 373.15 K, P: 101325 Pa
 flow (kmol/hr): H2   10
                 CH4  5
                 O2   100
                 H2O  100
>>> reaction.show()
ParallelReaction (by mol):
index  stoichiometry            reactant    X[%]
[0]    H2 + 0.5 O2 -> H2O       H2         70.00
[1]    CH4 + O2 -> CO2 + 2 H2O  CH4        10.00
>>> reaction.adiabatic_reaction(s1)
>>> s1.show() # After adiabatic reaction
Stream: s1
 phase: 'g', T: 666.21 K, P: 101325 Pa
 flow (kmol/hr): H2   3
                 CH4  4.5
                 O2   96
                 CO2  0.5
                 H2O  108
reduce()[source]

Return a new Parallel reaction object that combines reaction with the same reactant together, reducing the number of reactions.

property X_net

[ChemicalIndexer] Net reaction conversion of reactants.

SeriesReaction

class thermosteam.reaction.SeriesReaction(reactions)[source]

Create a ParallelReaction object from Reaction objects. When called, it returns the change in material due to all reactions in series.

Parameters

reactions (Iterable[Reaction]) –

force_reaction(material)[source]

React material ignoring feasibility checks.

adiabatic_reaction(stream)[source]

React stream material adiabatically, accounting for the change in enthalpy due to the heat of reaction.

Examples

Note how the stream temperature changed after the reaction due to the heat of reaction:

>>> import thermosteam as tmo
>>> chemicals = tmo.Chemicals(['CH4', 'CO','O2', 'CO2', 'H2O'], cache=True)
>>> tmo.settings.set_thermo(chemicals)
>>> reaction = tmo.SeriesReaction([
...     #            Reaction definition                 Reactant       Conversion
...     tmo.Reaction('2CH4 + 3O2 -> 2CO + 4H2O',       reactant='CH4',    X=0.7),
...     tmo.Reaction('2CO + O2 -> 2CO2',               reactant='CO',     X=0.1)
...     ])
>>> s1 = tmo.Stream('s1', CH4=5, O2=100, H2O=100, T=373.15, phase='g')
>>> s1.show() # Before reaction
Stream: s1
 phase: 'g', T: 373.15 K, P: 101325 Pa
 flow (kmol/hr): CH4  5
                 O2   100
                 H2O  100
>>> reaction.show()
SeriesReaction (by mol):
index  stoichiometry               reactant    X[%]
[0]    CH4 + 1.5 O2 -> CO + 2 H2O  CH4        70.00
[1]    CO + 0.5 O2 -> CO2          CO         10.00
>>> reaction.adiabatic_reaction(s1)
>>> s1.show() # After adiabatic reaction
Stream: s1
 phase: 'g', T: 649.84 K, P: 101325 Pa
 flow (kmol/hr): CH4  1.5
                 CO   3.15
                 O2   94.6
                 CO2  0.35
                 H2O  107
property X_net

[ChemicalIndexer] Net reaction conversion of reactants.

indexer

Use the indexer subpackage for fast indexing of chemical arrays.

Indexer

class thermosteam.indexer.Indexer[source]

Abstract class for fast indexing.

ChemicalIndexer

class thermosteam.indexer.ChemicalIndexer(phase=LockedPhase(None), units=None, chemicals=None, **ID_data)[source]

Create a ChemicalIndexer that can index a single-phase, 1d-array given chemical IDs.

Parameters
  • phase ([str or PhaseContainer] {'s', 'l', 'g', 'S', 'L', 'G'}) – Phase of data.

  • units (str) – Units of measure of input data.

  • chemicals (Chemicals) – Required to define the chemicals that are present.

  • **ID_data (float) – ID-value pairs

Notes

A ChemicalIndexer does not have any units defined. To use units of measure, use the ChemicalMolarIndexer, ChemicalMassIndexer, or ChemicalVolumetricIndexer.

show(N=None)[source]

Print all specifications.

Parameters

N (int, optional) – Number of compounds to display.

MaterialIndexer

class thermosteam.indexer.MaterialIndexer(phases=None, units=None, chemicals=None, **phase_data)[source]

Create a MaterialIndexer that can index a multi-phase, 2d-array given the phase and chemical IDs.

Parameters
  • phases (tuple['s', 'l', 'g', 'S', 'L', 'G']) – Phases of data rows.

  • units (str) – Units of measure of input data.

  • chemicals (Chemicals) – Required to define the chemicals that are present.

  • **phase_data (tuple[str, float]) – phase-(ID, value) pairs

Notes

A MaterialIndexer does not have any units defined. To use units of measure, use the MolarIndexer, MassIndexer, or VolumetricIndexer.

iter_composition()[source]

Iterate over phase-composition pairs.

show(N=None)

Print all specifications.

Parameters

N (int, optional) – Number of compounds to display.

ChemicalMolarFlowIndexer

class thermosteam.indexer.ChemicalMolarFlowIndexer(phase=LockedPhase(None), units=None, chemicals=None, **ID_data)

Create a ChemicalMolarFlowIndexer that can index a single-phase, 1d-array given chemical IDs.

Parameters
  • phase ([str or PhaseContainer] {'s', 'l', 'g', 'S', 'L', 'G'}) – Phase of data.

  • units (str) – Units of measure of input data.

  • chemicals (Chemicals) – Required to define the chemicals that are present.

  • **ID_data (float) – ID-value pairs

by_mass()

Return a ChemicalMassFlowIndexer that references this object’s molar data.

by_volume(TP)

Return a ChemicalVolumetricFlowIndexer that references this object’s molar data.

Parameters

TP (ThermalCondition) –

ChemicalMassFlowIndexer

class thermosteam.indexer.ChemicalMassFlowIndexer(phase=LockedPhase(None), units=None, chemicals=None, **ID_data)

Create a ChemicalMassFlowIndexer that can index a single-phase, 1d-array given chemical IDs.

Parameters
  • phase ([str or PhaseContainer] {'s', 'l', 'g', 'S', 'L', 'G'}) – Phase of data.

  • units (str) – Units of measure of input data.

  • chemicals (Chemicals) – Required to define the chemicals that are present.

  • **ID_data (float) – ID-value pairs

ChemicalVolumetricFlowIndexer

class thermosteam.indexer.ChemicalVolumetricFlowIndexer(phase=LockedPhase(None), units=None, chemicals=None, **ID_data)

Create a ChemicalVolumetricFlowIndexer that can index a single-phase, 1d-array given chemical IDs.

Parameters
  • phase ([str or PhaseContainer] {'s', 'l', 'g', 'S', 'L', 'G'}) – Phase of data.

  • units (str) – Units of measure of input data.

  • chemicals (Chemicals) – Required to define the chemicals that are present.

  • **ID_data (float) – ID-value pairs

MolarFlowIndexer

class thermosteam.indexer.MolarFlowIndexer(phases=None, units=None, chemicals=None, **phase_data)

Create a MolarFlowIndexer that can index a multi-phase, 2d-array given the phase and chemical IDs.

Parameters
  • phases (tuple['s', 'l', 'g', 'S', 'L', 'G']) – Phases of data rows.

  • units (str) – Units of measure of input data.

  • chemicals (Chemicals) – Required to define the chemicals that are present.

  • **phase_data (tuple[str, float]) – phase-(ID, value) pairs

by_mass()

Return a MassFlowIndexer that references this object’s molar data.

by_volume(TP)

Return a VolumetricFlowIndexer that references this object’s molar data.

Parameters

TP (ThermalCondition) –

MassFlowIndexer

class thermosteam.indexer.MassFlowIndexer(phases=None, units=None, chemicals=None, **phase_data)

Create a MassFlowIndexer that can index a multi-phase, 2d-array given the phase and chemical IDs.

Parameters
  • phases (tuple['s', 'l', 'g', 'S', 'L', 'G']) – Phases of data rows.

  • units (str) – Units of measure of input data.

  • chemicals (Chemicals) – Required to define the chemicals that are present.

  • **phase_data (tuple[str, float]) – phase-(ID, value) pairs

VolumetricFlowIndexer

class thermosteam.indexer.VolumetricFlowIndexer(phases=None, units=None, chemicals=None, **phase_data)

Create a VolumetricFlowIndexer that can index a multi-phase, 2d-array given the phase and chemical IDs.

Parameters
  • phases (tuple['s', 'l', 'g', 'S', 'L', 'G']) – Phases of data rows.

  • units (str) – Units of measure of input data.

  • chemicals (Chemicals) – Required to define the chemicals that are present.

  • **phase_data (tuple[str, float]) – phase-(ID, value) pairs

mixture

mixture_builders

All Mixture object builders.

thermosteam.mixture.mixture_builders.ideal_mixture(chemicals, include_excess_energies=False)[source]

Create a Mixture object that computes mixture properties using ideal mixing rules.

Parameters
  • chemicals (Chemicals) – For retrieving pure component chemical data.

  • include_excess_energies=False (bool) – Whether to include excess energies in enthalpy and entropy calculations.

Examples

>>> from thermosteam import Chemicals
>>> from thermosteam.mixture import ideal_mixture
>>> chemicals = Chemicals(['Water', 'Ethanol'])
>>> ideal_mixture_model = ideal_mixture(chemicals)
>>> ideal_mixture_model.Hvap([0.2, 0.8], 350)
39601.089191849824

IdealMixtureModel

class thermosteam.mixture.IdealMixtureModel(models, var)[source]

Create an IdealMixtureModel object that calculates mixture properties based on the molar weighted sum of pure chemical properties.

Parameters
  • models (Iterable[function(T, P)]) – Chemical property functions of temperature and pressure.

  • var (str) – Description of thermodynamic variable returned.

Notes

Mixture objects can contain IdealMixtureModel objects to establish as mixture model for thermodynamic properties.

Examples

>>> from thermosteam.mixture import IdealMixtureModel
>>> from thermosteam import Chemicals
>>> chemicals = Chemicals(['Water', 'Ethanol'])
>>> models = [i.Psat for i in chemicals]
>>> mixture_model = IdealMixtureModel(models, 'Psat')
>>> mixture_model
<IdealMixtureModel(mol, T, P=None) -> Psat [Pa]>
>>> mixture_model([0.2, 0.8], 350)
84902.48775

Mixture

class thermosteam.mixture.Mixture(rule, Cn, H, S, H_excess, S_excess, mu, V, kappa, Hvap, sigma, epsilon, include_excess_energies=False)[source]

Create an Mixture object for estimating mixture properties.

Parameters
  • rule (str) – Description of mixing rules used.

  • Cn (function(phase, mol, T)) – Molar heat capacity mixture model [J/mol/K].

  • H (function(phase, mol, T)) – Enthalpy mixture model [J/mol].

  • S (function(phase, mol, T, P)) – Entropy mixture model [J/mol].

  • H_excess (function(phase, mol, T, P)) – Excess enthalpy mixture model [J/mol].

  • S_excess (function(phase, mol, T, P)) – Excess entropy mixture model [J/mol].

  • mu (function(phase, mol, T, P)) – Dynamic viscosity mixture model [Pa*s].

  • V (function(phase, mol, T, P)) – Molar volume mixture model [m^3/mol].

  • kappa (function(phase, mol, T, P)) – Thermal conductivity mixture model [W/m/K].

  • Hvap (function(mol, T)) – Heat of vaporization mixture model [J/mol]

  • sigma (function(mol, T, P)) – Surface tension mixture model [N/m].

  • epsilon (function(mol, T, P)) – Relative permitivity mixture model [-]

  • include_excess_energies=False (bool) – Whether to include excess energies in enthalpy and entropy calculations.

Notes

Although the mixture models are on a molar basis, this is only if the molar data is normalized before the calculation (i.e. the mol parameter is normalized before being passed to the model).

rule

Description of mixing rules used.

Type

str

include_excess_energies

Whether to include excess energies in enthalpy and entropy calculations.

Type

bool

Cn(phase, mol, T)

Mixture molar heat capacity [J/mol/K].

mu(phase, mol, T, P)

Mixture dynamic viscosity [Pa*s].

V(phase, mol, T, P)

Mixture molar volume [m^3/mol].

kappa(phase, mol, T, P)

Mixture thermal conductivity [W/m/K].

Hvap(mol, T, P)

Mixture heat of vaporization [J/mol]

sigma(mol, T, P)

Mixture surface tension [N/m].

epsilon(mol, T, P)

Mixture relative permitivity [-].

H(phase, mol, T, P)[source]

Return enthalpy [J/mol].

S(phase, mol, T, P)[source]

Return entropy in [J/mol].

solve_T(phase, mol, H, T_guess, P)[source]

Solve for temperature in Kelvin.

xsolve_T(phase_mol, H, T_guess, P)[source]

Solve for temperature in Kelvin.

xCn(phase_mol, T)[source]

Multi-phase mixture heat capacity [J/mol/K].

xH(phase_mol, T, P)[source]

Multi-phase mixture enthalpy [J/mol].

xS(phase_mol, T, P)[source]

Multi-phase mixture entropy [J/mol].

xV(phase_mol, T, P)[source]

Multi-phase mixture molar volume [mol/m^3].

xmu(phase_mol, T, P)[source]

Multi-phase mixture hydrolic [Pa*s].

xkappa(phase_mol, T, P)[source]

Multi-phase mixture thermal conductivity [W/m/K].

separations

This module contains functions for modeling separations in unit operations.

thermosteam.separations.split(feed, top, bottom, split)[source]

Run splitter mass and energy balance with mixing all input streams.

Parameters
  • feed (Stream) – Inlet fluid.

  • top (Stream) – Top inlet fluid.

  • bottom (Stream) – Bottom inlet fluid

  • split (array_like) – Component-wise split of feed to the top stream.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> feed = tmo.Stream(Water=35, Ethanol=10)
>>> split = 0.8
>>> effluent_a = tmo.Stream('effluent_a')
>>> effluent_b = tmo.Stream('effluent_b')
>>> tmo.separations.split(feed, effluent_a, effluent_b, split)
>>> effluent_a.show()
Stream: effluent_a
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kmol/hr): Water    28
                 Ethanol  8
>>> effluent_b.show()
Stream: effluent_b
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kmol/hr): Water    7
                 Ethanol  2
thermosteam.separations.mix_and_split(ins, top, bottom, split)[source]

Run splitter mass and energy balance with mixing all input streams.

Parameters
  • ins (Iterable[Stream]) – All inlet fluids.

  • top (Stream) – Top inlet fluid.

  • bottom (Stream) – Bottom inlet fluid

  • split (array_like) – Component-wise split of feed to the top stream.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> feed_a = tmo.Stream(Water=20, Ethanol=5)
>>> feed_b = tmo.Stream(Water=15, Ethanol=5)
>>> split = 0.8
>>> effluent_a = tmo.Stream('effluent_a')
>>> effluent_b = tmo.Stream('effluent_b')
>>> tmo.separations.mix_and_split([feed_a, feed_b], effluent_a, effluent_b, split)
>>> effluent_a.show()
Stream: effluent_a
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kmol/hr): Water    28
                 Ethanol  8
>>> effluent_b.show()
Stream: effluent_b
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kmol/hr): Water    7
                 Ethanol  2
thermosteam.separations.adjust_moisture_content(retentate, permeate, moisture_content)[source]

Remove water from permate to adjust retentate moisture content.

Parameters
  • retentate (Stream) –

  • permeate (Stream) –

  • moisture_content (float) – Fraction of water in retentate.

Examples

>>> import thermosteam as tmo
>>> Solids = tmo.Chemical('Solids', default=True, search_db=False, phase='s')
>>> tmo.settings.set_thermo(['Water', Solids])
>>> retentate = tmo.Stream('retentate', Solids=20, units='kg/hr')
>>> permeate = tmo.Stream('permeate', Water=50, Solids=0.1, units='kg/hr')
>>> moisture_content = 0.5
>>> tmo.separations.adjust_moisture_content(retentate, permeate, moisture_content)
>>> retentate.show(flow='kg/hr')
Stream: retentate
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): Water   20
               Solids  20
>>> permeate.show(flow='kg/hr')
Stream: permeate
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): Water   30
               Solids  0.1

Note that if not enough water is available, an InfeasibleRegion error is raised:

>>> retentate.imol['Water'] = permeate.imol['Water'] = 0
>>> tmo.separations.adjust_moisture_content(retentate, permeate, moisture_content)
Traceback (most recent call last):
InfeasibleRegion: not enough water; permeate moisture content is infeasible
thermosteam.separations.mix_and_split_with_moisture_content(ins, retentate, permeate, split, moisture_content)[source]

Run splitter mass and energy balance with mixing all input streams and and ensuring retentate moisture content.

Parameters
  • ins (Iterable[Stream]) – Inlet fluids with solids.

  • retentate (Stream) –

  • permeate (Stream) –

  • split (array_like) – Component splits to the retentate.

  • moisture_content (float) – Fraction of water in retentate.

Examples

>>> import thermosteam as tmo
>>> Solids = tmo.Chemical('Solids', default=True, search_db=False, phase='s')
>>> tmo.settings.set_thermo(['Water', Solids])
>>> feed = tmo.Stream('feed', Water=100, Solids=10, units='kg/hr')
>>> wash_water = tmo.Stream('wash_water', Water=10, units='kg/hr')
>>> retentate = tmo.Stream('retentate')
>>> permeate = tmo.Stream('permeate')
>>> split = [0., 1.]
>>> moisture_content = 0.5
>>> tmo.separations.mix_and_split_with_moisture_content(
...     [feed, wash_water], retentate, permeate, split, moisture_content
... )
>>> retentate.show(flow='kg/hr')
Stream: retentate
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): Water   10
               Solids  10
>>> permeate.show(flow='kg/hr')
Stream: permeate
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): Water  100
thermosteam.separations.partition_coefficients(IDs, top, bottom)[source]

Return partition coefficients given streams in equilibrium.

Parameters
  • top (Stream) – Vapor fluid.

  • bottom (Stream) – Liquid fluid.

  • IDs (tuple[str]) – IDs of chemicals in equilibrium.

Returns

K – Patition coefficients in mol fraction in top stream over mol fraction in bottom stream.

Return type

1d array

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', tmo.Chemical('O2', phase='g')], cache=True)
>>> s = tmo.Stream('s', Water=20, Ethanol=20, O2=0.1)
>>> s.vle(V=0.5, P=101325)
>>> tmo.separations.partition_coefficients(('Water', 'Ethanol'), s['g'], s['l'])
array([0.629, 1.59 ])
thermosteam.separations.vle_partition_coefficients(top, bottom)[source]

Return VLE partition coefficients given vapor and liquid streams in equilibrium.

Parameters
  • top (Stream) – Vapor fluid.

  • bottom (Stream) – Liquid fluid.

Returns

  • IDs (tuple[str]) – IDs for chemicals in vapor-liquid equilibrium.

  • K (1d array) – Patition coefficients in mol fraction in vapor over mol fraction in liquid.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', tmo.Chemical('O2', phase='g')], cache=True)
>>> s = tmo.Stream('s', Water=20, Ethanol=20, O2=0.1)
>>> s.vle(V=0.5, P=101325)
>>> IDs, K = tmo.separations.vle_partition_coefficients(s['g'], s['l'])
>>> IDs
('Water', 'Ethanol')
>>> K
array([0.629, 1.59 ])
thermosteam.separations.lle_partition_coefficients(top, bottom)[source]

Return LLE partition coefficients given two liquid streams in equilibrium.

Parameters
  • top (Stream) – Liquid fluid.

  • bottom (Stream) – Other liquid fluid.

Returns

  • IDs (tuple[str]) – IDs for chemicals in liquid-liquid equilibrium.

  • K (1d array) – Patition coefficients in mol fraction in top liquid over mol fraction in bottom liquid.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', 'Octanol'], cache=True)
>>> s = tmo.Stream('s', Water=20, Octanol=20, Ethanol=1)
>>> s.lle(T=298.15, P=101325)
>>> IDs, K = tmo.separations.lle_partition_coefficients(s['l'], s['L'])
>>> IDs
('Water', 'Ethanol', 'Octanol')
>>> K
array([6.82e+00, 2.38e-01, 3.00e-04])
thermosteam.separations.partition(feed, top, bottom, IDs, K, phi=None)[source]

Run equilibrium of feed to top and bottom streams given partition coeffiecients and return the phase fraction.

Parameters
  • feed (Stream) – Mixed feed.

  • top (Stream) – Top fluid.

  • bottom (Stream) – Bottom fluid.

  • IDs (tuple[str]) – IDs of chemicals in equilibrium.

  • K (1d array) – Partition coefficeints corresponding to IDs.

  • phi (float, optional) – Guess phase fraction in top phase.

Returns

phi – Phase fraction in top phase.

Return type

float

Notes

Chemicals not in equilibrium end up in the top phase.

Examples

>>> import numpy as np
>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', tmo.Chemical('O2', phase='g')], cache=True)
>>> IDs = ('Water', 'Ethanol')
>>> K = np.array([0.629, 1.59])
>>> feed = tmo.Stream('feed', Water=20, Ethanol=20, O2=0.1)
>>> top = tmo.Stream('top')
>>> bottom = tmo.Stream('bottom')
>>> tmo.separations.partition(feed, top, bottom, IDs, K)
0.5002512677600628
>>> top.show()
Stream: top
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kmol/hr): Water    7.73
                 Ethanol  12.3
                 O2       0.1
>>> bottom.show()
Stream: bottom
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kmol/hr): Water    12.3
                 Ethanol  7.72
thermosteam.separations.lle(feed, top, bottom, top_chemical=None, efficiency=1.0, multi_stream=None)[source]

Run LLE mass and energy balance.

Parameters
  • feed (Stream) – Mixed feed.

  • top (Stream) – Top fluid.

  • bottom (Stream) – Bottom fluid.

  • top_chemical (str, optional) – Identifier of chemical that will be favored in the top fluid.

  • efficiency=1. (float,) – Fraction of feed in liquid-liquid equilibrium. The rest of the feed is divided equally between phases

  • multi_stream (MultiStream, optional) – Data from feed is passed to this stream to perform liquid-liquid equilibrium.

Examples

Perform liquid-liquid equilibrium around water and octanol and split the phases:

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', 'Octanol'], cache=True)
>>> feed = tmo.Stream('feed', Water=20, Octanol=20, Ethanol=1)
>>> top = tmo.Stream('top')
>>> bottom = tmo.Stream('bottom')
>>> tmo.separations.lle(feed, top, bottom)
>>> top.show()
Stream: top
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kmol/hr): Water    3.55
                 Ethanol  0.861
                 Octanol  20
>>> bottom.show()
Stream: bottom
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kmol/hr): Water    16.5
                 Ethanol  0.139
                 Octanol  0.00409

Assume that 1% of the feed is not in equilibrium (possibly due to poor mixing):

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol', 'Octanol'], cache=True)
>>> feed = tmo.Stream('feed', Water=20, Octanol=20, Ethanol=1)
>>> top = tmo.Stream('top')
>>> bottom = tmo.Stream('bottom')
>>> ms = tmo.MultiStream('ms', phases='lL') # Store flow rate data here as well
>>> tmo.separations.lle(feed, top, bottom, efficiency=0.99, multi_stream=ms)
>>> ms.show()
MultiStream: ms
 phases: ('L', 'l'), T: 298.15 K, P: 101325 Pa
 flow (kmol/hr): (L) Water    3.55
                     Ethanol  0.861
                     Octanol  20
                 (l) Water    16.5
                     Ethanol  0.139
                     Octanol  0.00408
thermosteam.separations.vle(feed, vap, liq, T=None, P=None, V=None, Q=None, x=None, y=None, multi_stream=None)[source]

Run VLE mass and energy balance.

Parameters
  • feed (Stream) – Mixed feed.

  • vap (Stream) – Vapor fluid.

  • liq (Stream) – Liquid fluid.

  • P=None (float) – Operating pressure [Pa].

  • Q=None (float) – Duty [kJ/hr].

  • T=None (float) – Operating temperature [K].

  • V=None (float) – Molar vapor fraction.

  • x=None (float) – Molar composition of liquid (for binary mixtures).

  • y=None (float) – Molar composition of vapor (for binary mixtures).

  • multi_stream (MultiStream, optional) – Data from feed is passed to this stream to perform vapor-liquid equilibrium.

Examples

Perform vapor-liquid equilibrium on water and ethanol and split phases to vapor and liquid streams:

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> feed = tmo.Stream('feed', Water=20, Ethanol=20)
>>> vapor = tmo.Stream('top')
>>> liquid = tmo.Stream('bottom')
>>> tmo.separations.vle(feed, vapor, liquid, V=0.5, P=101325)
>>> vapor.show()
Stream: top
 phase: 'g', T: 353.88 K, P: 101325 Pa
 flow (kmol/hr): Water    7.72
                 Ethanol  12.3
>>> liquid.show()
Stream: bottom
 phase: 'l', T: 353.88 K, P: 101325 Pa
 flow (kmol/hr): Water    12.3
                 Ethanol  7.72

It is also possible to save flow rate data in a multi-stream as well:

>>> ms = tmo.MultiStream('ms', phases='lg')
>>> tmo.separations.vle(feed, vapor, liquid, V=0.5, P=101325, multi_stream=ms)
>>> ms.show()
MultiStream: ms
 phases: ('g', 'l'), T: 353.88 K, P: 101325 Pa
 flow (kmol/hr): (g) Water    7.72
                     Ethanol  12.3
                 (l) Water    12.3
                     Ethanol  7.72
thermosteam.separations.material_balance(chemical_IDs, variable_inlets, constant_inlets=(), constant_outlets=(), is_exact=True, balance='flow')[source]

Solve stream mass balance by iteration.

Parameters
  • chemical_IDs (tuple[str]) – Chemicals that will be used to solve mass balance linear equations. The number of chemicals must be same as the number of input streams varied.

  • variable_inlets (Iterable[Stream]) – Inlet streams that can vary in net flow rate to accomodate for the mass balance.

  • constant_inlets (Iterable[Stream], optional) – Inlet streams that cannot vary in flow rates.

  • constant_outlets (Iterable[Stream], optional) – Outlet streams that cannot vary in flow rates.

  • is_exact=True (bool, optional) – True if exact flow rate solution is required for the specified IDs.

  • balance='flow' ({'flow', 'composition'}, optional) –

    • ‘flow’: Satisfy output flow rates

    • ’composition’: Satisfy net output molar composition

Examples

Vary inlet flow rates to satisfy outlet flow rates:

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> in_a = tmo.Stream('in_a', Water=1)
>>> in_b = tmo.Stream('in_b', Ethanol=1)
>>> variable_inlets = [in_a, in_b]
>>> in_c = tmo.Stream('in_c', Water=100)
>>> constant_inlets = [in_c]
>>> out_a = tmo.Stream('out_a', Water=200, Ethanol=2)
>>> out_b = tmo.Stream('out_b', Ethanol=100)
>>> constant_outlets = [out_a, out_b]
>>> chemical_IDs = ('Water', 'Ethanol')
>>> tmo.separations.material_balance(chemical_IDs, variable_inlets, constant_inlets, constant_outlets)
>>> tmo.Stream.sum([in_a, in_b, in_c]).mol - tmo.Stream.sum([out_a, out_b]).mol # Molar flow rates entering and leaving are equal
array([0., 0.])

Vary inlet flow rates to satisfy outlet composition:

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> in_a = tmo.Stream('in_a', Water=1)
>>> in_b = tmo.Stream('in_b', Ethanol=1)
>>> variable_inlets = [in_a, in_b]
>>> in_c = tmo.Stream('in_c', Water=100)
>>> constant_inlets = [in_c]
>>> out_a = tmo.Stream('out_a', Water=200, Ethanol=2)
>>> out_b = tmo.Stream('out_b', Ethanol=100)
>>> constant_outlets = [out_a, out_b]
>>> chemical_IDs = ('Water', 'Ethanol')
>>> tmo.separations.material_balance(chemical_IDs, variable_inlets, constant_inlets, constant_outlets, balance='composition')
>>> tmo.Stream.sum([in_a, in_b, in_c]).z_mol - tmo.Stream.sum([out_a, out_b]).z_mol # Molar composition entering and leaving are equal
array([0., 0.])
class thermosteam.separations.MultiStageLLE(N_stages, feed, solvent, carrier_chemical=None, thermo=None, partition_data=None)[source]

Create a MultiStageLLE object that models a counter-current system of mixer-settlers for liquid-liquid extraction.

Parameters
  • N_stages (int) – Number of stages.

  • feed (Stream) – Feed with solute.

  • solvent (Stream) – Solvent to contact feed and recover solute.

  • carrier_chemical (str) – Name of main chemical in the feed (which is not selectively extracted by the solvent).

  • partition_data ({'IDs': tuple[str], 'K': 1d array}, optional) – IDs of chemicals in equilibrium and partition coefficients (molar composition ratio of the raffinate over the extract). If given, The mixer-settlers will be modeled with these constants. Otherwise, partition coefficients are computed based on temperature and composition.

Examples

Simulate 2-stage extraction of methanol from water using octanol:

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Methanol', 'Octanol'], cache=True)
>>> N_stages = 2
>>> feed = tmo.Stream('feed', Water=500, Methanol=50)
>>> solvent = tmo.Stream('solvent', Octanol=500)
>>> stages = tmo.separations.MultiStageLLE(N_stages, feed, solvent)
>>> stages.simulate_multi_stage_lle_without_side_draws()
>>> stages.raffinate.show()
Stream:
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kmol/hr): Water     413
                 Methanol  8.4
                 Octanol   0.1
>>> stages.extract.show()
Stream:
 phase: 'L', T: 298.15 K, P: 101325 Pa
 flow (kmol/hr): Water     87.
                 Methanol  41.
                 Octanol   500

Simulate 10-stage extraction with user defined partition coefficients:

>>> import numpy as np
>>> tmo.settings.set_thermo(['Water', 'Methanol', 'Octanol'])
>>> N_stages = 10
>>> feed = tmo.Stream('feed', Water=5000, Methanol=500)
>>> solvent = tmo.Stream('solvent', Octanol=5000)
>>> stages = tmo.separations.MultiStageLLE(N_stages, feed, solvent,
...     partition_data={
...         'K': np.array([6.894, 0.7244, 3.381e-04]),
...         'IDs': ('Water', 'Methanol', 'Octanol'),
...         'phi': 0.4100271108219455 # Initial phase fraction guess. This is optional.
...     }
... )
>>> stages.simulate_multi_stage_lle_without_side_draws()
>>> stages.raffinate.show()
Stream:
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kmol/hr): Water     4.1e+03
                 Methanol  1.2
                 Octanol   1.5
>>> stages.extract.show()
Stream:
 phase: 'L', T: 298.15 K, P: 101325 Pa
 flow (kmol/hr): Water     871
                 Methanol  499
                 Octanol   5e+03
thermosteam.separations.single_component_flow_rates_for_multi_stage_lle_without_side_draws(N_stages, phase_ratios, partition_coefficients, feed, solvent)[source]

Solve flow rates for a single component across a multi stage liquid-liquid extraction operation without side draws.

Parameters
  • N_stages (int) – Number of stages.

  • phase_ratios (1d array) – Phase ratios by stage. The phase ratio for a given stage is defined as F_l / F_L; where F_l and F_L are the flow rates of phase l (raffinate) and L (extract) leaving the stage respectively.

  • partition_coefficients (1d array) – Partition coefficients by stage. The partition coefficient for a given stage is defined as x_l / x_L; where x_l and x_L are the fraction of the component in phase l (raffinate) and L (extract) leaving the stage.

  • feed (float) – Component flow rate in feed entering stage 1.

  • solvent (float) – Component flow rate in solvent entering stage N.

Returns

extract_flow_rates – Extract component flow rates by stage.

Return type

1d array

thermosteam.separations.flow_rates_for_multi_stage_extration_without_side_draws(N_stages, phase_fractions, partition_coefficients, feed, solvent)[source]

Solve flow rates for a single component across a multi stage liquid-liquid extraction without side draws.

Parameters
  • N_stages (int) – Number of stages.

  • phase_fractions (1d array) – Phase fractions by stage. The phase fraction for a given stage is defined as F_l / (F_l + F_L); where F_l and F_L are the flow rates of phase l (raffinate) and L (extract) leaving the stage respectively.

  • partition_coefficients (Iterable[1d array]) – Partition coefficients with components by row and stages by column. The partition coefficient for a component in a given stage is defined as x_l / x_L; where x_l and x_L are the fraction of the component in phase l (raffinate) and L (extract) leaving the stage.

  • feed (Iterable[float]) – Flow rates of all components in feed entering stage 1.

  • solvent (Iterable[float]) – Flow rates of all components in solvent entering stage N.

Returns

extract_flow_rates – Extract flow rates with stages by row and components by column.

Return type

2d array

thermosteam.separations.chemical_splits(a, b=None, mixed=None)[source]

Return a ChemicalIndexer with splits for all chemicals to stream a.

Examples

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Water', 'Ethanol'], cache=True)
>>> stream = tmo.Stream('stream', Water=10, Ethanol=10)
>>> stream.vle(V=0.5, P=101325)
>>> isplits = tmo.separations.chemical_splits(stream['g'], stream['l'])
>>> isplits.show()
ChemicalIndexer:
 Water    0.3861
 Ethanol  0.6139
>>> isplits = tmo.separations.chemical_splits(stream['g'], mixed=stream)
>>> isplits.show()
ChemicalIndexer:
 Water    0.3861
 Ethanol  0.6139

Thermosteam 0.22

0.22.5

  • Activity coefficient calculations for chemicals with missing functional groups models are default to 1 and a RuntimeWarning is issued. Previously, an RuntimeError was raised instead.

0.22.4

Bug fixes:

  • Corrected bug in the gas enthalpy of chemicals, closing enthalpy balances.

0.22.3

Bug fixes:

  • thermosteam.reaction.Reaction math operations (including divisions) are now working correctly. Please see examples in documentation to learn how to add, multiply, and divide reactions.

0.22.2

New features:

0.22.1

New features:

Contributing to Thermosteam

General Process

Here’s the short summary of how to contribute using git bash:

  1. If you are a first-time contributor:

    • Go to https://github.com/BioSTEAMDevelopmentGroup/thermosteam and click the “fork” button to create your own copy of the project.

    • Clone the project to your local computer:

      git clone --depth 100 https://github.com/your-username/thermosteam.git
      
    • Change the directory:

      cd thermosteam
      
    • Add the upstream repository:

      git remote add upstream https://github.com/BioSTEAMDevelopmentGroup/thermosteam.git
      
    • Now, git remote -v will show two remote repositories named “upstream” (which refers to the thermosteam repository), and “origin” (which refers to your personal fork).

  2. Develop your contribution:

    • Pull the latest changes from upstream:

      git checkout master
      git pull upstream master
      
    • Create a branch for the feature you want to work on. Since the branch name will appear in the merge message, use a sensible name such as “Chemical-properties-enhancement”:

      git checkout -b Chemical-properties-enhancement
      
    • Commit locally as you progress (git add and git commit) Use a properly formatted commit message, write tests that fail before your change and pass afterward, run all the tests locally. Be sure to document any changed behavior in docstrings, keeping to the NumPy docstring standard.

  3. To submit your contribution:

    • Push your changes back to your fork on GitHub:

      git push origin Chemical-properties-enhancement
      
    • Enter your GitHub username and password (repeat contributors or advanced users can remove this step by connecting to GitHub with SSH).

    • Go to GitHub. The new branch will show up with a green Pull Request button. Make sure the title and message are clear, concise, and self- explanatory. Then click the button to submit it.

    • If your commit introduces a new feature or changes functionality, post in https://github.com/BioSTEAMDevelopmentGroup/thermosteam/issues to explain your changes. For bug fixes, documentation updates, etc., this is generally not necessary, though if you do not get any reaction, do feel free to ask for a review.

Testing

First install the developer version of thermosteam:

$ cd thermosteam
$ pip install -e .[dev]

This installs pytest and other dependencies you need to run the tests locally. You can run tests by going to your local thermosteam directory and running the following:

$ pytest
 =========================== test session starts ============================
 platform win32 -- Python 3.7.6, pytest-6.0.1, py-1.9.0, pluggy-0.13.1
 rootdir: C:\...\thermosteam, configfile: pytest.ini
 plugins: hypothesis-5.5.4, arraydiff-0.3, astropy-header-0.1.2, cov-2.10.1,
 doctestplus-0.5.0, openfiles-0.4.0, remotedata-0.3.2
 collected 157 items

 tests\test_biorefineries.py .......                                   [  4%]
 tests\test_chemical.py .                                              [  5%]
 tests\test_stream.py ..                                               [  6%]
 thermosteam\_chemical.py .......                                      [ 10%]
 thermosteam\_chemicals.py .....................                       [ 24%]
 thermosteam\_multi_stream.py ............                             [ 31%]
 thermosteam\_stream.py ......................................         [ 56%]
 thermosteam\_thermo.py ...                                            [ 57%]
 thermosteam\_thermo_data.py .                                         [ 58%]
 thermosteam\eos.py ...................                                [ 70%]
 thermosteam\functional.py .....                                       [ 73%]
 thermosteam\separations.py .............                              [ 82%]
 thermosteam\units_of_measure.py ....                                  [ 84%]
 thermosteam\base\functor.py .                                         [ 85%]
 thermosteam\chemicals\reaction.py ..                                  [ 86%]
 thermosteam\equilibrium\bubble_point.py ...                           [ 88%]
 thermosteam\equilibrium\dew_point.py ...                              [ 90%]
 thermosteam\equilibrium\lle.py .                                      [ 91%]
 thermosteam\equilibrium\sle.py .                                      [ 91%]
 thermosteam\equilibrium\vle.py .                                      [ 92%]
 thermosteam\mixture\ideal_mixture_model.py .                          [ 92%]
 thermosteam\mixture\mixture_builders.py .                             [ 93%]
 thermosteam\reaction\_reaction.py .....                               [ 96%]
 thermosteam\utils\representation.py .....                             [100%]

 =========================== 157 passed in 36.52s ===========================

This runs all the doctests in thermosteam, which covers most of the API. If any test is marked with a letter F, that test has failed. Pytest will point you to the location of the error, the values that were expected, and the values that were generated.

Note

Some parts in thermosteam do not have tests yet. Any contributions towards rigorous testing is welcome!

Documentation

Concise and thorough documentation is required for any contribution. Make sure to:

  • Use NumPy style docstrings.

  • Document all functions and classes.

  • Document short functions in one line if possible.

  • Mention and reference any equations or methods used and make sure to include the chapter and page number if it is a book or a long document.

  • Preview the docs before making a pull request (open your cmd/terminal in the “docs” folder, run “make html”, and open “docs/_build/html/index.html”).

Best practices

Please refer to the following guides for best practices to make software designs more understandable, flexible, and maintainable:

Indices and tables