16. Phase equilibrium#

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

16.1. Fugacities#

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

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

# Create a LiquidFugacities object
F_l = bst.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([43338.226, 57731.001])
[2]:
# Create a GasFugacities object
F_g = bst.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])

16.2. Bubble and dew points#

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

[3]:
# Create a BubblePoint object
BP = bst.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.00, P=109407, IDs=('Water', 'Ethanol'), z=[0.5 0.5], y=[0.344 0.656])
[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,
 109406.50388756258,
 ('Water', 'Ethanol'),
 array([0.5, 0.5]),
 array([0.344, 0.656]))
[5]:
# Solve bubble point at constant pressure
BP(z=molar_composition, P=2*101325)
[5]:
BubblePointValues(T=371.86, P=202650, IDs=('Water', 'Ethanol'), z=[0.5 0.5], y=[0.351 0.649])
[6]:
# Create a DewPoint object
DP = bst.equilibrium.DewPoint(chemicals)

# Solve for dew point at constant temperautre
dp = DP(z=molar_composition, T=355)
dp
[6]:
DewPointValues(T=355.00, P=92008, IDs=('Water', 'Ethanol'), z=[0.5 0.5], x=[0.849 0.151])
[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,
 92008.17239046741,
 ('Water', 'Ethanol'),
 array([0.5, 0.5]),
 array([0.849, 0.151]))
[8]:
# Solve for dew point at constant pressure
DP(z=molar_composition, P=2*101324)
[8]:
DewPointValues(T=376.25, P=202648, IDs=('Water', 'Ethanol'), z=[0.5 0.5], x=[0.83 0.17])

16.3. Vapor liquid equilibrium#

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

[9]:
# Create a VLE object
vle = bst.equilibrium.VLE()

