4. Thermodynamic equilibrium

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.

4.1. Fugacities

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

[1]:
import thermosteam as tmo
import numpy as np
chemicals = tmo.Chemicals(['Water', 'Ethanol'])
tmo.settings.set_thermo(chemicals)

# Create a LiquidFugacities object
F_l = tmo.equilibrium.LiquidFugacities(chemicals)

# Compute liquid fugacities
liquid_molar_composition = np.array([0.72, 0.28])
f_l = F_l(x=liquid_molar_composition, T=355)
f_l
[1]:
array([43274.119, 58056.67 ])
[2]:
# Create a GasFugacities object
F_g = tmo.equilibrium.GasFugacities(chemicals)

# Compute gas fugacities
gas_molar_composition = np.array([0.43, 0.57])
f_g = F_g(y=gas_molar_composition, T=355, P=101325)
f_g
[2]:
array([43569.75, 57755.25])

4.2. Bubble and dew points

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

[3]:
# Create a BubblePoint object
BP = tmo.equilibrium.BubblePoint(chemicals)
molar_composition = np.array([0.5, 0.5])

# Solve bubble point at constant temperature
bp = BP(z=molar_composition, T=355)
bp
[3]:
BubblePointValues(T=355, P=109755.45319869411, IDs=('Water', 'Ethanol'), z=[0.5 0.5], y=[0.343 0.657])
[4]:
# Note that the result is a BubblePointValues object which contain all results as attibutes
(bp.T, bp.P, bp.IDs, bp.z, bp.y)
[4]:
(355,
 109755.45319869411,
 ('Water', 'Ethanol'),
 array([0.5, 0.5]),
 array([0.343, 0.657]))
[5]:
# Solve bubble point at constant pressure
BP(z=molar_composition, P=2*101325)
[5]:
BubblePointValues(T=371.78188210405403, P=202650, IDs=('Water', 'Ethanol'), z=[0.5 0.5], y=[0.35 0.65])
[6]:
# Create a DewPoint object
DP = tmo.equilibrium.DewPoint(chemicals)

# Solve for dew point at constant temperautre
dp = DP(z=molar_composition, T=355)
dp
[6]:
DewPointValues(T=355, P=91970.14968399647, IDs=('Water', 'Ethanol'), z=[0.5 0.5], x=[0.851 0.149])
[7]:
# Note that the result is a DewPointValues object which contain all results as attibutes
(dp.T, dp.P, dp.IDs, dp.z, dp.x)
[7]:
(355,
 91970.14968399647,
 ('Water', 'Ethanol'),
 array([0.5, 0.5]),
 array([0.851, 0.149]))
[8]:
# Solve for dew point at constant pressure
DP(z=molar_composition, P=2*101324)
[8]:
DewPointValues(T=376.2616600249248, P=202648, IDs=('Water', 'Ethanol'), z=[0.5 0.5], x=[0.832 0.168])

4.3. Vapor liquid equilibrium

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

[9]:
# First create a material indexer for the VLE object to manage material data
imol = tmo.indexer.MaterialIndexer(l=[('Water', 0.5), ('Ethanol', 0.5)],
                                   g=[('Water', 0.5), ('Ethanol', 0.5)])

# Create a VLE object
vle = tmo.equilibrium.VLE(imol)
vle
[9]:
VLE(imol=MaterialIndexer(
        g=[('Water', 0.5), ('Ethanol', 0.5)],
        l=[('Water', 0.5), ('Ethanol', 0.5)]),
    thermal_condition=ThermalCondition(T=298.15, P=101325))

You can call the VLE object by setting 2 degrees of freedom from the following:T (temperature; in K), P (pressure; in Pa), V (molar vapor fraction), and H (enthalpy; in kJ/hr), y (binary molar vapor composition), x (binary molar liquid composition).

Here we are some examples of the possibilities:

[10]:
vle(T=355, P=101325)
vle
[10]:
VLE(imol=MaterialIndexer(
        g=[('Water', 0.6417), ('Ethanol', 0.8607)],
        l=[('Water', 0.3583), ('Ethanol', 0.1393)]),
    thermal_condition=ThermalCondition(T=355.00, P=101325))
