Example #1
0
    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
Example #2
0
    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
Example #3
0
    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
Example #4
0
    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()
Example #5
0
 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
Example #8
0
    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()
Example #9
0
    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
Example #10
0
 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)
Example #12
0
    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
Example #14
0
    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()
Example #15
0
    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()
Example #16
0
    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()
Example #17
0
 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
Example #18
0
    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)
Example #19
0
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)
Example #21
0
 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
Example #22
0
 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)
Example #24
0
    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
Example #25
0
    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
Example #26
0
    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)
Example #27
0
    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)
Example #28
0
    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
Example #29
0
    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
Example #30
0
    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']