def set_PV(self, P, V): self._setup() self._thermal_condition.P = self._P = P if self._N == 1: return self._set_PV_chemical(P, V) # Setup bounderies T_dew, x_dew = self._dew_point.solve_Tx(self._z, P) T_bubble, y_bubble = self._bubble_point.solve_Ty(self._z, P) thermal_condition = self._thermal_condition index = self._index mol = self._mol vapor_mol = self._vapor_mol liquid_mol = self._liquid_mol if V == 1: vapor_mol[index] = mol liquid_mol[index] = 0 thermal_condition.T = T_dew elif V == 0: vapor_mol[index] = 0 liquid_mol[index] = mol thermal_condition.T = T_bubble else: self._refresh_v(V, y_bubble) self._V = V try: T = flx.IQ_interpolation(self._V_at_T, T_bubble, T_dew, 0, 1, self._T, V, self.T_tol, self.V_tol) except: self._v = self._estimate_v(V, y_bubble) T = flx.IQ_interpolation(self._V_at_T, T_bubble, T_dew, 0, 1, self._T, V, self.T_tol, self.V_tol) # In case solver cannot reach the vapor fraction because # the composition does not converge at values that meets the # vapor fraction. V_offset = V - self._V if V_offset < -1e-2: F_mol_vapor = self._F_mol_vle * V v = y_bubble * F_mol_vapor mask = v > mol v[mask] = mol[mask] T = T_bubble elif V_offset > 1e2: v = x_dew * self._F_mol_vle * V mask = v < mol v[mask] = mol[mask] T = T_dew else: v = self._v self._T = thermal_condition.T = T vapor_mol[index] = v liquid_mol[index] = mol - v self._H_hat = self.mixture.xH(self._phase_data, T, P) / self._F_mass
def set_TH(self, T, H): self._setup() if self._N == 1: return self._set_TH_chemical(T, H) self._T = T # Setup bounderies P_dew, x_dew = self._dew_point.solve_Px(self._z, T) P_bubble, y_bubble = self._bubble_point.solve_Py(self._z, T) index = self._index mol = self._mol vapor_mol = self._vapor_mol liquid_mol = self._liquid_mol phase_data = self._phase_data # Check if super heated vapor vapor_mol[index] = mol liquid_mol[index] = 0 H_dew = self.mixture.xH(phase_data, T, P_dew) dH_dew = (H - H_dew) if dH_dew >= 0: self._T = self.mixture.xsolve_T(phase_data, H, T, P_dew) self._thermal_condition.P = P_dew return # Check if subcooled liquid vapor_mol[index] = 0 liquid_mol[index] = mol H_bubble = self.mixture.xH(phase_data, T, P_bubble) dH_bubble = (H - H_bubble) if dH_bubble <= 0: self._T = self.mixture.xsolve_T(phase_data, H, T, P_bubble) self._thermal_condition.P = P_bubble return # Guess overall vapor fraction, and vapor flow rates V = self._V or dH_bubble / (H_dew - H_bubble) # Guess composition in the vapor is a weighted average of boiling points self._refresh_v(V, y_bubble) F_mass = self._F_mass self._H_hat = H / F_mass try: P = flx.IQ_interpolation(self._H_hat_at_P, P_bubble, P_dew, H_bubble / F_mass, H_dew / F_mass, self._P, self._H_hat, self.P_tol, self.H_hat_tol) except: self._v = self._estimate_v(V, y_bubble) P = flx.IQ_interpolation(self._H_hat_at_P, P_bubble, P_dew, H_bubble / F_mass, H_dew / F_mass, self._P, self._H_hat, self.P_tol, self.H_hat_tol) self._P = self._thermal_condition.P = P self._thermal_condition.T = T
def set_TV(self, T, V): self._setup() mol = self._mol thermal_condition = self._thermal_condition thermal_condition.T = self._T = T if self._N == 1: return self._set_TV_chemical(T, V) P_dew, x_dew = self._dew_point.solve_Px(self._z, T) P_bubble, y_bubble = self._bubble_point.solve_Py(self._z, T) if V == 1: self._vapor_mol[self._index] = self._mol self._liquid_mol[self._index] = 0 thermal_condition.P = P_dew elif V == 0: self._vapor_mol[self._index] = 0 self._liquid_mol[self._index] = self._mol thermal_condition.P = P_bubble else: self._V = V self._refresh_v(V, y_bubble) try: P = flx.IQ_interpolation(self._V_at_P, P_bubble, P_dew, 0, 1, self._P, self._V, self.P_tol, self.V_tol) except: self._V = V self._v = self._estimate_v(V, y_bubble) P = flx.IQ_interpolation(self._V_at_P, P_bubble, P_dew, 0, 1, self._P, V, self.P_tol, self.V_tol) # In case solver cannot reach the vapor fraction because # the composition does not converge at values that meets the # vapor fraction. V_offset = V - self._V if V_offset < -1e-2: F_mol_vapor = self._F_mol_vle * V v = y_bubble * F_mol_vapor mask = v > mol v[mask] = mol[mask] P = P_bubble elif V_offset > 1e2: v = x_dew * self._F_mol_vle * V mask = v < mol v[mask] = mol[mask] P = P_dew else: v = self._v self._P = thermal_condition.P = P self._vapor_mol[self._index] = v self._liquid_mol[self._index] = mol - v self._H_hat = self.mixture.xH(self._phase_data, T, P) / self._F_mass
def solve_Px(self, z, T): """ 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])) """ z_norm = z / z.sum() Psats = array([i(T) for i in self.Psats], dtype=float) z_over_Psats = z / Psats args = (T, z_norm, z_over_Psats) self.T = T f = self._P_error P_guess = self.P or self._P_ideal(z_over_Psats) try: P = flx.aitken_secant(f, P_guess, P_guess - 10, 1e-3, 5e-12, args, checkiter=False) except (InfeasibleRegion, DomainError): Pmin = self.Pmin Pmax = self.Pmax P = flx.IQ_interpolation(f, Pmin, Pmax, f(Pmin, *args), f(Pmax, *args), P_guess, 1e-3, 5e-12, args, checkiter=False, checkbounds=False) self.x = fn.normalize(self.x) return P, self.x.copy()
def _design(self): effluent = self.outs[1] v_0 = effluent.F_vol tau = self._tau tau_0 = self.tau_0 V_wf = self.V_wf Design = self.design_results if self.autoselect_N: N = self.N_at_minimum_capital_cost elif self.V: f = lambda N: v_0 / N / V_wf * (tau + tau_0) / (1 - 1 / N) - self.V N = flx.IQ_interpolation(f, self.Nmin, self.Nmax, xtol=0.01, ytol=0.5, checkbounds=False) N = ceil(N) else: N = self._N Design.update(size_batch(v_0, tau, tau_0, N, V_wf)) Design['Number of reactors'] = N duty = self.Hnet Design['Reactor duty'] = abs(duty) self.heat_utilities[0](duty, self.T)
def solve_phase_fraction(zs, Ks, guess): """ Return phase fraction for N-component binary equilibrium through numerically solving an objective function. """ args = (zs, Ks) f = phase_fraction_objective_function if Ks.max() < 1.0: return 0. if Ks.min() > 1.0: return 1. y0 = f(0., *args) y1 = f(1., *args) if y0 > y1 > 0.: return 1 if y1 > y0 > 0.: return 0. if y0 < y1 < 0.: return 1. if y1 < y0 < 0.: return 0. return flx.IQ_interpolation(f, 0., 1., y0, y1, guess, 1e-16, 1e-16, args, checkiter=False)
def load_titer(self, titer): """ Load titer specification Parameters ---------- titer : float Titer for fermentors in g products / L effluent. Notes ----- Substrate concentration in bioreactor feed is adjusted to satisfy this specification. Warnings -------- Changing the titer affects the productivity. """ f = self._titer_objective_function feed = self.feed feed.imol[self.products] = 0. substates = feed.imass[self.substrates].sum() self.titer = titer try: flx.aitken_secant(f, substates, ytol=1e-5) except: flx.IQ_interpolation(f, 1e-12, 0.50 * feed.F_mass, ytol=1e-5, maxiter=100) self.reactor.tau = titer / self.productivity
def solve_Tx(self, z, P): """ 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])) """ f = self._T_error z_norm = z / z.sum() zP = z * P args = (P, z_norm, zP) self.P = P T_guess = self.T or self._T_ideal(zP) try: T = flx.aitken_secant(f, T_guess, T_guess + 1e-3, 1e-9, 5e-12, args, checkiter=False) except (InfeasibleRegion, DomainError): Tmin = self.Tmin Tmax = self.Tmax T = flx.IQ_interpolation(f, Tmin, Tmax, f(Tmin, *args), f(Tmax, *args), T_guess, 1e-9, 5e-12, args, checkiter=False, checkbounds=False) self.x = fn.normalize(self.x) return T, self.x.copy()
def set_TV(self, T, V): self._setup() mol = self._mol thermal_condition = self._thermal_condition thermal_condition.T = self._T = T if self._N == 0: raise RuntimeError('no chemicals present to perform VLE') if self._N == 1: return self._set_TV_chemical(T, V) if V == 1: P_dew, x_dew = self._dew_point.solve_Px(self._z, T) self._vapor_mol[self._index] = self._mol self._liquid_mol[self._index] = 0 thermal_condition.P = P_dew elif V == 0: P_bubble, y_bubble = self._bubble_point.solve_Py(self._z, T) self._vapor_mol[self._index] = 0 self._liquid_mol[self._index] = self._mol thermal_condition.P = P_bubble else: P_dew, x_dew = self._dew_point.solve_Px(self._z, T) P_bubble, y_bubble = self._bubble_point.solve_Py(self._z, T) self._V = V self._refresh_v(V, y_bubble) P = flx.IQ_interpolation(self._V_err_at_P, P_bubble, P_dew, 0 - V, 1 - V, self._P, self.P_tol, self.V_tol, (self._V, ), checkiter=False, checkbounds=False) # In case solver cannot reach the vapor fraction because # the composition does not converge at values that meets the # vapor fraction. V_offset = V - self._V if V_offset < -1e-2: F_mol_vapor = self._F_mol_vle * V v = y_bubble * F_mol_vapor mask = v > mol v[mask] = mol[mask] P = P_bubble elif V_offset > 1e2: v = x_dew * self._F_mol_vle * V mask = v < mol v[mask] = mol[mask] P = P_dew else: v = self._v self._P = thermal_condition.P = P self._vapor_mol[self._index] = v self._liquid_mol[self._index] = mol - v self._H_hat = self.mixture.xH(self._phase_data, T, P) / self._F_mass
def _T_ideal(self, z_over_P): f = self._T_error_ideal args = (z_over_P,) Tmin = self.Tmin Tmax = self.Tmax T = flx.IQ_interpolation(f, Tmin, Tmax, f(Tmin, *args), f(Tmax, *args), None, 1e-9, 5e-12, args, checkbounds=False) return T
def _run(self): steam = self.ins[1] steam_mol = steam.F_mol or 1. f = self.pressure_objective_function steam_mol = flx.IQ_interpolation(f, *flx.find_bracket( f, 0., steam_mol, None, None), xtol=1e-2, ytol=1e-4, maxiter=500, checkroot=False)
def set_TH(self, T, H): self._setup() if self._N == 0: raise RuntimeError('no chemicals present to perform VLE') if self._N == 1: return self._set_TH_chemical(T, H) self._T = T index = self._index mol = self._mol vapor_mol = self._vapor_mol liquid_mol = self._liquid_mol phase_data = self._phase_data # Check if super heated vapor P_dew, x_dew = self._dew_point.solve_Px(self._z, T) vapor_mol[index] = mol liquid_mol[index] = 0 H_dew = self.mixture.xH(phase_data, T, P_dew) dH_dew = (H - H_dew) if dH_dew >= 0: self._T = self.mixture.xsolve_T(phase_data, H, T, P_dew) self._thermal_condition.P = P_dew return # Check if subcooled liquid P_bubble, y_bubble = self._bubble_point.solve_Py(self._z, T) vapor_mol[index] = 0 liquid_mol[index] = mol H_bubble = self.mixture.xH(phase_data, T, P_bubble) dH_bubble = (H - H_bubble) if dH_bubble <= 0: self._T = self.mixture.xsolve_T(phase_data, H, T, P_bubble) self._thermal_condition.P = P_bubble return # Guess overall vapor fraction, and vapor flow rates V = self._V or dH_bubble / (H_dew - H_bubble) # Guess composition in the vapor is a weighted average of boiling points self._refresh_v(V, y_bubble) F_mass = self._F_mass H_hat = H / F_mass P = flx.IQ_interpolation(self._H_hat_err_at_P, P_bubble, P_dew, H_bubble / F_mass - H_hat, H_dew / F_mass - H_hat, self._P, self.P_tol, self.H_hat_tol, (H_hat, ), checkiter=False, checkbounds=False) self._P = self._thermal_condition.P = P self._thermal_condition.T = T
def _solve_Underwood_constant(self, alpha_mean, alpha_LK): q = self._get_feed_quality() z_f = self.ins[0].get_normalized_mol(self._IDs_vle) args = (q, z_f, alpha_mean) ub = np.inf lb = -np.inf bracket = flx.find_bracket(objective_function_Underwood_constant, 1.0, alpha_LK, lb, ub, args) theta = flx.IQ_interpolation(objective_function_Underwood_constant, *bracket, args=args, checkiter=False, checkbounds=False) return theta
def solve_Py(self, z, T): """ 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])) """ self.T = T Psat = array([i(T) for i in self.Psats]) z_norm = z / z.sum() z_Psat_gamma_pcf = z * Psat * self.gamma(z_norm, T) * self.pcf(z_norm, T) f = self._P_error args = (T, z_Psat_gamma_pcf) P_guess = self.P or self._P_ideal(z_Psat_gamma_pcf) try: P = flx.aitken_secant(f, P_guess, P_guess-1, 1e-3, 1e-9, args, checkiter=False) except (InfeasibleRegion, DomainError): Pmin = self.Pmin; Pmax = self.Pmax P = flx.IQ_interpolation(f, Pmin, Pmax, f(Pmin, *args), f(Pmax, *args), P_guess, 1e-3, 5e-12, args, checkiter=False, checkbounds=False) self.y = fn.normalize(self.y) return P, self.y.copy()
def solve_Ty(self, z, P): """ 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])) """ self.P = P f = self._T_error z_norm = z / z.sum() z_over_P = z_norm/P args = (P, z_over_P, z_norm) T_guess = self.T or self._T_ideal(z_over_P) try: T = flx.aitken_secant(f, T_guess, T_guess + 1e-3, 1e-9, 5e-12, args, checkiter=False) except (InfeasibleRegion, DomainError): Tmin = self.Tmin; Tmax = self.Tmax T = flx.IQ_interpolation(f, Tmin, Tmax, f(Tmin, *args), f(Tmax, *args), T_guess, 1e-9, 5e-12, args, checkiter=False, checkbounds=False) self.y = fn.normalize(self.y) return T, self.y.copy()
def _run(self): eff, sludge = self.outs solubles, solids = self.solubles, self.solids mixed = self._mixed mixed.mix_from(self.ins) eff.T = sludge.T = mixed.T sludge.copy_flow(mixed, solids, remove=True) # all solids go to sludge eff.copy_flow(mixed, solubles) flx.IQ_interpolation(f=self._mc_at_split, x0=1e-3, x1=1. - 1e-3, args=(solubles, mixed, eff, sludge, self.sludge_moisture), checkbounds=False) self._set_split_at_mc()
def _design(self): effluent = self.effluent v_0 = effluent.F_vol tau = self._tau tau_0 = self.tau_0 V_wf = self.V_wf Design = self.design_results if self.V: f = lambda N: v_0 / N / V_wf * (tau + tau_0) / (1 - 1 / N) - self.V N = flx.IQ_interpolation(f, self.Nmin, self.Nmax, xtol=0.01, ytol=0.5, checkbounds=False) N = ceil(N) else: N = self._N dct = size_batch(v_0, tau, tau_0, N, V_wf) Design['Crystallizer volume'] = volume = dct.pop('Reactor volume') Design.update(dct) Design['Number of crystallizers'] = N self.heat_utilities[0](self.Hnet, self.T) self.power_utility.consumption = self.kW * V_wf * volume * N
def set_PH(self, P, H): self._setup() self._thermal_condition.P = self._P = P if self._N == 1: return self._set_PH_chemical(P, H) # Setup bounderies T_dew, x_dew = self._dew_point.solve_Tx(self._z, P) T_bubble, y_bubble = self._bubble_point.solve_Ty(self._z, P) index = self._index mol = self._mol vapor_mol = self._vapor_mol liquid_mol = self._liquid_mol # Check if super heated vapor vapor_mol[index] = mol liquid_mol[index] = 0 H_dew = self.mixture.xH(self._phase_data, T_dew, P) dH_dew = H - H_dew if dH_dew >= 0: self._thermal_condition.T = self.mixture.xsolve_T( self._phase_data, H, T_dew, P) # Check if subcooled liquid vapor_mol[index] = 0 liquid_mol[index] = mol H_bubble = self.mixture.xH(self._phase_data, T_dew, P) dH_bubble = H - H_bubble if dH_bubble <= 0: self._thermal_condition.T = self.mixture.xsolve_T( self._phase_data, H, T_bubble, P) # Guess T, overall vapor fraction, and vapor flow rates self._V = V = self._V or dH_bubble / (H_dew - H_bubble) self._refresh_v(V, y_bubble) F_mass = self._F_mass self._H_hat = H / F_mass self._T = self._thermal_condition.T = flx.IQ_interpolation( self._H_hat_at_T, T_bubble, T_dew, H_bubble / F_mass, H_dew / F_mass, self._T, self._H_hat, self.T_tol, self.H_hat_tol)
def test_bounded_solvers(): x0, x1 = [-10, 20] f = lambda x: x**3 - 40 + 2 * x assert_allclose(flx.IQ_interpolation(f, x0, x1), 3.225240462791775, rtol=1e-5) assert_allclose(flx.bisection(f, x0, x1), 3.225240461761132, rtol=1e-5) assert_allclose(flx.false_position(f, x0, x1), 3.2252404627266342, rtol=1e-5) assert_allclose(flx.find_bracket(f, -5, 5), (-5, 5, -175, 95), rtol=1e-5) assert_allclose(flx.find_bracket(f, -5, 0), (-5, 10.0, -175, 980.0), rtol=1e-5) assert_allclose(flx.find_bracket(f, -10, -5), (-10, 5.0, -1060, 95.0), rtol=1e-5) assert_allclose(flx.find_bracket(f, 5, 10), (-6.073446327683616, 10, -276.1765907762578, 980), rtol=1e-5) assert_allclose(flx.find_bracket(f, 10, 5), (-6.073446327683616, 10, -276.1765907762578, 980), rtol=1e-5)
def solve_phase_fraction(zs, Ks, guess): """ Return phase fraction for N-component binary equilibrium through numerically solving an objective function. """ args = (zs, Ks) f = phase_fraction_objective_function f_min = f(0., *args) f_max = f(1., *args) if f_min > f_max > 0.: return 1. if f_max < f_min < 0.: return 0. return flx.IQ_interpolation(f, 0., 1., f_min, f_max, guess, 1e-16, 1e-16, args, checkiter=False)
def _Ty_ideal(self, z_over_P): f = self._T_error_ideal y = z_over_P.copy() args = (z_over_P, y) Tmin = self.Tmin Tmax = self.Tmax fmax = f(Tmin, *args) if fmax < 0.: return Tmin, y fmin = f(Tmax, *args) if fmin > 0.: return Tmax, y T = flx.IQ_interpolation(f, Tmin, Tmax, fmax, fmin, None, 1e-9, 5e-12, args, checkiter=False, checkbounds=False) return T, y
def _Tx_ideal(self, zP): f = self._T_error_ideal Tmin = self.Tmin Tmax = self.Tmax x = zP.copy() args = (zP, x) fmin = f(Tmin, *args) if fmin > 0.: return Tmin, x fmax = f(Tmax, *args) if fmax < 0.: return Tmax, x T = flx.IQ_interpolation(f, Tmin, Tmax, fmin, fmax, None, 1e-9, 5e-12, args, checkiter=False, checkbounds=False) return T, x
def solve_phase_fraction_Rashford_Rice(zs, Ks, guess, za, zb): """ Return phase fraction for N-component binary equilibrium by numerically solving the Rashford Rice equation. """ if Ks.max() < 1.0 and not za: return 0. if Ks.min() > 1.0 and not zb: return 1. K_minus_1 = Ks - 1. args = (- zs * K_minus_1, K_minus_1, za, zb) f = phase_fraction_objective_function x0 = 0. x1 = 1. y0 = -np.inf if za else f(x0, *args) y1 = np.inf if zb else f(x1, *args) if y0 > y1 > 0.: return 1 if y1 > y0 > 0.: return 0. if y0 < y1 < 0.: return 1. if y1 < y0 < 0.: return 0. x0, x1, y0, y1 = flx.find_bracket(f, x0, x1, y0, y1, args, tol=5e-8) if abs(x1 - x0) < 1e-6: return (x0 + x1) / 2. return flx.IQ_interpolation(f, x0, x1, y0, y1, guess, 1e-16, 1e-16, args, checkiter=False)
def set_PV(self, P, V): self._setup() self._thermal_condition.P = self._P = P if self._N == 0: raise RuntimeError('no chemicals present to perform VLE') if self._N == 1: return self._set_PV_chemical(P, V) # Setup bounderies thermal_condition = self._thermal_condition index = self._index mol = self._mol vapor_mol = self._vapor_mol liquid_mol = self._liquid_mol if V == 1: T_dew, x_dew = self._dew_point.solve_Tx(self._z, P) vapor_mol[index] = mol liquid_mol[index] = 0 thermal_condition.T = T_dew elif V == 0: T_bubble, y_bubble = self._bubble_point.solve_Ty(self._z, P) vapor_mol[index] = 0 liquid_mol[index] = mol thermal_condition.T = T_bubble else: T_dew, x_dew = self._dew_point.solve_Tx(self._z, P) T_bubble, y_bubble = self._bubble_point.solve_Ty(self._z, P) self._refresh_v(V, y_bubble) self._V = V T = flx.IQ_interpolation(self._V_err_at_T, T_bubble, T_dew, 0 - V, 1 - V, self._T, self.T_tol, self.V_tol, (V, ), checkiter=False, checkbounds=False) # In case solver cannot reach the vapor fraction because # the composition does not converge at values that meets the # vapor fraction. V_offset = V - self._V if V_offset < -1e-2: F_mol_vapor = self._F_mol_vle * V v = y_bubble * F_mol_vapor mask = v > mol v[mask] = mol[mask] T = T_bubble elif V_offset > 1e2: v = x_dew * self._F_mol_vle * V mask = v < mol v[mask] = mol[mask] T = T_dew else: v = self._v self._T = thermal_condition.T = T vapor_mol[index] = v liquid_mol[index] = mol - v self._H_hat = self.mixture.xH(self._phase_data, T, P) / self._F_mass
def set_PH(self, P, H): self._setup() thermal_condition = self._thermal_condition thermal_condition.P = self._P = P if self._N == 0: thermal_condition.T = self.mixture.xsolve_T( self._phase_data, H, thermal_condition.T, P) return if self._N == 1: return self._set_PH_chemical(P, H) # Setup bounderies index = self._index mol = self._mol vapor_mol = self._vapor_mol liquid_mol = self._liquid_mol # Check if subcooled liquid T_bubble, y_bubble = self._bubble_point.solve_Ty(self._z, P) vapor_mol[index] = 0 liquid_mol[index] = mol H_bubble = self.mixture.xH(self._phase_data, T_bubble, P) dH_bubble = H - H_bubble if dH_bubble <= 0: thermal_condition.T = self.mixture.xsolve_T( self._phase_data, H, T_bubble, P) return # Check if super heated vapor T_dew, x_dew = self._dew_point.solve_Tx(self._z, P) vapor_mol[index] = mol liquid_mol[index] = 0 H_dew = self.mixture.xH(self._phase_data, T_dew, P) dH_dew = H - H_dew if dH_dew >= 0: thermal_condition.T = self.mixture.xsolve_T( self._phase_data, H, T_dew, P) return # Guess T, overall vapor fraction, and vapor flow rates self._V = V = self._V or dH_bubble / (H_dew - H_bubble) self._refresh_v(V, y_bubble) F_mass = self._F_mass H_hat = H / F_mass H_hat_bubble = H_bubble / F_mass H_hat_dew = H_dew / F_mass T = flx.IQ_interpolation(self._H_hat_err_at_T, T_bubble, T_dew, H_hat_bubble - H_hat, H_hat_dew - H_hat, self._T, self.T_tol, self.H_hat_tol, (H_hat, ), checkiter=False, checkbounds=False) # Make sure enthalpy balance is correct self._T = thermal_condition.T = self.mixture.xsolve_T( self._phase_data, H, T, P) self._H_hat = H_hat
def solve_Tx(self, z, P): """ 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])) """ positives = z > 0. N = positives.sum() if N == 0: raise ValueError('no positive components present') if N == 1: T = self.chemicals[fn.first_true_index(positives)].Tsat(P) x = z.copy() else: if P > self.Pmax: P = self.Pmax elif P < self.Pmin: P = self.Pmin f = self._T_error z_norm = z / z.sum() zP = z * P T_guess, x = self._Tx_ideal(zP) args = (P, z_norm, zP, x) try: T = flx.aitken_secant(f, T_guess, T_guess + 1e-3, 1e-9, 5e-12, args, checkiter=False) except (InfeasibleRegion, DomainError): Tmin = self.Tmin Tmax = self.Tmax T = flx.IQ_interpolation(f, Tmin, Tmax, f(Tmin, *args), f(Tmax, *args), T_guess, 1e-9, 5e-12, args, checkiter=False, checkbounds=False) return T, fn.normalize(x)
def solve_Px(self, z, T): """ 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])) """ positives = z > 0. N = positives.sum() if N == 0: raise ValueError('no positive components present') if N == 1: P = self.chemicals[fn.first_true_index(z)].Psat(T) x = z.copy() else: if T > self.Tmax: T = self.Tmax elif T < self.Tmin: T = self.Tmin z_norm = z / z.sum() Psats = np.array([i(T) for i in self.Psats], dtype=float) z_over_Psats = z / Psats P_guess, x = self._Px_ideal(z_over_Psats) args = (T, z_norm, z_over_Psats, x) f = self._P_error try: P = flx.aitken_secant(f, P_guess, P_guess - 10, 1e-3, 5e-12, args, checkiter=False) except (InfeasibleRegion, DomainError): Pmin = self.Pmin Pmax = self.Pmax P = flx.IQ_interpolation(f, Pmin, Pmax, f(Pmin, *args), f(Pmax, *args), P_guess, 1e-3, 5e-12, args, checkiter=False, checkbounds=False) return P, fn.normalize(x)
def _design(self): B_eff = self.boiler_efficiency steam_demand = self.steam_demand Design = self.design_results self._load_utility_agents() mol_steam = sum([i.flow for i in self.steam_utilities]) feed_solids, feed_gas, makeup_water, feed_CH4, lime, chems = self.ins emissions, blowdown_water, ash_disposal, remainder_feed_solids, remainder_feed_gas = self.outs chemicals = emissions.chemicals if not lime.price: lime.price = 0.19937504680689402 if not chems.price: chems.price = 4.995862254032183 H_steam = sum([i.duty for i in self.steam_utilities]) side_steam = self.side_steam if side_steam: H_steam += side_steam.H mol_steam += side_steam.F_mol steam_demand.imol['7732-18-5'] = mol_steam duty_over_mol = 39000 # kJ / mol-superheated steam emissions_mol = emissions.mol emissions.T = self.agent.T emissions.P = 101325 emissions.phase = 'g' self.combustion_reactions = combustion_rxns = chemicals.get_combustion_reactions( ) non_empty_feeds = [ i for i in (feed_solids, feed_gas) if not i.isempty() ] def calculate_excess_heat_at_natual_gas_flow(natural_gas_flow): if natural_gas_flow: natural_gas_flow = abs(natural_gas_flow) feed_CH4.imol['CH4'] = natural_gas_flow else: feed_CH4.empty() H_combustion = feed_CH4.H + feed_CH4.HHV emissions_mol[:] = feed_CH4.mol for feed in non_empty_feeds: H_combustion += feed.H + feed.HHV emissions_mol[:] += feed.mol combustion_rxns.force_reaction(emissions_mol) emissions.imol['O2'] = 0 H_content = B_eff * H_combustion - emissions.H #: [float] Total steam produced by the boiler (kmol/hr) self.total_steam = H_content / duty_over_mol Design['Flow rate'] = self.total_steam * 18.01528 # Excess heat available H_excess = H_content - H_steam return H_excess remainder_feed_gas.empty() remainder_feed_solids.empty() gas_fraction_burned = solids_fraction_burned = 1. excess_heat = calculate_excess_heat_at_natual_gas_flow(0) if excess_heat < -1: f = calculate_excess_heat_at_natual_gas_flow lb = 0. ub = 100. while f(ub) < 0.: lb = ub ub *= 2 flx.IQ_interpolation(f, lb, ub, xtol=1, ytol=1) elif excess_heat > 1: def calculate_excess_heat_with_diverted_gas(fraction_burned): H_combustion = fraction_burned * ( feed_gas.H + feed_gas.HHV) + (feed_solids.H + feed_solids.HHV) emissions_mol[:] = fraction_burned * feed_gas.mol + feed_solids.mol combustion_rxns.force_reaction(emissions_mol) emissions.imol['O2'] = 0 H_content = B_eff * H_combustion - emissions.H #: [float] Total steam produced by the boiler (kmol/hr) self.total_steam = H_content / duty_over_mol Design['Flow rate'] = self.total_steam * 18.01528 # Excess heat available H_excess = H_content - H_steam return H_excess if f(0.) > 1.: gas_fraction_burned = 0. else: f = calculate_excess_heat_with_diverted_gas gas_fraction_burned = flx.IQ_interpolation(f, 0., 1., xtol=1, ytol=1) if gas_fraction_burned == 0.: def calculate_excess_heat_with_diverted_solids( fraction_burned): H_combustion = fraction_burned * (feed_solids.H + feed_solids.HHV) emissions_mol[:] = fraction_burned * feed_solids.mol combustion_rxns.force_reaction(emissions_mol) emissions.imol['O2'] = 0 H_content = B_eff * H_combustion - emissions.H #: [float] Total steam produced by the boiler (kmol/hr) self.total_steam = H_content / duty_over_mol Design['Flow rate'] = self.total_steam * 18.01528 # Excess heat available H_excess = H_content - H_steam return H_excess f = calculate_excess_heat_with_diverted_solids solids_fraction_burned = flx.IQ_interpolation(f, 0., 1., xtol=1, ytol=1) remainder_feed_solids.mol = (1. - solids_fraction_burned) * feed_solids.mol remainder_feed_gas.mol = (1. - gas_fraction_burned) * feed_gas.mol hus_heating = bst.HeatUtility.sum_by_agent(tuple(self.steam_utilities)) for hu in hus_heating: hu.reverse() self.heat_utilities = tuple(hus_heating) water_index = chemicals.index('7732-18-5') blowdown_water.mol[water_index] = makeup_water.mol[water_index] = ( self.total_steam * self.boiler_blowdown * 1 / (1 - self.RO_rejection)) ash_IDs = [i.ID for i in self.chemicals if not i.formula] emissions_mol = emissions.mol if 'SO2' in chemicals: ash_IDs.append('CaSO4') lime_index = chemicals.index(self._ID_lime) self.desulfurization_reaction.force_reaction(emissions) # FGD lime scaled based on SO2 generated, # 20% stoichiometetric excess based on P52 of ref [1] lime.mol[lime_index] = lime_mol = max( 0, -emissions_mol[lime_index] * 1.2) emissions_mol[emissions_mol < 0.] = 0. # About 0.4536 kg/hr of boiler chemicals are needed per 234484 kg/hr steam produced chems.imol['Ash'] = boiler_chems = 1.9345e-06 * Design['Flow rate'] ash_disposal.empty() ash_disposal.copy_flow(emissions, IDs=tuple(ash_IDs), remove=True) ash_disposal.imol['Ash'] += boiler_chems dry_ash = ash_disposal.F_mass ash_disposal.imass['Water'] = moisture = dry_ash * 0.3 # ~20% moisture if 'SO2' in chemicals: if self._ID_lime == '1305-62-0': # Ca(OH)2 lime.imol['Water'] = 4 * lime_mol # Its a slurry else: # CaO lime.imol['Water'] = 5 * lime_mol
def _design(self): B_eff = self.boiler_efficiency TG_eff = self.turbogenerator_efficiency steam_demand = self.steam_demand Design = self.design_results chemicals = self.chemicals self._load_utility_agents() mol_steam = sum([i.flow for i in self.steam_utilities]) feed_solids, feed_gas, makeup_water, feed_CH4, lime, chems = self.ins emissions, blowdown_water, ash_disposal = self.outs if not lime.price: lime.price = 0.19937504680689402 if not chems.price: chems.price = 4.995862254032183 H_steam = sum([i.duty for i in self.steam_utilities]) side_steam = self.side_steam if side_steam: H_steam += side_steam.H mol_steam += side_steam.F_mol steam_demand.imol['7732-18-5'] = mol_steam duty_over_mol = 39000 # kJ / mol-superheated steam emissions_mol = emissions.mol emissions.T = self.agent.T emissions.P = 101325 emissions.phase = 'g' self.combustion_reactions = combustion_rxns = chemicals.get_combustion_reactions( ) non_empty_feeds = [ i for i in (feed_solids, feed_gas) if not i.isempty() ] def calculate_excess_electricity_at_natual_gas_flow(natural_gas_flow): if natural_gas_flow: natural_gas_flow = abs(natural_gas_flow) feed_CH4.imol['CH4'] = natural_gas_flow else: feed_CH4.empty() H_combustion = feed_CH4.H + feed_CH4.HHV emissions_mol[:] = feed_CH4.mol for feed in non_empty_feeds: H_combustion += feed.H + feed.HHV emissions_mol[:] += feed.mol combustion_rxns.force_reaction(emissions_mol) emissions.imol['O2'] = 0 H_content = B_eff * H_combustion - emissions.H #: [float] Total steam produced by the boiler (kmol/hr) self.total_steam = H_content / duty_over_mol Design['Flow rate'] = flow_rate = self.total_steam * 18.01528 # Heat available for the turbogenerator H_electricity = H_content - H_steam if H_electricity < 0: self.cooling_duty = electricity = 0 else: electricity = H_electricity * TG_eff self.cooling_duty = electricity - H_electricity Design['Work'] = work = electricity / 3600 boiler = self.cost_items['Boiler'] rate_boiler = boiler.kW * flow_rate / boiler.S return work - self.electricity_demand - rate_boiler excess_electricity = calculate_excess_electricity_at_natual_gas_flow(0) if excess_electricity < 0: f = calculate_excess_electricity_at_natual_gas_flow lb = 0. ub = -excess_electricity * 3600 / feed_CH4.chemicals.CH4.LHV while f(ub) < 0.: lb = ub ub *= 2 flx.IQ_interpolation(f, lb, ub, xtol=1, ytol=1) hu_cooling = bst.HeatUtility() hu_cooling(self.cooling_duty, steam_demand.T) hus_heating = bst.HeatUtility.sum_by_agent(tuple(self.steam_utilities)) for hu in hus_heating: hu.reverse() self.heat_utilities = (*hus_heating, hu_cooling) water_index = chemicals.index('7732-18-5') blowdown_water.mol[water_index] = makeup_water.mol[water_index] = ( self.total_steam * self.boiler_blowdown * 1 / (1 - self.RO_rejection)) ash_IDs = [i.ID for i in self.chemicals if not i.formula] emissions_mol = emissions.mol if 'SO2' in chemicals: ash_IDs.append('CaSO4') lime_index = emissions.chemicals.index(self._ID_lime) sulfur_index = emissions.chemicals.index('CaSO4') self.desulfurization_reaction.force_reaction(emissions) # FGD lime scaled based on SO2 generated, # 20% stoichiometetric excess based on P52 of ref [1] lime.mol[lime_index] = lime_mol = max( 0, emissions_mol[sulfur_index] * 1.2) emissions_mol[emissions_mol < 0.] = 0. else: lime.empty() # About 0.4536 kg/hr of boiler chemicals are needed per 234484 kg/hr steam produced chems.imol['Ash'] = boiler_chems = 1.9345e-06 * Design['Flow rate'] ash_disposal.empty() ash_disposal.copy_flow(emissions, IDs=tuple(ash_IDs), remove=True) ash_disposal.imol['Ash'] += boiler_chems dry_ash = ash_disposal.F_mass ash_disposal.imass['Water'] = moisture = dry_ash * 0.3 # ~20% moisture Design['Ash disposal'] = dry_ash + moisture if 'SO2' in chemicals: if self._ID_lime == '1305-62-0': # Ca(OH)2 lime.imol['Water'] = 4 * lime_mol # Its a slurry else: # CaO lime.imol['Water'] = 5 * lime_mol
def _run(self): out_wt_solids, liq = self.outs ins = self.ins self.load_components() if self.V == 0: out_wt_solids.copy_like(ins[0]) liq.empty() return if self.V_definition == 'Overall': P = tuple(self.P) self.P = list(P) for i in range(self._N_evap - 1): if self._V_overall(0.) > self.V: self.P.pop() self.load_components() else: break self.P = P self._V_first_effect = flx.IQ_interpolation( self._V_overall_objective_function, 0., 1., None, None, self._V_first_effect, xtol=1e-9, ytol=1e-6, checkiter=False) V_overall = self.V else: V_overall = self._V_overall(self.V) n = self._N_evap # Number of evaporators components = self.components evaporators = components['evaporators'] condenser = components['condenser'] mixer = components['mixer'] last_evaporator = evaporators[-1] # Condensing vapor from last effector outs_vap = last_evaporator.outs[0] condenser.ins[:] = [outs_vap] condenser._run() outs_liq = [condenser.outs[0]] # list containing all output liquids # Unpack other output streams out_wt_solids.copy_like(last_evaporator.outs[1]) for i in range(1, n): evap = evaporators[i] outs_liq.append(evap.outs[2]) # Mix liquid streams mixer.ins[:] = outs_liq mixer._run() liq.copy_like(mixer.outs[0]) mixed_stream = MultiStream(thermo=self.thermo) mixed_stream.copy_flow(self.ins[0]) mixed_stream.vle(P=last_evaporator.P, V=V_overall) out_wt_solids.mol = mixed_stream.imol['l'] liq.mol = mixed_stream.imol['g']