def load_interface_dfs(): global _interface_dfs_loaded, sigma_data_Mulero_Cachadina, sigma_values_Mulero_Cachadina global sigma_data_Jasper_Lange, sigma_values_Jasper_Lange global sigma_data_Somayajulu, sigma_values_Somayajulu, sigma_data_Somayajulu2 global sigma_values_Somayajulu2, sigma_data_VDI_PPDS_11, sigma_values_VDI_PPDS_11 sigma_data_Mulero_Cachadina = data_source('MuleroCachadinaParameters.tsv') sigma_values_Mulero_Cachadina = np.array( sigma_data_Mulero_Cachadina.values[:, 1:], dtype=float) sigma_data_Jasper_Lange = data_source('Jasper-Lange.tsv') sigma_values_Jasper_Lange = np.array(sigma_data_Jasper_Lange.values[:, 1:], dtype=float) sigma_data_Somayajulu = data_source('Somayajulu.tsv') sigma_values_Somayajulu = np.array(sigma_data_Somayajulu.values[:, 1:], dtype=float) sigma_data_Somayajulu2 = data_source('SomayajuluRevised.tsv') sigma_values_Somayajulu2 = np.array(sigma_data_Somayajulu2.values[:, 1:], dtype=float) sigma_data_VDI_PPDS_11 = data_source('VDI PPDS surface tensions.tsv') sigma_values_VDI_PPDS_11 = np.array(sigma_data_VDI_PPDS_11.values[:, 1:], dtype=float)
def _load_phase_change_correlations(): global phase_change_data_Perrys2_150, phase_change_values_Perrys2_150 global phase_change_data_VDI_PPDS_4, phase_change_values_VDI_PPDS_4 global phase_change_data_Alibakhshi_Cs, _phase_change_corrs_loaded # 66554 for pandas; 19264 bytes for numpy phase_change_data_Perrys2_150 = data_source('Table 2-150 Heats of Vaporization of Inorganic and Organic Liquids.tsv') phase_change_values_Perrys2_150 = np.array(phase_change_data_Perrys2_150.values[:, 1:], dtype=float) # 52187 bytes for pandas, 13056 bytes for numpy phase_change_data_VDI_PPDS_4 = data_source('VDI PPDS Enthalpies of vaporization.tsv') phase_change_values_VDI_PPDS_4 = np.array(phase_change_data_VDI_PPDS_4.values[:, 2:], dtype=float) phase_change_data_Alibakhshi_Cs = data_source('Alibakhshi one-coefficient enthalpy of vaporization.tsv') _phase_change_corrs_loaded = True
def test_array_as_tridiagonals(): A = [[10.0, 2.0, 0.0, 0.0], [3.0, 10.0, 4.0, 0.0], [0.0, 1.0, 7.0, 5.0], [0.0, 0.0, 3.0, 4.0]] tridiagonals = array_as_tridiagonals(A) expect_diags = [[3.0, 1.0, 3.0], [10.0, 10.0, 7.0, 4.0], [2.0, 4.0, 5.0]] assert_allclose(tridiagonals[0], expect_diags[0], rtol=0, atol=0) assert_allclose(tridiagonals[1], expect_diags[1], rtol=0, atol=0) assert_allclose(tridiagonals[2], expect_diags[2], rtol=0, atol=0) A = np.array(A) tridiagonals = array_as_tridiagonals(A) assert_allclose(tridiagonals[0], expect_diags[0], rtol=0, atol=0) assert_allclose(tridiagonals[1], expect_diags[1], rtol=0, atol=0) assert_allclose(tridiagonals[2], expect_diags[2], rtol=0, atol=0) a, b, c = [3.0, 1.0, 3.0], [10.0, 10.0, 7.0, 4.0], [2.0, 4.0, 5.0] expect_mat = tridiagonals_as_array(a, b, c) assert_allclose(expect_mat, A, rtol=0, atol=0) d = [3.0, 4.0, 5.0, 6.0] solved_expect = [ 0.1487758945386064, 0.756120527306968, -1.001883239171375, 2.2514124293785316 ] assert_allclose(solve_tridiagonal(a, b, c, d), solved_expect, rtol=1e-12)
def validate_prop(self, prop, phase, evaluated_points=30): phase_key = '_g' if phase == 'g' else '_l' name = prop + phase_key if prop in ['CP0MOLAR']: name = prop pts = getattr(self, name).points predictor = getattr(self, name) for i in range(len(pts) - 1): Ts = np.linspace(pts[i], pts[i + 1], evaluated_points) # print(Ts[0], Ts[-1]) prop_approx = [predictor(T) for T in Ts] prop_calc = [ CoolProp_T_dependent_property(T, self.CAS, prop, phase) for T in Ts ] # print(prop_approx) # print(prop_calc) # The approximators do give differences at the very low value side # so we need atol=1E-9 # print(prop, self.CAS, prop_approx[0], prop_calc[0]) try: assert_close1d(prop_approx, prop_calc, rtol=1E-7, atol=1E-9) except: '''There are several cases that assert_allclose doesn't deal with well for some reason. We could increase rtol, but instead the relative errors are used here to check everything is as desidred. Some example errors this won't trip on but assert_allclose does are: 1.00014278827e-08 1.62767956613e-06 -0.0 -1.63895899641e-16 -4.93284549625e-15 ''' prop_calc = np.array(prop_calc) prop_approx = np.array(prop_approx) errs = abs((prop_calc - prop_approx) / prop_calc) try: assert max(errs) < 2E-6 except: print( '%s %s failed with mean relative error %s and maximum relative error %s' % (self.CAS, prop, str(np.mean(errs)), str(max(errs))))
def test_min_max_ratios(): actual = [1, 2, 3, 4, 5] calculated = [.9, 2.1, 3.05, 3.8, 5.5] min_ratio_np, max_ratio_np = np.min(np.array(calculated) / actual), np.max( np.array(calculated) / actual) assert_close1d([min_ratio_np, max_ratio_np], min_max_ratios(actual, calculated), rtol=1e-14) # Case with a zero match actual = [1, 2, 3, 0, 5] calculated = [.9, 2.1, 3.05, 0.0, 5.5] assert_close1d(min_max_ratios(actual, calculated), (0.9, 1.1), rtol=0) # Case with a zero mismatch actual = [1, 2, 3, 0, 5] calculated = [.9, 2.1, 3.05, 1, 5.5] assert_close1d(min_max_ratios(actual, calculated), (0.9, 10.0), rtol=0)
def test_bisplev(): from fluids.numerics import bisplev as my_bisplev from scipy.interpolate import bisplev tck = [ np.array([ 0.0, 0.0, 0.0, 0.0, 0.0213694, 0.0552542, 0.144818, 0.347109, 0.743614, 0.743614, 0.743614, 0.743614 ]), np.array([0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0]), np.array([ 1.0001228445490002, 0.9988161050974387, 0.9987070557919563, 0.9979385859402731, 0.9970983069823832, 0.96602540121758, 0.955136014969614, 0.9476842472211648, 0.9351143114374392, 0.9059649602818451, 0.9218915266550902, 0.9086000082864022, 0.8934758292610783, 0.8737960765592091, 0.83185251064324, 0.8664296734965998, 0.8349705397843921, 0.809133298969704, 0.7752206120745123, 0.7344035693011536, 0.817047920445813, 0.7694560150930563, 0.7250979336267909, 0.6766754605968431, 0.629304180420512, 0.7137237030611423, 0.6408238328161417, 0.5772000233279148, 0.504889627280836, 0.440579886434288, 0.6239736474980684, 0.5273646894226224, 0.43995388722059986, 0.34359277007615313, 0.26986439252143746, 0.5640689738382749, 0.4540959882735219, 0.35278120580740957, 0.24364672351604122, 0.1606942128340308 ]), 3, 1 ] my_tck = [ tck[0].tolist(), tck[1].tolist(), tck[2].tolist(), tck[3], tck[4] ] xs = np.linspace(0, 1, 10) zs = np.linspace(0, 1, 10) ys_scipy = bisplev(xs, zs, tck) ys = my_bisplev(xs, zs, my_tck) assert_allclose(ys, ys_scipy) ys_scipy = bisplev(0.5, .7, tck) ys = my_bisplev(.5, .7, my_tck) assert_allclose(ys, ys_scipy)
def load_vapor_pressure_dfs(): global Psat_data_WagnerMcGarry, Psat_values_WagnerMcGarry, Psat_data_AntoinePoling, Psat_values_AntoinePoling global Psat_data_WagnerPoling, Psat_values_WagnerPoling, Psat_data_AntoineExtended, Psat_values_AntoineExtended global Psat_data_Perrys2_8, Psat_values_Perrys2_8, Psat_data_VDI_PPDS_3, Psat_values_VDI_PPDS_3 global _vapor_pressure_dfs_loaded # 57463 bytes for df; 13720 bytes for numpy Psat_data_WagnerMcGarry = data_source('Wagner Original McGarry.tsv') Psat_values_WagnerMcGarry = np.array(Psat_data_WagnerMcGarry.values[:, 1:], dtype=float) # 58216 bytes for df; 13000 bytes for numpy Psat_data_AntoinePoling = data_source('Antoine Collection Poling.tsv') Psat_values_AntoinePoling = np.array(Psat_data_AntoinePoling.values[:, 1:], dtype=float) # 20928 bytes for df; 7488 bytes for numpy Psat_data_WagnerPoling = data_source('Wagner Collection Poling.tsv') Psat_values_WagnerPoling = np.array(Psat_data_WagnerPoling.values[:, 1:], dtype=float) # 21388 bytes for df; 7760 bytes for numpy Psat_data_AntoineExtended = data_source('Antoine Extended Collection Poling.tsv') Psat_values_AntoineExtended = np.array(Psat_data_AntoineExtended.values[:, 1:], dtype=float) # 65740 bytes for df; 21760 bytes for numpy Psat_data_Perrys2_8 = data_source('Table 2-8 Vapor Pressure of Inorganic and Organic Liquids.tsv') Psat_values_Perrys2_8 = np.array(Psat_data_Perrys2_8.values[:, 1:], dtype=float) # 52742 bytes for df; 15400 bytes for numpy Psat_data_VDI_PPDS_3 = data_source('VDI PPDS Boiling temperatures at different pressures.tsv') Psat_values_VDI_PPDS_3 = np.array(Psat_data_VDI_PPDS_3.values[:, 1:], dtype=float) _vapor_pressure_dfs_loaded = True
def test_splev(): from fluids.numerics import py_splev from scipy.interpolate import splev # Originally Dukler_XA_tck tck = [ np.array([ -2.4791105294648372, -2.4791105294648372, -2.4791105294648372, -2.4791105294648372, 0.14360803483759585, 1.7199938263676038, 1.7199938263676038, 1.7199938263676038, 1.7199938263676038 ]), np.array([ 0.21299880246561081, 0.16299733301915248, -0.042340970712679615, -1.9967836909384598, -2.9917366639619414, 0.0, 0.0, 0.0, 0.0 ]), 3 ] my_tck = [tck[0].tolist(), tck[1].tolist(), tck[2]] xs = np.linspace(-3, 2, 100).tolist() # test extrapolation ys_scipy = splev(xs, tck, ext=0) ys = [py_splev(xi, my_tck, ext=0) for xi in xs] assert_allclose(ys, ys_scipy) # test truncating to side values ys_scipy = splev(xs, tck, ext=3) ys = [py_splev(xi, my_tck, ext=3) for xi in xs] assert_allclose(ys, ys_scipy) # Test returning zeros for bad values ys_scipy = splev(xs, tck, ext=1) ys = [py_splev(xi, my_tck, ext=1) for xi in xs] assert_allclose(ys, ys_scipy) # Test raising an error when extrapolating is not allowed with pytest.raises(ValueError): py_splev(xs[0], my_tck, ext=2) with pytest.raises(ValueError): splev(xs[0], my_tck, ext=2)
def hwm14(Z, latitude=0, longitude=0, day=0, seconds=0, geomagnetic_disturbance_index=4): r'''Horizontal Wind Model 2014, for calculating wind velocity in the atmosphere as a function of time of year, longitude and latitude, and earth's geomagnetic disturbance. The model is described in [1]_. The model no longer accounts for solar flux. Parameters ---------- Z : float Elevation, [m] latitude : float, optional Latitude, between -90 and 90 [degrees] longitude : float, optional Longitude, between -180 and 180 or 0 and 360, [degrees] day : float, optional Day of year, 0-366 [day] seconds : float, optional Seconds since start of day, in UT1 time; using UTC provides no loss in accuracy [s] geomagnetic_disturbance_index : float, optional Average daily `Ap` or also known as planetary magnetic index. Returns ------- v_north : float Wind velocity, meridional (Northward) [m/s] v_east : float Wind velocity, zonal (Eastward) [m/s] Examples -------- >>> hwm14(5E5, 45, 50, 365) # doctest: +SKIP (-38.64341354370117, 12.871272087097168) Notes ----- No full description has been published of this model; it has been defined by its implementation only. It was written in FORTRAN, and is accessible at http://onlinelibrary.wiley.com/store/10.1002/2014EA000089/asset/supinfo/ess224-sup-0002-supinfo.tgz?v=1&s=2a957ba70b7cf9dd0612d9430076297c3634ea75. F2PY auto-compilation support is not yet currently supported. To compile this file, run the following commands in a shell after navigating to $FLUIDSPATH/fluids/optional/. This should generate the file hwm14.so in that directory. Generate a .pyf signature file: .. code-block:: bash f2py -m hwm14 -h hwm14.pyf hwm14.f90 Compile the interface: .. code-block:: bash f2py -c hwm14.pyf hwm14.f90 If the module is not compiled, an import error will be raised. No patches were necessary to either the generated pyf or hwm14.f90 file, as the authors of [1]_ have made it F2PY compatible. Developed using 73 million data points taken by 44 instruments over 60 years. References ---------- .. [1] Drob, Douglas P., John T. Emmert, John W. Meriwether, Jonathan J. Makela, Eelco Doornbos, Mark Conde, Gonzalo Hernandez, et al. "An Update to the Horizontal Wind Model (HWM): The Quiet Time Thermosphere." Earth and Space Science 2, no. 7 (July 1, 2015): 2014EA000089. doi:10.1002/2014EA000089. ''' # Needed by hwm14 os.environ["HWMPATH"] = os.path.join(os.path.dirname(__file__), 'optional') try: try: from fluids.optional import hwm14 except: import optional.hwm14 except: # pragma: no cover raise ImportError(no_gfortran_error) ans = hwm14.hwm14(day, seconds, Z*1e-3, latitude, longitude, 0, 0, 0, np.array([np.nan, geomagnetic_disturbance_index])) return tuple(ans.tolist())
def _load_permittivity_data(): global _permittivity_data_loaded, permittivity_values_CRC, permittivity_data_CRC permittivity_data_CRC = data_source( 'Permittivity (Dielectric Constant) of Liquids.tsv') permittivity_values_CRC = np.array(permittivity_data_CRC.values[:, 1:], dtype=float)
def sunrise_sunset(moment, latitude, longitude): r'''Calculates the times at which the sun is at sunset; sunrise; and halfway between sunrise and sunset (transit). Uses the Reda and Andreas (2004) model described in [1]_, originally incorporated into the excellent `pvlib library <https://github.com/pvlib/pvlib-python>`_ Parameters ---------- moment : datetime Date for the calculation; needs to contain only the year, month, and day, [-] latitude : float Latitude, between -90 and 90 [degrees] longitude : float Longitude, between -180 and 180, [degrees] Returns ------- sunrise : datetime The time at the specified day when the sun rises **IN UTC**, [-] sunset : datetime The time at the specified day when the sun sets **IN UTC**, [-] transit : datetime The time at the specified day when the sun is at solar noon - halfway between sunrise and sunset **IN UTC**, [-] Examples -------- >>> sunrise, sunset, transit = sunrise_sunset(datetime(2018, 4, 17), ... 51.0486, -114.07) >>> sunrise datetime.datetime(2018, 4, 17, 12, 36, 55, 782660) >>> sunset datetime.datetime(2018, 4, 18, 2, 34, 4, 249326) >>> transit datetime.datetime(2018, 4, 17, 19, 35, 46, 686265) Notes ----- This functions takes on the order of 2 ms per calculation. The reason the function cannot return the time correct the local timezone is that the function does not know the timezone at the specified lat/long. References ---------- .. [1] Reda, Ibrahim, and Afshin Andreas. "Solar Position Algorithm for Solar Radiation Applications." Solar Energy 76, no. 5 (January 1, 2004): 577-89. https://doi.org/10.1016/j.solener.2003.12.003. ''' from fluids.optional import spa delta_t = spa.calculate_deltat(moment.year, moment.month) # Strip the part of the day moment = datetime(moment.year, moment.month, moment.day) import calendar unixtime = calendar.timegm(moment.timetuple()) unixtime = unixtime - unixtime % ( 86400 ) # Remove the remainder of the value, rounding it to the day it is transit, sunrise, sunset = spa.transit_sunrise_sunset(np.array([unixtime]), lat=latitude, lon=longitude, delta_t=delta_t, numthreads=1) transit = datetime.utcfromtimestamp(float(transit)) sunrise = datetime.utcfromtimestamp(float(sunrise)) sunset = datetime.utcfromtimestamp(float(sunset)) return sunrise, sunset, transit
def sunrise_sunset(moment, latitude, longitude): r'''Calculates the times at which the sun is at sunset; sunrise; and halfway between sunrise and sunset (transit). Uses the Reda and Andreas (2004) model described in [1]_, originally incorporated into the excellent `pvlib library <https://github.com/pvlib/pvlib-python>`_ Parameters ---------- moment : datetime Date for the calculationl; needs to contain only the year, month, and day, [-] latitude : float Latitude, between -90 and 90 [degrees] longitude : float Longitude, between -180 and 180, [degrees] Returns ------- sunrise : datetime The time at the specified day when the sun rises **IN UTC**, [-] sunset : datetime The time at the specified day when the sun sets **IN UTC**, [-] transit : datetime The time at the specified day when the sun is at solar noon - halfway between sunrise and sunset **IN UTC**, [-] Examples -------- >>> sunrise, sunset, transit = sunrise_sunset(datetime(2018, 4, 17), ... 51.0486, -114.07) >>> sunrise datetime.datetime(2018, 4, 17, 12, 36, 55, 782660) >>> sunset datetime.datetime(2018, 4, 18, 2, 34, 4, 249326) >>> transit datetime.datetime(2018, 4, 17, 19, 35, 46, 686265) Notes ----- This functions takes on the order of 2 ms per calculation. The reason the function cannot return the time correct the local timezone is that the function does not know the timezone at the specified lat/long. References ---------- .. [1] Reda, Ibrahim, and Afshin Andreas. "Solar Position Algorithm for Solar Radiation Applications." Solar Energy 76, no. 5 (January 1, 2004): 577-89. https://doi.org/10.1016/j.solener.2003.12.003. ''' from fluids.optional import spa delta_t = spa.calculate_deltat(moment.year, moment.month) # Strip the part of the day moment = datetime(moment.year, moment.month, moment.day) import calendar unixtime = calendar.timegm(moment.timetuple()) unixtime = unixtime - unixtime % (86400) # Remove the remainder of the value, rounding it to the day it is transit, sunrise, sunset = spa.transit_sunrise_sunset(np.array([unixtime]), lat=latitude, lon=longitude, delta_t=delta_t, numthreads=1) transit = datetime.utcfromtimestamp(float(transit)) sunrise = datetime.utcfromtimestamp(float(sunrise)) sunset = datetime.utcfromtimestamp(float(sunset)) return sunrise, sunset, transit
def hwm14(Z, latitude=0, longitude=0, day=0, seconds=0, geomagnetic_disturbance_index=4): r'''Horizontal Wind Model 2014, for calculating wind velocity in the atmosphere as a function of time of year, longitude and latitude, and earth's geomagnetic disturbance. The model is described in [1]_. The model no longer accounts for solar flux. Parameters ---------- Z : float Elevation, [m] latitude : float, optional Latitude, between -90 and 90 [degrees] longitude : float, optional Longitude, between -180 and 180 or 0 and 360, [degrees] day : float, optional Day of year, 0-366 [day] seconds : float, optional Seconds since start of day, in UT1 time; using UTC provides no loss in accuracy [s] geomagnetic_disturbance_index : float, optional Average daily `Ap` or also known as planetary magnetic index. Returns ------- v_north : float Wind velocity, meridional (Northward) [m/s] v_east : float Wind velocity, zonal (Eastward) [m/s] Examples -------- >>> hwm14(5E5, 45, 50, 365) (-38.64341354370117, 12.871272087097168) Notes ----- No full description has been published of this model; it has been defined by its implementation only. It was written in FORTRAN, and is accessible at http://onlinelibrary.wiley.com/store/10.1002/2014EA000089/asset/supinfo/ess224-sup-0002-supinfo.tgz?v=1&s=2a957ba70b7cf9dd0612d9430076297c3634ea75. F2PY auto-compilation support is not yet currently supported. To compile this file, run the following command in a shell after navigating to $FLUIDSPATH/fluids/optional/. This should generate the file hwm14.so in that directory. f2py -c hwm14.pyf hwm14.f90 The fortran .pyf signature file is included with this project, but it can also be re-created with the command: f2py -m hwm14 -h hwm14.pyf hwm14.f90 If the module is not compiled, an import error will be raised. No patches were necessary to either the generated pyf or hwm14.f90 file, as the authors of [1]_ have made it F2PY compatible. Developed using 73 million data points taken by 44 instruments over 60 years. References ---------- .. [1] Drob, Douglas P., John T. Emmert, John W. Meriwether, Jonathan J. Makela, Eelco Doornbos, Mark Conde, Gonzalo Hernandez, et al. "An Update to the Horizontal Wind Model (HWM): The Quiet Time Thermosphere." Earth and Space Science 2, no. 7 (July 1, 2015): 2014EA000089. doi:10.1002/2014EA000089. ''' # Needed by hwm14 os.environ["HWMPATH"] = os.path.join(os.path.dirname(__file__), 'optional') try: import optional.hwm14 except: # pragma: no cover raise ImportError(no_gfortran_error) ans = optional.hwm14.hwm14(day, seconds, Z/1000., latitude, longitude, 0, 0, 0, np.array([np.nan, geomagnetic_disturbance_index])) return tuple(ans.tolist())
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 fit_customized(Ts, data, fitting_func, fit_parameters, use_fit_parameters, fit_method, objective, multiple_tries_max_objective, guesses=None, initial_guesses=None, analytical_jac=None, solver_kwargs=None, use_numba=False, multiple_tries=False, do_statistics=False, multiple_tries_max_err=1e-5, func_wrapped_for_leastsq=None, jac_wrapped_for_leastsq=None): if solver_kwargs is None: solver_kwargs = {} if use_numba: fit_func_dict = fluids.numba.numerics.fit_minimization_targets else: fit_func_dict = fit_minimization_targets err_func = fit_func_dict[objective] err_fun_multiple_guesses = fit_func_dict[multiple_tries_max_objective] do_minimization = fit_method == 'differential_evolution' if do_minimization: def minimize_func(params): calc = fitting_func(Ts, *params) err = err_func(data, calc) return err p0 = [1.0]*len(fit_parameters) if guesses: for i, k in enumerate(use_fit_parameters): if k in guesses: p0[i] = guesses[k] if initial_guesses: # iterate over all the initial guess parameters we have and find the one # with the lowest error (according to the error criteria) best_hardcoded_guess = None best_hardcoded_err = 1e308 hardcoded_errors = [] hardcoded_guesses = initial_guesses extra_user_guess = [{k: v for k, v in zip(use_fit_parameters, p0)}] all_iter_guesses = hardcoded_guesses + extra_user_guess array_init_guesses = [] err_func_init = fit_func_dict['MeanRelErr'] for hardcoded in all_iter_guesses: ph = [None]*len(fit_parameters) for i, k in enumerate(use_fit_parameters): ph[i] = hardcoded[k] array_init_guesses.append(ph) calc = fitting_func(Ts, *ph) err = err_func_init(data, calc) hardcoded_errors.append(err) if err < best_hardcoded_err: best_hardcoded_err = err best_hardcoded_guess = ph p0 = best_hardcoded_guess if best_hardcoded_err == 1e308 and fit_method != 'differential_evolution': raise ValueError("No attemped fitting parameters yielded remotely reasonable errors. Check input data or provide guesses") array_init_guesses = [p0 for _, p0 in sorted(zip(hardcoded_errors, array_init_guesses))] else: array_init_guesses = [p0] if func_wrapped_for_leastsq is None: def func_wrapped_for_leastsq(params): # jacobian is the same return fitting_func(Ts, *params) - data if jac_wrapped_for_leastsq is None: def jac_wrapped_for_leastsq(params): return analytical_jac(Ts, *params) pcov = None if fit_method == 'differential_evolution': if 'bounds' in solver_kwargs: working_bounds = solver_kwargs.pop('bounds') else: factor = 4.0 if len(array_init_guesses) > 3: lowers_guess, uppers_guess = np.array(array_init_guesses).min(axis=0), np.array(array_init_guesses).max(axis=0) working_bounds = [(lowers_guess[i]*factor if lowers_guess[i] < 0. else lowers_guess[i]*(1.0/factor), uppers_guess[i]*(1.0/factor) if uppers_guess[i] < 0. else uppers_guess[i]*(factor), ) for i in range(len(use_fit_parameters))] else: working_bounds = [(0, 1000) for k in use_fit_parameters] popsize = solver_kwargs.get('popsize', 15)*len(fit_parameters) init = array_init_guesses for i in range(len(init), popsize): to_add = [uniform(ll, lh) for ll, lh in working_bounds] init.append(to_add) res = differential_evolution(minimize_func,# init=np.array(init), bounds=working_bounds, **solver_kwargs) popt = res['x'] else: lm_direct = fit_method == 'lm' Dfun = jac_wrapped_for_leastsq if analytical_jac is not None else None if 'maxfev' not in solver_kwargs and fit_method == 'lm': # DO NOT INCREASE THIS! Make an analytical jacobian instead please. # Fought very hard to bring the analytical jacobian maxiters down to 500! # 250 seems too small. if analytical_jac is not None: solver_kwargs['maxfev'] = 500 else: solver_kwargs['maxfev'] = 5000 if multiple_tries: multiple_tries_best_error = 1e300 best_popt, best_pcov = None, None popt = None if type(multiple_tries) is int and len(array_init_guesses) > multiple_tries: array_init_guesses = array_init_guesses[0:multiple_tries] for p0 in array_init_guesses: try: if lm_direct: if Dfun is not None: popt = lmder(func_wrapped_for_leastsq, Dfun, p0, tuple(), False, 0, 1.49012e-8, 1.49012e-8, 0.0, solver_kwargs['maxfev'], 100, None)[0] else: popt, _ = leastsq(func_wrapped_for_leastsq, p0, Dfun=Dfun, **solver_kwargs) pcov = None else: popt, pcov = curve_fit(fitting_func, Ts, data, p0=p0, jac=analytical_jac, method=fit_method, **solver_kwargs) except: continue calc = fitting_func(Ts, *popt) curr_err = err_fun_multiple_guesses(data, calc) if curr_err < multiple_tries_best_error: best_popt, best_pcov = popt, pcov multiple_tries_best_error = curr_err if curr_err < multiple_tries_max_err: break if best_popt is None: raise ValueError("No guesses converged") else: popt, pcov = best_popt, best_pcov else: if lm_direct: popt, _ = leastsq(func_wrapped_for_leastsq, p0, Dfun=Dfun, **solver_kwargs) pcov = None else: popt, pcov = curve_fit(fitting_func, Ts, data, p0=p0, jac=analytical_jac, method=fit_method, **solver_kwargs) out_kwargs = {} for param_name, param_value in zip(fit_parameters, popt): out_kwargs[param_name] = float(param_value) if do_statistics: if not use_numba: stats_func = data_fit_statistics else: stats_func = thermo.numba.fitting.data_fit_statistics calc = fitting_func(Ts, *popt) stats = stats_func(Ts, data, calc) statistics = {} statistics['calc'] = calc statistics['MAE'] = stats[0] statistics['STDEV'] = stats[1] statistics['min_ratio'] = stats[2] statistics['max_ratio'] = stats[3] statistics['pcov'] = pcov return out_kwargs, statistics return out_kwargs