diff --git a/tests/test_stream.py b/tests/test_stream.py index 271999b5..e20899c4 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -31,24 +31,14 @@ def test_registration_alias(): def test_vlle(): tmo.settings.set_thermo(['Water', 'Ethanol', 'Octane'], cache=True) - s = tmo.Stream(None, Water=1, Ethanol=1, Octane=2, vlle=True, T=351) - assert_allclose(s.mol, [1, 1, 2]) # mass balance + s = tmo.Stream(None, Water=1, Ethanol=0.5, Octane=2, vlle=True, T=350) + assert_allclose(s.mol, [1, 0.5, 2]) # mass balance total = s.F_mol xl = s.imol['l'].sum() / total - xL = s.imol['L'].sum() / total - xg = s.imol['g'].sum() / total - assert_allclose(xl + xL, 0.533447771190868, atol=0.05) # Convergence - assert_allclose(xg, 0.46655222880913216, atol=0.05) - # Make sure past equilibrium conditions do not affect result of vlle - s = tmo.Stream(None, Water=1, Ethanol=1, Octane=2, T=351) - s.vle(T=351, P=101325) - s.vlle(T=351, P=101325) - assert_allclose(s.mol, [1, 1, 2]) # mass balance - xl = s.imol['l'].sum() / total - xL = s.imol['L'].sum() / total + xL = s.imol['L'].sum() / total if 'L' in s.phases else 0. xg = s.imol['g'].sum() / total - assert_allclose(xl + xL, 0.533447771190868, atol=0.05) # Convergence - assert_allclose(xg, 0.46655222880913216, atol=0.05) + assert_allclose(xl + xL, 0.802404472833999, atol=0.05) # Convergence + assert_allclose(xg, 0.19759552716600104, atol=0.05) s = tmo.Stream(None, Water=1, Ethanol=1, Octane=2, vlle=True, T=300) assert set(s.phases) == set(['l', 'L']) # No gas phase diff --git a/thermosteam/_stream.py b/thermosteam/_stream.py index 67154dbb..c6b1d1d1 100644 --- a/thermosteam/_stream.py +++ b/thermosteam/_stream.py @@ -2058,37 +2058,33 @@ def vlle(self, T, P): lle = eq.LLE(imol, self._thermal_condition, self._thermo) - data = imol.data - net_chemical_flows = data.sum(axis=0) - data.clear() - imol['l'] = net_chemical_flows - total_flow = net_chemical_flows.sum() + data = self._imol.data + LIQ, gas, liq = data + liq += LIQ # All flows must be in the 'l' phase for VLE + LIQ[:] = 0. + vle(T=T, P=P) + if not gas.any() or not liq.any(): return + lle(T, P) + if not (LIQ.any() and liq.any()): return + total_flow = data.sum() def f(x, done=[False]): if done[0]: return x data[:] = x lle(T=T, P=P) - net_phase_flows = data.sum(axis=1, keepdims=True) - compositions = data / net_phase_flows - if (abs(compositions[0] - compositions[2]).sum() < 1e-3 - or compositions[0].sum() < 1e-6 - or compositions[2].sum() < 1e-6): # Perform VLE on one liquid phase - data[2] += data[0] # All flows must be in the 'l' phase for VLE - data[0] = 0. - vle(T=T, P=P) - done[0] = True - return data - else: # Perform VLE on each liquid phase - vle(T=T, P=P) - no_vapor = not data[1].any() - data[2], data[0] = data[0].copy(), data[2].copy() - vle(T=T, P=P) - done[0] = no_vapor and not data[1].any() # No VLE + vle(T=T, P=P) + liq[:], LIQ[:] = LIQ, liq.copy() + vle(T=T, P=P) + liq[:], LIQ[:] = LIQ, liq.copy() return data.to_array() - data[:] = total_flow * flx.fixed_point( - f, data / total_flow, xtol=1e-3, + flx.fixed_point( + f, data / total_flow, xtol=1e-6, checkiter=False, checkconvergence=False, convergenceiter=10 ) + if np.abs(liq - LIQ).sum() < 1e-6: + liq += LIQ + LIQ.clear() + data *= total_flow @property def vle_chemicals(self) -> list[tmo.Chemical]: diff --git a/thermosteam/equilibrium/dew_point.py b/thermosteam/equilibrium/dew_point.py index a91f9e47..17745477 100644 --- a/thermosteam/equilibrium/dew_point.py +++ b/thermosteam/equilibrium/dew_point.py @@ -29,6 +29,9 @@ def gamma_iter(gamma, x_gamma, T, P, f_gamma, gamma_args): return f_gamma(x, T, *gamma_args) def solve_x(x_guess, x_gamma, T, P, f_gamma, gamma_args): + mask = x_guess < 1e-32 + x_guess[mask] = 1e-32 + x_guess = fn.normalize(x_guess) gamma = f_gamma(x_guess, T, *gamma_args) args = (x_gamma, T, P, f_gamma, gamma_args) gamma = flx.wegstein( @@ -339,7 +342,6 @@ def solve_Px(self, z, T, gas_conversion=None): P_guess, x = self._Px_ideal(z_over_Psats) args = (T, z_norm, z_over_Psats, Psats, x) f = self._P_error - breakpoint() try: P = flx.aitken_secant(f, P_guess, P_guess-10, self.P_tol, 5e-12, args, checkiter=False, maxiter=self.maxiter) diff --git a/thermosteam/equilibrium/lle.py b/thermosteam/equilibrium/lle.py index dec57aff..ca2418f3 100644 --- a/thermosteam/equilibrium/lle.py +++ b/thermosteam/equilibrium/lle.py @@ -140,8 +140,8 @@ class LLE(Equilibrium, phases='lL'): >>> lle(T=360, top_chemical='Octane') >>> 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)]), + L=[('Water', 2.552), ('Ethanol', 2.167), ('Octane', 39.92), ('Hexane', 0.9886)], + l=[('Water', 301.4), ('Ethanol', 27.83), ('Octane', 0.07738), ('Hexane', 0.01141)]), thermal_condition=ThermalCondition(T=360.00, P=101325)) References diff --git a/thermosteam/equilibrium/vlle.py b/thermosteam/equilibrium/vlle.py index 4208fd96..21a4ca0e 100644 --- a/thermosteam/equilibrium/vlle.py +++ b/thermosteam/equilibrium/vlle.py @@ -165,157 +165,7 @@ class VLLE(Equilibrium, phases='lLg'): Themodynamic property package for equilibrium calculations. Defaults to `thermosteam.settings.get_thermo()`. - Examples - -------- - First create a VLLE 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)]) - >>> vlle = equilibrium.VLLE(imol) - >>> vlle - VLLE(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: - - >>> vlle(V=0.5, P=101325) - >>> vlle - VLLE(imol=MolarFlowIndexer( - g=[('Water', 126.7), ('Ethanol', 26.38), ('Methanol', 33.49), ('Propanol', 0.8958)], - l=[('Water', 177.3), ('Ethanol', 3.622), ('Methanol', 6.509), ('Propanol', 0.1042)]), - thermal_condition=ThermalCondition(T=363.85, P=101325)) - - Equilibrium given temperature and pressure: - - >>> vlle(T=363.88, P=101325) - >>> vlle - VLLE(imol=MolarFlowIndexer( - g=[('Water', 127.4), ('Ethanol', 26.41), ('Methanol', 33.54), ('Propanol', 0.8968)], - l=[('Water', 176.6), ('Ethanol', 3.59), ('Methanol', 6.456), ('Propanol', 0.1032)]), - thermal_condition=ThermalCondition(T=363.88, P=101325)) - - Equilibrium given enthalpy and pressure: - - >>> H = vlle.thermo.mixture.xH(vlle.imol, T=363.88, P=101325) - >>> vlle(H=H, P=101325) - >>> vlle - VLLE(imol=MolarFlowIndexer( - g=[('Water', 127.4), ('Ethanol', 26.41), ('Methanol', 33.54), ('Propanol', 0.8968)], - l=[('Water', 176.6), ('Ethanol', 3.59), ('Methanol', 6.456), ('Propanol', 0.1032)]), - thermal_condition=ThermalCondition(T=363.88, P=101325)) - - Equilibrium given entropy and pressure: - - >>> S = vlle.thermo.mixture.xS(vlle.imol, T=363.88, P=101325) - >>> vlle(S=S, P=101325) - >>> vlle - VLLE(imol=MolarFlowIndexer( - g=[('Water', 127.4), ('Ethanol', 26.41), ('Methanol', 33.54), ('Propanol', 0.8968)], - l=[('Water', 176.6), ('Ethanol', 3.59), ('Methanol', 6.456), ('Propanol', 0.1032)]), - thermal_condition=ThermalCondition(T=363.88, P=101325)) - - Equilibrium given vapor fraction and temperature: - - >>> vlle(V=0.5, T=363.88) - >>> vlle - VLLE(imol=MolarFlowIndexer( - g=[('Water', 126.7), ('Ethanol', 26.38), ('Methanol', 33.49), ('Propanol', 0.8958)], - l=[('Water', 177.3), ('Ethanol', 3.622), ('Methanol', 6.509), ('Propanol', 0.1042)]), - thermal_condition=ThermalCondition(T=363.88, P=101431)) - - Equilibrium given enthalpy and temperature: - - >>> vlle(H=H, T=363.88) - >>> vlle - VLLE(imol=MolarFlowIndexer( - g=[('Water', 127.4), ('Ethanol', 26.41), ('Methanol', 33.54), ('Propanol', 0.8968)], - l=[('Water', 176.6), ('Ethanol', 3.59), ('Methanol', 6.456), ('Propanol', 0.1032)]), - thermal_condition=ThermalCondition(T=363.88, P=101325)) - - Non-partitioning heavy and gaseous chemicals also affect VLLE. Calculation - are repeated with non-partitioning chemicals: - - >>> from thermosteam import indexer, equilibrium, settings, Chemical - >>> O2 = Chemical('O2', phase='g') - >>> Glucose = Chemical('Glucose', phase='l', default=True) - >>> settings.set_thermo(['Water', 'Ethanol', 'Methanol', 'Propanol', O2, Glucose], cache=True) - >>> imol = indexer.MolarFlowIndexer( - ... l=[('Water', 304), ('Ethanol', 30), ('Glucose', 5)], - ... g=[('Methanol', 40), ('Propanol', 1), ('O2', 10)]) - >>> vlle = equilibrium.VLLE(imol) - >>> vlle(T=363.88, P=101325) - >>> vlle - VLLE(imol=MolarFlowIndexer( - g=[('Water', 159.5), ('Ethanol', 27.65), ('Methanol', 35.63), ('Propanol', 0.9337), ('O2', 10)], - l=[('Water', 144.5), ('Ethanol', 2.352), ('Methanol', 4.369), ('Propanol', 0.0663), ('Glucose', 5)]), - thermal_condition=ThermalCondition(T=363.88, P=101325)) - - >>> vlle(V=0.5, P=101325) - >>> vlle - VLLE(imol=MolarFlowIndexer( - g=[('Water', 126.7), ('Ethanol', 26.38), ('Methanol', 33.52), ('Propanol', 0.8957), ('O2', 10)], - l=[('Water', 177.3), ('Ethanol', 3.618), ('Methanol', 6.478), ('Propanol', 0.1043), ('Glucose', 5)]), - thermal_condition=ThermalCondition(T=362.47, P=101325)) - - >>> H = vlle.thermo.mixture.xH(vlle.imol, T=363.88, P=101325) - >>> vlle(H=H, P=101325) - >>> vlle - VLLE(imol=MolarFlowIndexer( - g=[('Water', 127.4), ('Ethanol', 26.42), ('Methanol', 33.58), ('Propanol', 0.8968), ('O2', 10)], - l=[('Water', 176.6), ('Ethanol', 3.583), ('Methanol', 6.421), ('Propanol', 0.1032), ('Glucose', 5)]), - thermal_condition=ThermalCondition(T=362.51, P=101325)) - - >>> S = vlle.thermo.mixture.xS(vlle.imol, T=363.88, P=101325) - >>> vlle(S=S, P=101325) - >>> vlle - VLLE(imol=MolarFlowIndexer( - g=[('Water', 128.2), ('Ethanol', 26.45), ('Methanol', 33.63), ('Propanol', 0.8979), ('O2', 10)], - l=[('Water', 175.8), ('Ethanol', 3.548), ('Methanol', 6.365), ('Propanol', 0.1021), ('Glucose', 5)]), - thermal_condition=ThermalCondition(T=362.54, P=101325)) - - >>> vlle(V=0.5, T=363.88) - >>> vlle - VLLE(imol=MolarFlowIndexer( - g=[('Water', 126.7), ('Ethanol', 26.38), ('Methanol', 33.49), ('Propanol', 0.8958), ('O2', 10)], - l=[('Water', 177.3), ('Ethanol', 3.622), ('Methanol', 6.509), ('Propanol', 0.1042), ('Glucose', 5)]), - thermal_condition=ThermalCondition(T=363.88, P=106841)) - - >>> vlle(H=H, T=363.88) - >>> vlle - VLLE(imol=MolarFlowIndexer( - g=[('Water', 126.7), ('Ethanol', 26.38), ('Methanol', 33.49), ('Propanol', 0.8958), ('O2', 10)], - l=[('Water', 177.3), ('Ethanol', 3.622), ('Methanol', 6.51), ('Propanol', 0.1042), ('Glucose', 5)]), - thermal_condition=ThermalCondition(T=363.88, P=106842)) - - >>> vlle(S=S, T=363.88) - >>> vlle - VLLE(imol=MolarFlowIndexer( - g=[('Water', 128.1), ('Ethanol', 26.45), ('Methanol', 33.6), ('Propanol', 0.8978), ('O2', 10)], - l=[('Water', 175.9), ('Ethanol', 3.555), ('Methanol', 6.399), ('Propanol', 0.1022), ('Glucose', 5)]), - thermal_condition=ThermalCondition(T=363.88, P=106562)) - - The presence of a non-partitioning gaseous chemical will result in some - evaporation, even if the tempeture is below the saturated bubble point: - - >>> from thermosteam import indexer, equilibrium, settings, Chemical - >>> O2 = Chemical('O2', phase='g') - >>> settings.set_thermo(['Water', O2], cache=True) - >>> imol = indexer.MolarFlowIndexer( - ... l=[('Water', 30)], - ... g=[('O2', 10)]) - >>> vlle = equilibrium.VLLE(imol) - >>> vlle(T=300., P=101325) - >>> vlle - VLLE(imol=MolarFlowIndexer( - g=[('Water', 0.3617), ('O2', 10)], - l=[('Water', 29.64)]), - thermal_condition=ThermalCondition(T=300.00, P=101325)) - """ __slots__ = ( 'vle', diff --git a/thermosteam/separations.py b/thermosteam/separations.py index 2934f7e9..5c81f6c1 100644 --- a/thermosteam/separations.py +++ b/thermosteam/separations.py @@ -382,7 +382,7 @@ def lle_partition_coefficients(top, bottom): >>> IDs ('Water', 'Ethanol', 'Octanol') >>> round(K[2], -1) # Octanol - 3330.0 + 2390.0 """ IDs = tuple([i.ID for i in bottom.lle_chemicals]) @@ -605,15 +605,15 @@ def lle(feed, top, bottom, top_chemical=None, efficiency=1.0, multi_stream=None) >>> top.show() Stream: top phase: 'l', T: 298.15 K, P: 101325 Pa - flow (kmol/hr): Water 3.55 - Ethanol 0.861 + flow (kmol/hr): Water 2.87 + Ethanol 0.828 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 + flow (kmol/hr): Water 17.1 + Ethanol 0.172 + Octanol 0.00612 Assume that 1% of the feed is not in equilibrium (possibly due to poor mixing): @@ -627,12 +627,12 @@ def lle(feed, top, bottom, top_chemical=None, efficiency=1.0, multi_stream=None) >>> ms.show() MultiStream: ms phases: ('L', 'l'), T: 298.15 K, P: 101325 Pa - flow (kmol/hr): (L) Water 3.55 - Ethanol 0.861 + flow (kmol/hr): (L) Water 2.87 + Ethanol 0.828 Octanol 20 - (l) Water 16.5 - Ethanol 0.139 - Octanol 0.00409 + (l) Water 17.1 + Ethanol 0.172 + Octanol 0.00612 """ if multi_stream: