def test_horner(): from fluids.numerics import horner assert_allclose(horner([1.0, 3.0], 2.0), 5.0) assert_allclose(horner([3.0], 2.0), 3.0) poly = [1.12, 432.32, 325.5342, .235532, 32.235] assert_allclose(horner_and_der2(poly, 3.0), (14726.109396, 13747.040732, 8553.7884)) assert_allclose(horner_and_der3(poly, 3.0), (14726.109396, 13747.040732, 8553.7884, 2674.56))
def TDE_RIXExpansion(T, Bs, Cs, wavelength=589.26e-9): r'''Calculates the refractive index of a pure liquid at a given temperature, and wavelength, using the NIST TDE RIXExpansion formula [1]_. .. math:: n(T, \lambda) = \sum_{i=0}^{i} B_i t^i + \sum_j C_j w^j .. math:: t = T - 298.15 .. math:: w = WL\times 10^{9} - 589.26 Parameters ---------- T : float Temperature of the fluid [K] Bs : list[float] Polynomial temperature expansion coefficients, in reverse order to the polynomial (as needed for efficient computation with horner's method'), [-] Cs : list[float] Polynomial wavelength expansion coefficients, in reverse order to the polynomial (as needed for efficient computation with horner's method'), [-] wavelength : float Wavelength of fluid [meters] Returns ------- RI : float Refractive index of the pure fluid, [-] Notes ----- Examples -------- >>> TDE_RIXExpansion(330.0, Bs=[-0.000125041, 1.33245], Cs=[1.20771e-7, -3.56795e-5, 0.0], wavelength=589.26e-9*.7) 1.33854894426073 References ---------- .. [1] "ThermoData Engine (TDE103b V10.1) User’s Guide." https://trc.nist.gov/TDE/Help/TDE103b/Eqns-Pure-RefractiveIndex/RIXExpansion.htm. ''' t = T - 298.15 w = (wavelength - 589.26e-9) * 1e9 n_D = horner(Bs, t) if Cs is not None: n_D += horner(Cs, w) return n_D
def _custom_set_poly_fit(self): try: Tmin, Tmax = self.poly_fit_Tmin, self.poly_fit_Tmax poly_fit_coeffs = self.poly_fit_coeffs v_Tmin = horner(poly_fit_coeffs, Tmin) for T_trans in linspace(Tmin, Tmax, 25): v, d1, d2 = horner_and_der2(poly_fit_coeffs, T_trans) Psat = exp(v) dPsat_dT = Psat * d1 d2Psat_dT2 = Psat * (d1 * d1 + d2) A, B, C = Antoine_ABC = Antoine_coeffs_from_point(T_trans, Psat, dPsat_dT, d2Psat_dT2, base=e) self.poly_fit_AB = list( Antoine_AB_coeffs_from_point(T_trans, Psat, dPsat_dT, base=e)) self.DIPPR101_ABC = list( DIPPR101_ABC_coeffs_from_point(T_trans, Psat, dPsat_dT, d2Psat_dT2)) B_OK = B > 0.0 # B is negated in this implementation, so the requirement is reversed C_OK = -T_trans < C < 0.0 if B_OK and C_OK: self.poly_fit_Antoine = Antoine_ABC break else: continue # Calculate the extrapolation values v_Tmax = horner(poly_fit_coeffs, Tmax) v, d1, d2 = horner_and_der2(poly_fit_coeffs, Tmax) Psat = exp(v) dPsat_dT = Psat * d1 d2Psat_dT2 = Psat * (d1 * d1 + d2) # A, B, C = Antoine_ABC = Antoine_coeffs_from_point(T_trans, Psat, dPsat_dT, d2Psat_dT2, base=e) self.poly_fit_AB_high = list( Antoine_AB_coeffs_from_point(Tmax, Psat, dPsat_dT, base=e)) self.poly_fit_AB_high_ABC_compat = [ self.poly_fit_AB_high[0], -self.poly_fit_AB_high[1] ] self.DIPPR101_ABC_high = list( DIPPR101_ABC_coeffs_from_point(Tmax, Psat, dPsat_dT, d2Psat_dT2)) except: pass
def func(T): if T < Tmin: Cp = (T - Tmin)*Tmin_slope + Tmin_value elif T > Tmax: Cp = (T - Tmax)*Tmax_slope + Tmax_value else: Cp = horner(coeffs, T) return Cp
def Rac_Nusselt_Rayleigh_disk(H, D, insulated=True): r'''Calculates the critical Rayleigh number for free convection to begin in the parallel horizontal disk scenario. There are actually two cases - one for the top plate to be insulated (adiabatic) and the other where it has infinite thermal conductivity/is infinitely thin or not present (perfectly conducting). All real cases will lie between the two. Parameters ---------- H : float Distance between the two disks, [m] D : float Diameter of the two disks, [m] insulated : bool, optional Whether the top plate is insulated or uninsulated, [-] Returns ------- Rac : float Critical Rayleigh number, [-] Examples -------- >>> Rac_Nusselt_Rayleigh_disk(H=1, D=.4, insulated=False) 151199.9999999945 >>> Rac_Nusselt_Rayleigh_disk(H=1, D=4, insulated=False) 1891.520931853363 >>> Rac_Nusselt_Rayleigh_disk(2, 1, True) 24347.31479211917 Notes ----- The range of data covered by this function is `D`/`H` from 0.4 to infinity. As inifinity is not well suited to polynomial form, the upper limit is 6 in actuality. Values outside that range are rounded to the limits. This function provides 17-coefficient polynomial fits to interpolate in the table of values in [1]_. The source of the coefficients is cited as being from [2]_. References ---------- .. [1] Rohsenow, Warren and James Hartnett and Young Cho. Handbook of Heat Transfer, 3E. New York: McGraw-Hill, 1998. .. [2] Buell, J. C., and I. Catton. "The Effect of Wall Conduction on the Stability of a Fluid in a Right Circular Cylinder Heated From Below." Journal of Heat Transfer 105, no. 2 (May 1, 1983): 255-60. https://doi.org/10.1115/1.3245571. ''' x = min(max(D / H, 0.4), 6.0) if insulated: coeffs = insulated_disk_coeffs else: coeffs = uninsulated_disk_coeffs return exp(1.0 / horner(coeffs, 0.357142857142857151 * (x - 3.2)))
def test_fit_cheb_poly(): eos = PR(Tc=507.6, Pc=3025000.0, omega=0.2975, T=400., P=1E6) coeffs_linear_short = fit_cheb_poly(eos.Psat, 350, 370, 10) for T in linspace(350, 370, 30): assert_close(eos.Psat(T), horner(coeffs_linear_short, T), rtol=1e-9) # Test transformation of the output only coeffs_log_wide = fit_cheb_poly(eos.Psat, 200, 400, 15, interpolation_property=lambda x: log(x), interpolation_property_inv=lambda x: exp(x)) for T in linspace(200, 400, 30): assert_close(eos.Psat(T), exp(horner(coeffs_log_wide, T)), rtol=1e-9) # Test ability to have other arguments depend on it coeffs_linear_short_under_P = fit_cheb_poly(lambda T, P: eos.to(T=T, P=P).V_l, 350, 370, 7, arg_func=lambda T: (eos.Psat(T)*1.1,)) for T in linspace(350, 370, 30): P = eos.Psat(T)*1.1 assert_close(eos.to(T=T, P=P).V_l, horner(coeffs_linear_short_under_P, T), rtol=1e-9) # Test ability to have other arguments depend on it coeffs_log_short_above_P = fit_cheb_poly(lambda T, P: eos.to(T=T, P=P).V_g, 350, 370, 7, arg_func=lambda T: (eos.Psat(T)*.7,), interpolation_property=lambda x: log(x), interpolation_property_inv=lambda x: exp(x)) for T in linspace(350, 370, 30): P = eos.Psat(T)*0.7 assert_close(eos.to(T=T, P=P).V_g, exp(horner(coeffs_log_short_above_P, T)), rtol=1e-9) # test interpolation_x Tc = 750.0 coeffs_linear_short_SMK_x_trans = fit_cheb_poly(lambda T: SMK(T, Tc=Tc, omega=0.04), 200, 748, 20, interpolation_x=lambda T: log(1. - T/Tc), interpolation_x_inv=lambda x: -(exp(x)-1.0)*Tc) for T in linspace(200, 748, 30): x = log(1. - T/Tc) assert_close(SMK(T, Tc=Tc, omega=0.04), horner(coeffs_linear_short_SMK_x_trans, x), rtol=1e-7) # Case with one coefficient and no T bounds assert_close1d(fit_cheb_poly(func=lambda T: 102.5, low=298.15, high=298.15, n=1), [102.5])
def calculate(self, T, method): r'''Method to calculate heat of sublimation of a solid at temperature `T` with a given method. This method has no exception handling; see :obj:`T_dependent_property <thermo.utils.TDependentProperty.T_dependent_property>` for that. Parameters ---------- T : float Temperature at which to calculate heat of sublimation, [K] method : str Name of the method to use Returns ------- Hsub : float Heat of sublimation of the solid at T, [J/mol] ''' if method == POLY_FIT: if T < self.poly_fit_Tmin: Hsub = (T - self.poly_fit_Tmin ) * self.poly_fit_Tmin_slope + self.poly_fit_Tmin_value elif T > self.poly_fit_Tmax: Hsub = (T - self.poly_fit_Tmax ) * self.poly_fit_Tmax_slope + self.poly_fit_Tmax_value else: Hsub = horner(self.poly_fit_coeffs, T) elif method == GHARAGHEIZI_HSUB_298: Hsub = self.GHARAGHEIZI_Hsub elif method == GHARAGHEIZI_HSUB: T_base = 298.15 Hsub = self.GHARAGHEIZI_Hsub elif method == CRC_HFUS_HVAP_TM: T_base = self.Tm Hsub = self.CRC_Hfus try: Hsub += self.Hvap(T_base) except: Hsub += self.Hvap else: return self._base_calculate(T, method) if method in (GHARAGHEIZI_HSUB, CRC_HFUS_HVAP_TM): try: # Cpg, Cps = self.Cpg(T_base), self.Cps(T_base) # Hsub += (T - T_base)*(Cpg - Cps) Hsub += self.Cpg.T_dependent_property_integral( T_base, T) - self.Cps.T_dependent_property_integral( T_base, T) except: Hsub += (T - T_base) * (self.Cpg - self.Cps) return Hsub
def calculate(self, T, method): r'''Method to calculate sublimation pressure of a fluid at temperature `T` with a given method. This method has no exception handling; see :obj:`T_dependent_property <thermo.utils.TDependentProperty.T_dependent_property>` for that. Parameters ---------- T : float Temperature at calculate sublimation pressure, [K] method : str Name of the method to use Returns ------- Psub : float Sublimation pressure at T, [pa] ''' if method == BESTFIT: if T < self.poly_fit_Tmin: Psub = (T - self.poly_fit_Tmin ) * self.poly_fit_Tmin_slope + self.poly_fit_Tmin_value elif T > self.poly_fit_Tmax: Psub = (T - self.poly_fit_Tmax ) * self.poly_fit_Tmax_slope + self.poly_fit_Tmax_value else: Psub = horner(self.poly_fit_coeffs, T) Psub = exp(Psub) elif method == PSUB_CLAPEYRON: Psub = max( Psub_Clapeyron(T, Tt=self.Tt, Pt=self.Pt, Hsub_t=self.Hsub_t), 1e-200) elif method in self.tabular_data: Psub = self.interpolate(T, method) return Psub
def calculate(self, T, method): r'''Method to calculate vapor pressure of a fluid at temperature `T` with a given method. This method has no exception handling; see :obj:`thermo.utils.TDependentProperty.T_dependent_property` for that. Parameters ---------- T : float Temperature at calculate vapor pressure, [K] method : str Name of the method to use Returns ------- Psat : float Vapor pressure at T, [pa] ''' if method == BESTFIT: if T < self.poly_fit_Tmin: Psat = (T - self.poly_fit_Tmin ) * self.poly_fit_Tmin_slope + self.poly_fit_Tmin_value elif T > self.poly_fit_Tmax: Psat = (T - self.poly_fit_Tmax ) * self.poly_fit_Tmax_slope + self.poly_fit_Tmax_value else: Psat = horner(self.poly_fit_coeffs, T) Psat = exp(Psat) elif method == BEST_FIT_AB: if T < self.poly_fit_Tmax: return self.calculate(T, BESTFIT) A, B = self.poly_fit_AB_high_ABC_compat return exp(A + B / T) elif method == BEST_FIT_ABC: if T < self.poly_fit_Tmax: return self.calculate(T, BESTFIT) A, B, C = self.DIPPR101_ABC_high return exp(A + B / T + C * log(T)) elif method == WAGNER_MCGARRY: Psat = Wagner_original(T, self.WAGNER_MCGARRY_Tc, self.WAGNER_MCGARRY_Pc, *self.WAGNER_MCGARRY_coefs) elif method == WAGNER_POLING: Psat = Wagner(T, self.WAGNER_POLING_Tc, self.WAGNER_POLING_Pc, *self.WAGNER_POLING_coefs) elif method == ANTOINE_EXTENDED_POLING: Psat = TRC_Antoine_extended(T, *self.ANTOINE_EXTENDED_POLING_coefs) elif method == ANTOINE_POLING: A, B, C = self.ANTOINE_POLING_coefs Psat = Antoine(T, A, B, C, base=10.0) elif method == DIPPR_PERRY_8E: Psat = EQ101(T, *self.Perrys2_8_coeffs) elif method == VDI_PPDS: Psat = Wagner(T, self.VDI_PPDS_Tc, self.VDI_PPDS_Pc, *self.VDI_PPDS_coeffs) elif method == COOLPROP: Psat = PropsSI('P', 'T', T, 'Q', 0, self.CASRN) elif method == BOILING_CRITICAL: Psat = boiling_critical_relation(T, self.Tb, self.Tc, self.Pc) elif method == LEE_KESLER_PSAT: Psat = Lee_Kesler(T, self.Tc, self.Pc, self.omega) elif method == AMBROSE_WALTON: Psat = Ambrose_Walton(T, self.Tc, self.Pc, self.omega) elif method == SANJARI: Psat = Sanjari(T, self.Tc, self.Pc, self.omega) elif method == EDALAT: Psat = Edalat(T, self.Tc, self.Pc, self.omega) elif method == EOS: Psat = self.eos[0].Psat(T) elif method == BESTFIT: Psat = exp(horner(self.poly_fit_coeffs, T)) else: return self._base_calculate(T, method) return Psat
def calculate(self, T, method): r'''Method to calculate heat of vaporization of a liquid at temperature `T` with a given method. This method has no exception handling; see :obj:`T_dependent_property <thermo.utils.TDependentProperty.T_dependent_property>` for that. Parameters ---------- T : float Temperature at which to calculate heat of vaporization, [K] method : str Name of the method to use Returns ------- Hvap : float Heat of vaporization of the liquid at T, [J/mol] ''' if method == POLY_FIT: if T > self.poly_fit_Tc: Hvap = 0.0 else: Hvap = horner(self.poly_fit_coeffs, log(1.0 - T / self.poly_fit_Tc)) elif method == COOLPROP: Hvap = PropsSI('HMOLAR', 'T', T, 'Q', 1, self.CASRN) - PropsSI( 'HMOLAR', 'T', T, 'Q', 0, self.CASRN) elif method == DIPPR_PERRY_8E: Hvap = EQ106(T, *self.Perrys2_150_coeffs) # CSP methods elif method == VDI_PPDS: Hvap = PPDS12(T, self.VDI_PPDS_Tc, *self.VDI_PPDS_coeffs) elif method == ALIBAKHSHI: Hvap = Alibakhshi(T=T, Tc=self.Tc, C=self.Alibakhshi_C) elif method == MORGAN_KOBAYASHI: Hvap = MK(T, self.Tc, self.omega) elif method == SIVARAMAN_MAGEE_KOBAYASHI: Hvap = SMK(T, self.Tc, self.omega) elif method == VELASCO: Hvap = Velasco(T, self.Tc, self.omega) elif method == PITZER: Hvap = Pitzer(T, self.Tc, self.omega) elif method == CLAPEYRON: Psat = self.Psat(T) if callable(self.Psat) else self.Psat Zg = self.Zg(T, Psat) if callable(self.Zg) else self.Zg Zl = self.Zl(T, Psat) if callable(self.Zl) else self.Zl if Zg: if Zl: dZ = Zg - Zl else: dZ = Zg Hvap = Clapeyron(T, self.Tc, self.Pc, dZ=dZ, Psat=Psat) # CSP methods at Tb only elif method == RIEDEL: Hvap = Riedel(self.Tb, self.Tc, self.Pc) elif method == CHEN: Hvap = Chen(self.Tb, self.Tc, self.Pc) elif method == VETERE: Hvap = Vetere(self.Tb, self.Tc, self.Pc) elif method == LIU: Hvap = Liu(self.Tb, self.Tc, self.Pc) # Individual data point methods elif method == CRC_HVAP_TB: Hvap = self.CRC_HVAP_TB_Hvap elif method == CRC_HVAP_298: Hvap = self.CRC_HVAP_298 elif method == GHARAGHEIZI_HVAP_298: Hvap = self.GHARAGHEIZI_HVAP_298_Hvap else: return self._base_calculate(T, method) # Adjust with the watson equation if estimated at Tb or Tc only if method in self.boiling_methods or (self.Tc and method in ( CRC_HVAP_TB, CRC_HVAP_298, GHARAGHEIZI_HVAP_298)): if method in self.boiling_methods: Tref = self.Tb elif method == CRC_HVAP_TB: Tref = self.CRC_HVAP_TB_Tb elif method in [CRC_HVAP_298, GHARAGHEIZI_HVAP_298]: Tref = 298.15 Hvap = Watson(T, Hvap, Tref, self.Tc, self.Watson_exponent) return Hvap
def fit_cheb_poly(func, low, high, n, interpolation_property=None, interpolation_property_inv=None, interpolation_x=lambda x: x, interpolation_x_inv=lambda x: x, arg_func=None): r'''Fit a function of one variable to a polynomial of degree `n` using the Chebyshev approximation technique. Transformations of the base function are allowed as lambdas. Parameters ---------- func : callable Function to fit, [-] low : float Low limit of fitting range, [-] high : float High limit of fitting range, [-] n : int Degree of polynomial fitting, [-] interpolation_property : None or callable When specified, this callable will transform the output of the function before fitting; for example a property like vapor pressure should be `interpolation_property=lambda x: log(x)` because it rises exponentially. The output of the evaluated polynomial should then have the reverse transform applied to it; in this case, `exp`, [-] interpolation_property_inv : None or callable When specified, this callable reverses `interpolation_property`; it must always be provided when `interpolation_property` is set, and it must perform the reverse transform, [-] interpolation_x : None or callable Callable to transform the input variable to fitting. For example, enthalpy of vaporization goes from a high value at low temperatures to zero at the critical temperature; it is normally hard for a chebyshev series to match this, but by setting this to lambda T: log(1. - T/Tc), this issue is resolved, [-] interpolation_x_inv : None or callable Inverse function of `interpolation_x_inv`; must always be provided when `interpolation_x` is set, and it must perform the reverse transform, [-] arg_func : None or callable Function which is called with the value of `x` in the original domain, and that returns arguments to `func`. Returns ------- coeffs : list[float] Polynomial coefficients in order for evaluation by `horner`, [-] Notes ----- This is powered by Ian Bell's ChebTools. ''' global ChebTools if ChebTools is None: import ChebTools low_orig, high_orig = low, high cheb_fun = None low, high = interpolation_x(low_orig), interpolation_x(high_orig) if arg_func is not None: if interpolation_property is not None: def func_fun(T): arg = interpolation_x_inv(T) if arg > high_orig: arg = high_orig if arg < low_orig: arg = low_orig return interpolation_property(func(arg, *arg_func(arg))) else: def func_fun(T): arg = interpolation_x_inv(T) if arg > high_orig: arg = high_orig if arg < low_orig: arg = low_orig return func(arg, *arg_func(arg)) else: if interpolation_property is not None: def func_fun(T): arg = interpolation_x_inv(T) if arg > high_orig: arg = high_orig if arg < low_orig: arg = low_orig return interpolation_property(func(arg)) else: def func_fun(T): arg = interpolation_x_inv(T) if arg > high_orig: arg = high_orig if arg < low_orig: arg = low_orig return func(arg) func_fun = np.vectorize(func_fun) if n == 1: coeffs = [func_fun(0.5*(low + high)).tolist()] else: cheb_fun = ChebTools.generate_Chebyshev_expansion(n-1, func_fun, low, high) coeffs = cheb_fun.coef() coeffs = cheb2poly(coeffs)[::-1].tolist() # Convert to polynomial basis # Mix in low high limits to make it a normal polynomial if high != low: # Handle the case of no transformation, no limits my_poly = Polynomial([-0.5*(high + low)*2.0/(high - low), 2.0/(high - low)]) coeffs = horner(coeffs, my_poly).coef[::-1].tolist() return coeffs
def poly_fit_statistics(func, coeffs, low, high, pts=200, interpolation_property_inv=None, interpolation_x=lambda x: x, arg_func=None): r'''Function to check how accurate a fit function is to a polynomial. This function uses the asolute relative error definition. Parameters ---------- func : callable Function to fit, [-] coeffs : list[float] Coefficients for calculating the property, [-] low : float Low limit of fitting range, [-] high : float High limit of fitting range, [-] n : int Degree of polynomial fitting, [-] interpolation_property_inv : None or callable When specified, this callable reverses `interpolation_property`; it must always be provided when `interpolation_property` is set, and it must perform the reverse transform, [-] interpolation_x : None or callable Callable to transform the input variable to fitting. For example, enthalpy of vaporization goes from a high value at low temperatures to zero at the critical temperature; it is normally hard for a chebyshev series to match this, but by setting this to lambda T: log(1. - T/Tc), this issue is resolved, [-] arg_func : None or callable Function which is called with the value of `x` in the original domain, and that returns arguments to `func`. Returns ------- err_avg : float Mean error in the evaluated points, [-] err_std : float Standard deviation of errors in the evaluated points, [-] min_ratio : float Lowest ratio of calc/actual in any found points, [-] max_ratio : float Highest ratio of calc/actual in any found points, [-] Notes ----- ''' low_orig, high_orig = low, high all_points_orig = linspace(low_orig, high_orig, pts) # Get the low, high, and x points in the transformed domain low, high = interpolation_x(low_orig), interpolation_x(high_orig) all_points = [interpolation_x(v) for v in all_points_orig] # Calculate the fit values calc_pts = [horner(coeffs, x) for x in all_points] if interpolation_property_inv: for i in range(pts): calc_pts[i] = interpolation_property_inv(calc_pts[i]) if arg_func is not None: actual_pts = [func(v, *arg_func(v)) for v in all_points_orig] else: actual_pts = [func(v) for v in all_points_orig] ARDs = [(abs((i-j)/j) if j != 0 else 0.0) for i, j in zip(calc_pts, actual_pts)] err_avg = sum(ARDs)/pts err_std = np.std(ARDs) actual_pts = np.array(actual_pts) calc_pts = np.array(calc_pts) max_ratio, min_ratio = max(calc_pts/actual_pts), min(calc_pts/actual_pts) return err_avg, err_std, min_ratio, max_ratio
def test_horner(): from fluids.numerics import horner assert_allclose(horner([1.0, 3.0], 2.0), 5.0) assert_allclose(horner([3.0], 2.0), 3.0)