Example #1
0
def test_RMSE():
    a = np.array([2 + 4 * 1j, 3 + 2 * 1j])
    b = np.array([2 + 4 * 1j, 3 + 2 * 1j])

    assert rmse(a, b) == 0.0

    c = np.array([2 + 4 * 1j, 1 + 4 * 1j])
    d = np.array([4 + 2 * 1j, 3 + 2 * 1j])
    assert np.isclose(rmse(c, d), 2 * np.sqrt(2))
Example #2
0
def model_conductivity(freq, complex_z, cutoff, circuit, guess):
    """Takes a row of the dataframe and return the model object, the resistance and the rmse.

    Args:
        row ([type]): [description]

    Returns:
        model: the model object
        resistance: calculated resistance
        rmse: the root mean square error on the resistance
    """
    f, z = pp.cropFrequencies(np.array(freq), complex_z, cutoff)
    f, z = pp.ignoreBelowX(f, z)
    model = model_impedance(circuit, guess, f, z)
    rmse = fitting.rmse(z, model.predict(f))
    resistance = get_resistance(model)
    return model, resistance, rmse
Example #3
0
def linKK(f, Z, c=0.85, max_M=50, fit_type='real', add_cap=False):
    """ A method for implementing the Lin-KK test for validating linearity [1]

    Parameters
    ----------
    f: np.ndarray
        measured frequencies
    Z: np.ndarray of complex numbers
        measured impedances
    c: np.float
        cutoff for mu
    max_M: int
        the maximum number of RC elements
    fit_type: str
        selects which components of data are fit ('real', 'imag', or
        'complex')
    add_cap: bool
        option to add a serial capacitance that helps validate data with no
        low-frequency intercept

    Returns
    -------
    M: int
        number of RC elements used
    mu: np.float
        under- or over-fitting measure
    Z_fit: np.ndarray of complex numbers
        impedance of fit at input frequencies
    resids_real: np.ndarray
        real component of the residuals of the fit at input frequencies
    resids_imag: np.ndarray
        imaginary component of the residuals of the fit at input frequencies


    Notes
    -----

    The lin-KK method from Schönleber et al. [1] is a quick test for checking
    the
    validity of EIS data. The validity of an impedance spectrum is analyzed by
    its reproducibility by a Kramers-Kronig (KK) compliant equivalent circuit.
    In particular, the model used in the lin-KK test is an ohmic resistor,
    :math:`R_{Ohm}`, and :math:`M` RC elements.

    .. math::

        \\hat Z = R_{Ohm} + \\sum_{k=1}^{M} \\frac{R_k}{1 + j \\omega \\tau_k}

    The :math:`M` time constants, :math:`\\tau_k`, are distributed
    logarithmically,

    .. math::
        \\tau_1 = \\frac{1}{\\omega_{max}} ; \\tau_M = \\frac{1}{\\omega_{min}}
        ; \\tau_k = 10^{\\log{(\\tau_{min}) + \\frac{k-1}{M-1}\\log{{(
            \\frac{\\tau_{max}}{\\tau_{min}}}})}}

    and are not fit during the test (only :math:`R_{Ohm}` and :math:`R_{k}`
    are free parameters).

    In order to prevent under- or over-fitting, Schönleber et al. propose using
    the ratio of positive resistor mass to negative resistor mass as a metric
    for finding the optimal number of RC elements.

    .. math::

        \\mu = 1 - \\frac{\\sum_{R_k \\ge 0} |R_k|}{\\sum_{R_k < 0} |R_k|}

    The argument :code:`c` defines the cutoff value for :math:`\\mu`. The
    algorithm starts at :code:`M = 3` and iterates up to :code:`max_M` until a
    :math:`\\mu < c` is reached. The default of 0.85 is simply a heuristic
    value based off of the experience of Schönleber et al., but a lower value
    may give better results.

    If the argument :code:`c` is :code:`None`, then the automatic determination
    of RC elements is turned off and the solution is calculated for
    :code:`max_M` RC elements. This manual mode should be used with caution as
    under- and over-fitting should be avoided.

    [1] Schönleber, M. et al. A Method for Improving the Robustness of
    linear Kramers-Kronig Validity Tests. Electrochimica Acta 131, 20–27 (2014)
    `doi: 10.1016/j.electacta.2014.01.034
    <https://doi.org/10.1016/j.electacta.2014.01.034>`_.

    """

    if c is not None:
        M = 0
        mu = 1
        while mu > c and M <= max_M:
            M += 1
            ts = get_tc_distribution(f, M)
            elements, mu = fit_linKK(f, ts, M, Z, fit_type, add_cap)

            if M % 10 == 0:
                print(M, mu, rmse(eval_linKK(elements, ts, f), Z))
    else:
        M = max_M
        ts = get_tc_distribution(f, M)
        elements, mu = fit_linKK(f, ts, M, Z, fit_type, add_cap)

    Z_fit = eval_linKK(elements, ts, f)
    resids_real = residuals_linKK(elements, ts, Z, f, residuals='real')
    resids_imag = residuals_linKK(elements, ts, Z, f, residuals='imag')
    return M, mu, Z_fit, resids_real, resids_imag
