def volume_solutions_numpy(T, P, b, delta, epsilon, a_alpha): r'''Calculate the molar volume solutions to a cubic equation of state using NumPy's `roots` function, which is a power series iterative matrix solution that is very stable but does not have full precision in some cases. Parameters ---------- T : float Temperature, [K] P : float Pressure, [Pa] b : float Coefficient calculated by EOS-specific method, [m^3/mol] delta : float Coefficient calculated by EOS-specific method, [m^3/mol] epsilon : float Coefficient calculated by EOS-specific method, [m^6/mol^2] a_alpha : float Coefficient calculated by EOS-specific method, [J^2/mol^2/Pa] Returns ------- Vs : list[float] Three possible molar volumes, [m^3/mol] Notes ----- A sample region where this method does not obtain the correct solution (SRK EOS for ethane) is as follows: .. figure:: eos/volume_error_numpy_SRK_ethane.png :scale: 100 % :alt: numpy.roots error for SRK eos using ethane_ References ---------- .. [1] Reid, Robert C.; Prausnitz, John M.; Poling, Bruce E. Properties of Gases and Liquids. McGraw-Hill Companies, 1987. ''' RT_inv = R_inv/T P_RT_inv = P*RT_inv B = etas = b*P_RT_inv deltas = delta*P_RT_inv thetas = a_alpha*P_RT_inv*RT_inv epsilons = epsilon*P_RT_inv*P_RT_inv b = (deltas - B - 1.0) c = (thetas + epsilons - deltas*(B + 1.0)) d = -(epsilons*(B + 1.0) + thetas*etas) roots = np.roots([1.0, b, c, d]).tolist() RT_P = R*T/P return [V*RT_P for V in roots]
def Z_from_virial_density_form(T, P, *args): r'''Calculates the compressibility factor of a gas given its temperature, pressure, and molar density-form virial coefficients. Any number of coefficients is supported. .. math:: Z = \frac{PV}{RT} = 1 + \frac{B}{V} + \frac{C}{V^2} + \frac{D}{V^3} + \frac{E}{V^4} \dots Parameters ---------- T : float Temperature, [K] P : float Pressure, [Pa] B to Z : float, optional Virial coefficients, [various] Returns ------- Z : float Compressibility factor at T, P, and with given virial coefficients, [-] Notes ----- For use with B or with B and C or with B and C and D, optimized equations are used to obtain the compressibility factor directly. If more coefficients are provided, uses numpy's roots function to solve this equation. This takes substantially longer as the solution is numerical. If no virial coefficients are given, returns 1, as per the ideal gas law. The units of each virial coefficient are as follows, where for B, n=1, and C, n=2, and so on. .. math:: \left(\frac{\text{m}^3}{\text{mol}}\right)^n Examples -------- >>> Z_from_virial_density_form(300, 122057.233762653, 1E-4, 1E-5, 1E-6, 1E-7) 1.28434940526 References ---------- .. [1] Prausnitz, John M., Rudiger N. Lichtenthaler, and Edmundo Gomes de Azevedo. Molecular Thermodynamics of Fluid-Phase Equilibria. 3rd edition. Upper Saddle River, N.J: Prentice Hall, 1998. .. [2] Walas, Stanley M. Phase Equilibria in Chemical Engineering. Butterworth-Heinemann, 1985. ''' l = len(args) if l == 1: return 1 / 2. + (4 * args[0] * P + R * T)**0.5 / (2 * (R * T)**0.5) # return ((R*T*(4*args[0]*P + R*T))**0.5 + R*T)/(2*P) if l == 2: B, C = args[0], args[1] # A small imaginary part is ignored return ( P * (-(3 * B * R * T / P + R**2 * T**2 / P**2) / (3 * (-1 / 2 + csqrt(3) * 1j / 2) * (-9 * B * R**2 * T**2 / (2 * P**2) - 27 * C * R * T / (2 * P) + csqrt(-4 * (3 * B * R * T / P + R**2 * T**2 / P**2)** (3 + 0j) + (-9 * B * R**2 * T**2 / P**2 - 27 * C * R * T / P - 2 * R**3 * T**3 / P**3)** (2 + 0j)) / 2 - R**3 * T**3 / P**3)** (1 / 3. + 0j)) - (-1 / 2 + csqrt(3) * 1j / 2) * (-9 * B * R**2 * T**2 / (2 * P**2) - 27 * C * R * T / (2 * P) + csqrt(-4 * (3 * B * R * T / P + R**2 * T**2 / P**2)** (3 + 0j) + (-9 * B * R**2 * T**2 / P**2 - 27 * C * R * T / P - 2 * R**3 * T**3 / P**3)** (2 + 0j)) / 2 - R**3 * T**3 / P**3)** (1 / 3. + 0j) / 3 + R * T / (3 * P)) / (R * T)).real if l == 3: # Huge mess. Ideally sympy could optimize a function for quick python # execution. Derived with kate's text highlighting B, C, D = args[0], args[1], args[2] P2 = P**2 RT = R * T BRT = B * RT T2 = T**2 R2 = R**2 RT23 = 3 * R2 * T2 mCRT = -C * RT P2256 = 256 * P2 RT23P2256 = RT23 / (P2256) big1 = (D * RT / P - (-BRT / P - RT23 / (8 * P2))**2 / 12 - RT * (mCRT / (4 * P) - RT * (BRT / (16 * P) + RT23P2256) / P) / P) big3 = (-BRT / P - RT23 / (8 * P2)) big4 = (mCRT / P - RT * (BRT / (2 * P) + R2 * T2 / (8 * P2)) / P) big5 = big3 * (-D * RT / P + RT * (mCRT / (4 * P) - RT * (BRT / (16 * P) + RT23P2256) / P) / P) big2 = 2 * big1 / ( 3 * (big3**3 / 216 - big5 / 6 + big4**2 / 16 + csqrt(big1**3 / 27 + (-big3**3 / 108 + big5 / 3 - big4**2 / 8)**2 / 4))**(1 / 3)) big7 = 2 * BRT / (3 * P) - big2 + 2 * ( big3**3 / 216 - big5 / 6 + big4**2 / 16 + csqrt(big1**3 / 27 + (-big3**3 / 108 + big5 / 3 - big4**2 / 8)**2 / 4))**( 1 / 3) + R2 * T2 / (4 * P2) return (P * (( (csqrt(big7) / 2 + csqrt(4 * BRT / (3 * P) - (-2 * C * RT / P - 2 * RT * (BRT / (2 * P) + R2 * T2 / (8 * P2)) / P) / csqrt(big7) + big2 - 2 * (big3**3 / 216 - big5 / 6 + big4**2 / 16 + csqrt(big1**3 / 27 + (-big3**3 / 108 + big5 / 3 - big4**2 / 8)**2 / 4))** (1 / 3) + R2 * T2 / (2 * P2)) / 2 + RT / (4 * P)))) / R / T).real size = l + 2 # arr = np.ones(size, dtype=np.complex128) # numba: uncomment arr = [1.0] * size # numba: delete arr[-1] = -P / R / T for i in range(l): arr[-3 - i] = args[i] solns = np.roots(arr) for rho in solns: if abs(rho.imag) < 1e-12 and rho.real > 0.0: return float(P / (R * T * rho.real)) raise ValueError("Could not find real root")