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))
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
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
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')