# Add material data through the indexer
vle.imol['lg', ('Water', 'Ethanol')] = [0.5, 0.5]
vle
[9]:
VLE(imol=MolarFlowIndexer(
        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 list:

  • T - Temperature [K]

  • P - Pressure [Pa]

  • V - Molar vapor fraction

  • H - Enthalpy [kJ/hr]

  • S - Entropy [kJ/K/hr]

  • y - Binary molar vapor composition

  • x - Binary molar liquid composition

Here are some examples:

[10]:
vle(T=355, P=101325)
vle
[10]:
VLE(imol=MolarFlowIndexer(
        g=[('Water', 0.6361), ('Ethanol', 0.8548)],
        l=[('Water', 0.3639), ('Ethanol', 0.1452)]),
    thermal_condition=ThermalCondition(T=355.00, P=101325))
[11]:
mixture_enthalpy = vle.mixture.xH(vle.imol, *vle.thermal_condition)
vle(H=mixture_enthalpy, P=202650)
vle
[11]:
VLE(imol=MolarFlowIndexer(
        g=[('Water', 0.6081), ('Ethanol', 0.8183)],
        l=[('Water', 0.3919), ('Ethanol', 0.1817)]),
    thermal_condition=ThermalCondition(T=373.69, P=202650))
[12]:
vle(V=0.5, P=101325)
vle
[12]:
VLE(imol=MolarFlowIndexer(
        g=[('Water', 0.3874), ('Ethanol', 0.6126)],
        l=[('Water', 0.6126), ('Ethanol', 0.3874)]),
    thermal_condition=ThermalCondition(T=353.94, P=101325))
[13]:
vle(V=0.5, T=360)
vle
[13]:
VLE(imol=MolarFlowIndexer(
        g=[('Water', 0.3899), ('Ethanol', 0.6101)],
        l=[('Water', 0.6101), ('Ethanol', 0.3899)]),
    thermal_condition=ThermalCondition(T=360.00, P=127822))
[14]:
vle(x=np.array([0.8, 0.2]), P=101325)
vle
[14]:
VLE(imol=MolarFlowIndexer(
        g=[('Water', 0.8434), ('Ethanol', 0.9609)],
        l=[('Water', 0.1566), ('Ethanol', 0.03914)]),
    thermal_condition=ThermalCondition(T=356.31, P=127822))
[15]:
vle(y=np.array([0.4, 0.6]), T=360)
vle
[15]:
VLE(imol=MolarFlowIndexer(
        g=[('Water', 0.4627), ('Ethanol', 0.6941)],
        l=[('Water', 0.5373), ('Ethanol', 0.3059)]),
    thermal_condition=ThermalCondition(T=356.31, P=126587))

Note that some compositions are infeasible because the mass balance cannot be satisfied.

[16]:
with bst.print_error: vle(x=np.array([0.2, 0.8]), P=101325)
InfeasibleRegion: phase composition is infeasible

16.4. Liquid-liquid equilibrium#

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

[17]:
bst.settings.set_thermo(['Water', 'Octane', 'Butanol'])
lle = bst.equilibrium.LLE()
lle.imol['l', ('Water', 'Butanol', 'Octane')] = [304, 30, 100]
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.

16.5. Vapor-liquid-liquid equilibrium#

Perform vapor-liquid-liquid equilibrium calculations through VLLE objects. We can add flows after creating the VLLE object:

[18]:
import biosteam as bst
bst.settings.set_thermo(['Water', 'Octane', 'Ethanol'])
# Create a VLE object
vlle = bst.equilibrium.VLLE()

# Add flows
vlle.imol['l' , ('Water', 'Octane', 'Ethanol')] = [0.5, 0.5, 0.1]
vlle
[18]:
VLLE(imol=MolarFlowIndexer(phases=('L', 'g', 'l'),
        l=[('Water', 0.5), ('Octane', 0.5), ('Ethanol', 0.1)]),
    thermal_condition=ThermalCondition(T=298.15, P=101325))

You can call the VLLE object by setting 2 degrees of freedom from the following list:

  • T - Temperature [K]

  • P - Pressure [Pa]

  • V - Molar vapor fraction

  • H - Enthalpy [kJ/hr]

  • S - Entropy [kJ/K/hr]

Here are some examples:

[19]:
vlle(T=355, P=101325)
vlle
[19]:
VLLE(imol=MolarFlowIndexer(
        L=[('Water', 0.3882), ('Octane', 6.627e-05), ('Ethanol', 0.02807)],
        g=[('Water', 0.0865), ('Octane', 0.04158), ('Ethanol', 0.05337)],
        l=[('Water', 0.02528), ('Octane', 0.4584), ('Ethanol', 0.01856)]),
    thermal_condition=ThermalCondition(T=355.00, P=101325))
[20]:
mixture_enthalpy = vlle.mixture.xH(vlle.imol, *vlle.thermal_condition)
vlle(H=mixture_enthalpy, P=101325)
vlle
[20]:
VLLE(imol=MolarFlowIndexer(
        L=[('Water', 0.3882), ('Octane', 6.627e-05), ('Ethanol', 0.02807)],
        g=[('Water', 0.0865), ('Octane', 0.04158), ('Ethanol', 0.05337)],
        l=[('Water', 0.02528), ('Octane', 0.4584), ('Ethanol', 0.01856)]),
    thermal_condition=ThermalCondition(T=355.00, P=101325))
[21]:
vlle(V=0.5, P=101325)
vlle
[21]:
VLLE(imol=MolarFlowIndexer(
        L=[('Water', 0.1662), ('Octane', 1.281e-05), ('Ethanol', 0.004298)],
        g=[('Water', 0.3142), ('Octane', 0.1462), ('Ethanol', 0.08958)],
        l=[('Water', 0.01961), ('Octane', 0.3538), ('Ethanol', 0.006125)]),
    thermal_condition=ThermalCondition(T=358.76, P=101325))

Warning

VLLE is an experimental feature; results may not be accurate nor consistent.