Skip to content

Fitting

PASCal.fitting

This module definnput_format = orig_ext 6 es functions for linear, empirical or Chebyshev fitting to strain and volume data.

Functions

fit_linear_wls(y: ndarray, x: ndarray, x_errors: ndarray) -> List[statsmodels.regression.linear_model.RegressionResultsWrapper]

Fit a linear WLS models to the columns of the passed y array.

Parameters; y: An array of dependent variables as a function of x, e.g., strain, volume. x: The control variable, e.g., pressure, temperature. x_errors: The errors in the control variable to use as weights for fit

Returns:

Type Description
List[statsmodels.regression.linear_model.RegressionResultsWrapper]

The results of the fit.

Source code in PASCal/fitting.py
def fit_linear_wls(
    y: Union[Strain, Volume],
    x: Union[Pressure, Temperature, Charge],
    x_errors: Union[Pressure, Temperature, Charge],
) -> List[sm.regression.linear_model.RegressionResultsWrapper]:
    """Fit a linear WLS models to the columns of the passed y array.

    Parameters;
        y: An array of dependent variables as a function of x, e.g., strain, volume.
        x: The control variable, e.g., pressure, temperature.
        x_errors: The errors in the control variable to use as weights for fit

    Returns:
        The results of the fit.

    """
    fit_results = []
    if len(y.shape) > 1:
        n_models = y.shape[1]
        _y = y
    else:
        n_models = 1
        _y = y.reshape(-1, 1)

    for i in range(n_models):
        X = sm.add_constant(x)
        model = sm.WLS(_y[:, i], X, weights=1 / x_errors)

        fit_results.append(model.fit())

    return fit_results

fit_chebyshev(y: ndarray, x: ndarray, max_degree: int) -> Tuple[numpy.ndarray, numpy.ndarray]

Fit a Chebyshev polynomial to the passed y array of strains or volumes.

Parameters:

Name Type Description Default
y ndarray

An array of dependent variables as a function of x, e.g., strain, volume.

required
x ndarray

The control variable, e.g., pressure, temperature.

required
max_degree int

The maximum degree of Chebyshev polynomial to fit.

required

Returns:

Type Description
Tuple[numpy.ndarray, numpy.ndarray]

A tuple containing arrays of the coefficients and residuals of the fit.

Source code in PASCal/fitting.py
def fit_chebyshev(
    y: Union[Strain, Volume],
    x: Charge,
    max_degree: int,
) -> Tuple[np.ndarray, np.ndarray]:
    """Fit a Chebyshev polynomial to the passed y array of
    strains or volumes.

    Parameters:
        y: An array of dependent variables as a function of x, e.g., strain, volume.
        x: The control variable, e.g., pressure, temperature.
        max_degree: The maximum degree of Chebyshev polynomial to fit.

    Returns:
        A tuple containing arrays of the coefficients and residuals of the fit.

    """
    coeffs, (residuals, _, _, _) = np.polynomial.chebyshev.chebfit(
        x, y, max_degree, full=True
    )
    return coeffs.T, residuals

fit_empirical_strain_pressure(diagonal_strain: ndarray, pressure: ndarray, pressure_errors: ndarray, linear_fit_results: List[statsmodels.regression.linear_model.RegressionResultsWrapper], empirical_function: Callable[[numpy.ndarray, numpy.ndarray, Tuple[float, ...]], numpy.ndarray] = <function empirical_pressure_strain_relation at 0x7f1f931a4790>) -> Tuple[List[numpy.ndarray], List[numpy.ndarray], Callable]

Fit an empirical function to the diagonal strain vs pressure data.

Parameters:

Name Type Description Default
diagonal_strain ndarray

The array of diagonal strains.

required
pressure ndarray

The array of pressures.

required
pressure_errors ndarray

The errors in the pressure.

required
linear_fit_results List[statsmodels.regression.linear_model.RegressionResultsWrapper]

A list of statsmodel linear regression results for each direction, used as a preconditioner.

required
empirical_function Callable[[numpy.ndarray, numpy.ndarray, Tuple[float, ...]], numpy.ndarray]

The function to fit to the data which accepts pressure and popt fit parameters as arguments. (default: PASCal.utils.empirical_pressure),

