Exemple #1
0
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)
Exemple #2
0
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
Exemple #3
0
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)
Exemple #4
0
    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))))
Exemple #5
0
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)
Exemple #6
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
Exemple #8
0
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)
Exemple #9
0
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)
Exemple #11
0
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
Exemple #12
0
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
Exemple #13
0
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())
Exemple #14
0
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
Exemple #15
0
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