def test_fit_rc_c_1nF(c_data_1nF): actual_res, actual_cap, _ = \ fit_impedance(c_data_1nF, model='rc', model_config='parallel', p0=(1, 1)) desired_res, desired_cap = [1e13, 1e-9 / (2 * np.pi)] assert_allclose(actual_cap, desired_cap, atol=1e-22)
def test_fit_rc_series_complex(rc_data_complex): actual_res, actual_cap, impedance_func = \ fit_impedance(rc_data_complex, model='rc', model_config='series', p0=(1, 1)) desired_res, desired_cap = 1000, 1e-9 assert_allclose(actual_cap, desired_cap, rtol=1e-6) assert_allclose(actual_res, desired_res, rtol=1e-6)
def test_fit_rc_series(rc_data_series): data = rc_data_series['data'] actual_res, actual_cap, _ = \ fit_impedance(data, model='rc', model_config='series', p0=(1, 1)) desired_res, desired_cap = rc_data_series['params'] assert_allclose(actual_cap, desired_cap, rtol=1e-5) assert_allclose(actual_res, desired_res, rtol=1e-5)
def test_fit_rc_r_only(r_data): actual_res, actual_cap, _ = \ fit_impedance(r_data, model='rc', model_config='parallel', p0=(10, 1)) desired_res, desired_cap = [1, 0] actual_params = np.array([actual_res, actual_cap]) desired_params = np.array([desired_res, desired_cap]) assert_allclose(actual_params, desired_params, atol=1e-10)
def test_fit_rc_parallel(rc_data_parallel): data = rc_data_parallel['data'] actual_res, actual_cap, impedance_func = \ fit_impedance(data, model='rc', model_config='parallel', p0=(1, 1)) desired_res, desired_cap = rc_data_parallel['params'] if abs(np.log10(2 * np.pi * desired_res * desired_cap * 100)) > 5: rtol = 1e-1 else: rtol = 1e-4 assert_allclose(actual_cap, desired_cap, rtol=rtol) assert_allclose(actual_res, desired_res, rtol=rtol)
def plot_impedance(data, fit=True, model='rc', model_config='series', **kwargs): """ Plots the magnitude / phase of a set of impedance data. :param fit: Whether to attempt to fit an impedance model to the data :param model: Real/reactive model - options are "rc" :param model_config: Model configuration, either "series" or "parallel" """ # The challenge here is that we need to plot two things with the same x axis and different y axes: the magnitude and phase data. if data.shape[1] == 2: data_complex = True z_data = column_from_unit(data, ureg.ohm).to(ureg.ohm).m phase_data = np.angle(z_data) magnitude_data = np.abs(z_data) data_to_plot = pd.DataFrame({ 'Frequency (Hz)': data.iloc[:, 0], '|Z| (ohm)': magnitude_data, 'Phase (deg)': phase_data * 180 / np.pi }) else: data_complex = False phase_data = column_from_unit(data, ureg.rad).to(ureg.deg).m phase_name = cname_from_unit(data, ureg.rad) data_to_plot = data.rename(columns={phase_name: 'Phase (deg)'}) data_to_plot['Phase (deg)'] = phase_data if fit: _, _, impedance_func = fit_impedance(data) # This needs to return a functional form for the impedance vs. frequency in addition to the relevant parameters, as it's not clear freq_data = column_from_unit(data, ureg.Hz) min_log = np.log10(freq_data.min().to(ureg.Hz).m) max_log = np.log10(freq_data.max().to(ureg.Hz).m) freq_theory_data = np.logspace(min_log, max_log, 100) impedance_theory_data = impedance_func(freq_theory_data) mag_theory_data = abs(impedance_theory_data) phase_theory_data = np.angle(impedance_theory_data) * 180 / np.pi theory_data = pd.DataFrame({ 'Frequency (Hz)': freq_theory_data, 'Z (ohm)': mag_theory_data, 'Phase (deg)': phase_theory_data }) else: theory_data = None theory_to_plot = None subplot_kw = {'xscale': 'log', 'yscale': 'log'} if theory_data is not None: theory_to_plot = theory_data.iloc[:, [0, 1]] kwargs.update(subplot_kw=subplot_kw, theory_data=theory_to_plot, theory_name='|Z| (Fit)', plot_type='scatter') fig, ax = default_plotter(data_to_plot.iloc[:, [0, 1]], **kwargs) if theory_data is not None: theory_to_plot = theory_data.iloc[:, [0, -1]] kwargs.update(subplot_kw=subplot_kw, fig=fig, ax=ax, theory_data=theory_to_plot, theory_name='Phase (Fit)', plot_type='scatter') fig, ax = default_plotter(data_to_plot.iloc[:, [0, -1]], **kwargs) prettifyPlot(fig=fig, ax=ax) return fig, ax