<function empirical_pressure_strain_relation at 0x7f1f931a4790>

Returns:

Type Description
Tuple[List[numpy.ndarray], List[numpy.ndarray], Callable]

A tuple of popt and pcov results for each strain direction, and the empirical function used.

Source code in PASCal/fitting.py
def fit_empirical_strain_pressure(
    diagonal_strain: Strain,
    pressure: Pressure,
    pressure_errors: Pressure,
    linear_fit_results: List[sm.regression.linear_model.RegressionResultsWrapper],
    empirical_function: Callable[
        [Pressure, Strain, Tuple[float, ...]], np.ndarray
    ] = empirical_pressure_strain_relation,  # type: ignore[assignment]
) -> Tuple[List[np.ndarray], List[np.ndarray], Callable]:
    """Fit an empirical function to the diagonal strain vs pressure data.

    Parameters:
        diagonal_strain: The array of diagonal strains.
        pressure: The array of pressures.
        pressure_errors: The errors in the pressure.
        linear_fit_results: A list of statsmodel linear regression results
            for each direction, used as a preconditioner.
        empirical_function: The function to fit to the data which accepts
            pressure and `popt` fit parameters as arguments.
            (default: `PASCal.utils.empirical_pressure`),

    Returns:
        A tuple of `popt` and `pcov` results for each strain direction,
        and the empirical function used.

    """

    bounds = (
        [-1e3, -1e3, -1e3, 0],
        [1e3, 1e3, np.min(pressure), 1e3],
    )

    popts: List[np.ndarray] = []
    pcovs: List[np.ndarray] = []

    for i in range(3):
        # todo check param order
        init_params = np.array(
            [
                linear_fit_results[i].params[1],
                linear_fit_results[i].params[0],
                np.min(pressure) - 0.001,
                0.5,
            ]
        )
        popt, pcov = curve_fit(
            empirical_function,
            pressure,
            diagonal_strain[:, i],
            p0=init_params,
            bounds=bounds,
            sigma=pressure_errors,
            maxfev=100000,
        )

        popts.append(popt)
        pcovs.append(pcov)

    return popts, pcovs, empirical_function

fit_birch_murnaghan_volume_pressure(cell_volumes: ndarray, pressure: ndarray, pressure_errors: ndarray, critical_pressure: Optional[float] = None) -> Tuple[Dict[Callable, numpy.ndarray], Dict[Callable, numpy.ndarray]]

Make a series of Birch-Murnaghan (BM) fits to the cell volume vs pressure data.

Parameters:

Name Type Description Default
cell_volumes ndarray

The array of cell volumes.

required
pressure ndarray

The array of pressures.

required
pressure_errors ndarray

The errors in the pressure.

required
critical_pressure Optional[float]

A critical pressure to use for the modified 3rd order BM fit (optional).

None

Returns:

Type Description
Tuple[Dict[Callable, numpy.ndarray], Dict[Callable, numpy.ndarray]]

Two dictionaries of popt and pcov results, keyed by the fitting function used.