[11]:
mixture_enthalpy = vle.mixture.xH(imol, *vle.thermal_condition)
vle(H=mixture_enthalpy, P=202650)
vle
[11]:
VLE(imol=MaterialIndexer(
        g=[('Water', 0.6161), ('Ethanol', 0.8266)],
        l=[('Water', 0.3839), ('Ethanol', 0.1734)]),
    thermal_condition=ThermalCondition(T=373.69, P=202650))
[12]:
vle(V=0.5, P=101325)
vle
[12]:
VLE(imol=MaterialIndexer(
        g=[('Water', 0.3861), ('Ethanol', 0.6139)],
        l=[('Water', 0.6139), ('Ethanol', 0.3861)]),
    thermal_condition=ThermalCondition(T=353.88, P=101325))
[13]:
vle(V=0.5, T=360)
vle
[13]:
VLE(imol=MaterialIndexer(
        g=[('Water', 0.3886), ('Ethanol', 0.6114)],
        l=[('Water', 0.6114), ('Ethanol', 0.3886)]),
    thermal_condition=ThermalCondition(T=360.00, P=128136))
[14]:
vle(x=np.array([0.8, 0.2]), P=101325)
vle
[14]:
VLE(imol=MaterialIndexer(
        g=[('Water', 0.8356), ('Ethanol', 0.9589)],
        l=[('Water', 0.1644), ('Ethanol', 0.04109)]),
    thermal_condition=ThermalCondition(T=356.25, P=128136))
[15]:
vle(y=np.array([0.4, 0.6]), T=360)
vle
[15]:
VLE(imol=MaterialIndexer(
        g=[('Water', 0.4691), ('Ethanol', 0.7036)],
        l=[('Water', 0.5309), ('Ethanol', 0.2964)]),
    thermal_condition=ThermalCondition(T=356.25, P=126727))

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

[16]:
vle(x=np.array([0.2, 0.8]), P=101325)
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-16-3d0d3ab197da> in <module>
----> 1 vle(x=np.array([0.2, 0.8]), P=101325)

~\OneDrive\Code\thermosteam\thermosteam\equilibrium\vle.py in __call__(self, P, H, T, V, x, y)
    165                 return self.set_PH(P, H)
    166             elif x_spec:
--> 167                 return self.set_Px(P, np.asarray(x))
    168             else: # y_spec
    169                 return self.set_Py(P, np.asarray(y))

~\OneDrive\Code\thermosteam\thermosteam\equilibrium\vle.py in set_Px(self, P, x)
    343         assert self._N == 2, 'number of species in equilibrium must be 2 to specify x'
    344         self._thermal_condition.T, y = self._bubble_point.solve_Ty(x, P)
--> 345         self._lever_rule(x, y)
    346
    347     def set_Ty(self, T, y):

~\OneDrive\Code\thermosteam\thermosteam\equilibrium\vle.py in _lever_rule(self, x, y)
    325     def _lever_rule(self, x, y):
    326         split_frac = (self._z[0]-x[0])/(y[0]-x[0])
--> 327         assert -0.00001 < split_frac < 1.00001, 'desired composition is infeasible'
    328         if split_frac > 1:
    329             split_frac = 1

AssertionError: desired composition is infeasible

4.4. Liquid-liquid equilibrium

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

[17]:
tmo.settings.set_thermo(['Water', 'Octane', 'Butanol'])
imol = tmo.indexer.MolarFlowIndexer(
            l=[('Water', 304), ('Butanol', 30)],
            L=[('Octane', 100)])
lle = tmo.equilibrium.LLE(imol)
lle(T=360)
lle
[17]:
LLE(imol=MolarFlowIndexer(
        L=[('Water', 290.6), ('Octane', 0.02062), ('Butanol', 4.3)],
        l=[('Water', 13.35), ('Octane', 99.98), ('Butanol', 25.7)]),
    thermal_condition=ThermalCondition(T=360.00, P=101325))

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