Example #4
0
def linKK(f, Z, c=0.85, max_M=50):
    """ A method for implementing the Lin-KK test for validating linearity [1]

    Parameters
    ----------
    f: np.ndarray
        measured frequencies
    Z: np.ndarray of complex numbers
        measured impedances
    c: np.float
        cutoff for mu
    max_M: int
        the maximum number of RC elements

    Returns
    -------
    mu: np.float
        under- or over-fitting measure
    residuals: np.ndarray of complex numbers
        the residuals of the fit at input frequencies
    Z_fit: np.ndarray of complex numbers
        impedance of fit at input frequencies

    Notes
    -----

    The lin-KK method from Schönleber et al. [1] is a quick test for checking
    the
    validity of EIS data. The validity of an impedance spectrum is analyzed by
    its reproducibility by a Kramers-Kronig (KK) compliant equivalent circuit.
    In particular, the model used in the lin-KK test is an ohmic resistor,
    :math:`R_{Ohm}`, and :math:`M` RC elements.

    .. math::

        \\hat Z = R_{Ohm} + \\sum_{k=1}^{M} \\frac{R_k}{1 + j \\omega \\tau_k}

    The :math:`M` time constants, :math:`\\tau_k`, are distributed
    logarithmically,

    .. math::
        \\tau_1 = \\frac{1}{\\omega_{max}} ; \\tau_M = \\frac{1}{\\omega_{min}}
        ; \\tau_k = 10^{\\log{(\\tau_{min}) + \\frac{k-1}{M-1}\\log{{(
            \\frac{\\tau_{max}}{\\tau_{min}}}})}}

    and are not fit during the test (only :math:`R_{Ohm}` and :math:`R_{k}`
    are free parameters).

    In order to prevent under- or over-fitting, Schönleber et al. propose using
    the ratio of positive resistor mass to negative resistor mass as a metric
    for finding the optimal number of RC elements.

    .. math::

        \\mu = 1 - \\frac{\\sum_{R_k \\ge 0} |R_k|}{\\sum_{R_k < 0} |R_k|}

    The argument :code:`c` defines the cutoff value for :math:`\\mu`. The
    algorithm starts at :code:`M = 3` and iterates up to :code:`max_M` until a
    :math:`\\mu < c` is reached. The default of 0.85 is simply a heuristic
    value based off of the experience of Schönleber et al.

    If the argument :code:`c` is :code:`None`, then the automatic determination
    of RC elements is turned off and the solution is calculated for
    :code:`max_M` RC elements. This manual mode should be used with caution as
    under- and over-fitting should be avoided.

    [1] Schönleber, M. et al. A Method for Improving the Robustness of
    linear Kramers-Kronig Validity Tests. Electrochimica Acta 131, 20–27 (2014)
    `doi: 10.1016/j.electacta.2014.01.034
    <https://doi.org/10.1016/j.electacta.2014.01.034>`_.

    """
    def get_tc_distribution(f, M):
        """ Returns the distribution of time constants for the linKK method """

        t_max = 1 / (2 * np.pi * np.min(f))
        t_min = 1 / (2 * np.pi * np.max(f))

        ts = np.zeros(shape=(M, ))
        ts[0] = t_min
        ts[-1] = t_max
        if M > 1:
            for k in range(2, M):
                ts[k - 1] = 10**(np.log10(t_min) +
                                 ((k - 1) / (M - 1)) * np.log10(t_max / t_min))

        return ts

    R0 = min(np.real(Z))
    if c is not None:
        M = 0
        mu = 1
        while mu > c and M <= max_M:
            M += 1
            ts = get_tc_distribution(f, M)
            p_values, mu = fitLinKK(f, ts, M, Z)

            if M % 10 == 0:
                print(M, mu, rmse(eval_linKK(p_values, R0, ts, f), Z))
    else:
        M = max_M
        ts = get_tc_distribution(f, M)
        p_values, mu = fitLinKK(f, ts, M, Z)

    return M, mu, eval_linKK(p_values, R0, ts, f), \
        residuals_linKK(p_values, R0, ts, Z, f, residuals='real'), \
        residuals_linKK(p_values, R0, ts, Z, f, residuals='imag')