Source code in PASCal/fitting.py
def fit_birch_murnaghan_volume_pressure(
    cell_volumes: Volume,
    pressure: Pressure,
    pressure_errors: Pressure,
    critical_pressure: Optional[float] = None,
) -> Tuple[Dict[Callable, np.ndarray], Dict[Callable, np.ndarray]]:
    """Make a series of Birch-Murnaghan (BM) fits to the cell volume vs pressure data.

    Parameters:
        cell_volumes: The array of cell volumes.
        pressure: The array of pressures.
        pressure_errors: The errors in the pressure.
        critical_pressure: A critical pressure to use for the modified 3rd order BM fit (optional).

    Returns:
        Two dictionaries of popt and pcov results, keyed by the fitting function used.

    """
    from PASCal.utils import (
        birch_murnaghan_2nd,
        birch_murnaghan_2nd_jac,
        birch_murnaghan_3rd,
        birch_murnaghan_3rd_jac,
        birch_murnaghan_3rd_pc,
    )

    popts: Dict[Callable, np.ndarray] = {}
    pcovs: Dict[Callable, np.ndarray] = {}

    dp = pressure[-1] - pressure[0]
    dV = cell_volumes[-1] - cell_volumes[0]
    b_prime = -cell_volumes[0] * dp / dV

    # init params will use the last fit each round, so start with 2nd order
    init_params_2nd: np.ndarray = np.array(
        [
            cell_volumes[0],
            b_prime,
        ]
    )

    popts[birch_murnaghan_2nd], pcovs[birch_murnaghan_2nd] = curve_fit(
        birch_murnaghan_2nd,
        cell_volumes,
        pressure,
        p0=init_params_2nd,
        sigma=pressure_errors,
        maxfev=5000,
        jac=birch_murnaghan_2nd_jac,
    )

    init_params_3rd: np.ndarray = np.array(
        [cell_volumes[0], popts[birch_murnaghan_2nd][1], b_prime / dp]
    )

    popts[birch_murnaghan_3rd], pcovs[birch_murnaghan_3rd] = curve_fit(
        birch_murnaghan_3rd,
        cell_volumes,
        pressure,
        p0=init_params_3rd,
        sigma=pressure_errors,
        maxfev=5000,
        jac=birch_murnaghan_3rd_jac,
    )

    if critical_pressure is not None:
        fn = partial(birch_murnaghan_3rd_pc, p_c=critical_pressure)
        popts[fn], pcovs[fn] = curve_fit(
            fn,
            cell_volumes,
            pressure,
            p0=popts[birch_murnaghan_3rd],
            sigma=pressure_errors,
            maxfev=5000,
        )

    return popts, pcovs

get_best_chebyshev_strain_fit(strain_fits: Dict[int, Tuple[numpy.ndarray, numpy.ndarray]]) -> Tuple[List[int], List[numpy.ndarray]]

Loop through the Chebyshev strain fits and return the indices of those with the lowest residuals for each strain direction.

Parameters:

Name Type Description Default
strain_fits Dict[int, Tuple[numpy.ndarray, numpy.ndarray]]

The Chebyshev strain fits.

required

Returns:

Type Description
Tuple[List[int], List[numpy.ndarray]]

A list of the best fit degrees for each strain direction, and the corresponding coefficients.

Source code in PASCal/fitting.py
def get_best_chebyshev_strain_fit(
    strain_fits: Dict[int, Tuple[np.ndarray, np.ndarray]]
) -> Tuple[List[int], List[np.ndarray]]:
    """Loop through the Chebyshev strain fits and return the indices of those with
    the lowest residuals for each strain direction.

    Parameters:
        strain_fits: The Chebyshev strain fits.

    Returns:
        A list of the best fit degrees for each strain direction, and
        the corresponding coefficients.

    """
    best_degrees = []
    best_coeffs = []
    for i in range(3):
        residuals = []
        degrees = []
        for deg in strain_fits:
            degrees.append(deg)
            residuals.append(strain_fits[deg][1][i])
        best_degrees.append(degrees[np.argmin(residuals)])
        best_coeffs.append(strain_fits[best_degrees[-1]][0][i])

    return best_degrees, best_coeffs

get_best_chebyshev_volume_fit(volume_fits: Dict[int, Tuple[numpy.ndarray, float]]) -> Tuple[int, numpy.ndarray]

Loop through the Chebyshev strain fits and return the indices of those with the lowest residuals for each strain direction.

Parameters:

Name Type Description Default
strain_fits

The Chebyshev strain fits.

required

Returns:

Type Description
Tuple[int, numpy.ndarray]

A list of the best fit degrees for each strain direction, and the corresponding coefficients.

Source code in PASCal/fitting.py
def get_best_chebyshev_volume_fit(
    volume_fits: Dict[int, Tuple[np.ndarray, float]]
) -> Tuple[int, np.ndarray]:
    """Loop through the Chebyshev strain fits and return the indices of those with
    the lowest residuals for each strain direction.

    Parameters:
        strain_fits: The Chebyshev strain fits.

    Returns:
        A list of the best fit degrees for each strain direction, and
        the corresponding coefficients.

    """
    residuals = []
    degrees = []
    for deg in volume_fits:
        degrees.append(deg)
        residuals.append(volume_fits[deg][1])
    best_degree = degrees[np.argmin(residuals)]
    best_coeffs = volume_fits[best_degree][0]

    return best_degree, best_coeffs