### Create example directory and launch Jupyter Lab Source: https://impedancepy.readthedocs.io/en/latest/_sources/getting-started.rst.txt Optional: creates a directory for the example project and then starts Jupyter Lab, which opens a new tab in your browser. ```bash mkdir ~/Documents/impedance-example cd ~/Documents/impedance-example ``` ```bash jupyter lab ``` -------------------------------- ### Load libraries Source: https://impedancepy.readthedocs.io/en/latest/examples/model_io_example.html Initial setup importing necessary modules for preprocessing, circuit modeling, visualization, and numerical operations. ```python # Load libraries import impedance.preprocessing as preprocessing import impedance.models.circuits as circuits from impedance.visualization import plot_nyquist import numpy as np import matplotlib.pyplot as plt ``` -------------------------------- ### Install impedance.py with pip Source: https://impedancepy.readthedocs.io/en/latest/_sources/index.rst.txt Install the impedance.py package from PyPI using pip. This is the recommended installation method. ```bash pip install impedance ``` -------------------------------- ### Importing linKK and dependencies Source: https://impedancepy.readthedocs.io/en/latest/_sources/examples/validation_example.ipynb.txt Initial setup to import necessary libraries and the linKK validation module. ```python import matplotlib.pyplot as plt import numpy as np import sys sys.path.append('../../../') from impedance.validation import linKK ``` -------------------------------- ### Install impedance.py and Jupyter Lab Source: https://impedancepy.readthedocs.io/en/latest/getting-started.html Installs impedance.py and its dependencies (scipy, numpy, matplotlib) via pip, and installs Jupyter Lab via conda for the example notebook. ```bash pip install impedance ``` ```bash conda install jupyter jupyterlab ``` -------------------------------- ### Install Miniconda: update conda and verify installation Source: https://impedancepy.readthedocs.io/en/latest/_sources/getting-started.rst.txt Provides essential command-line commands to update conda, list installed packages, and check the Python version after installing Miniconda. A successful conda install should display a package list and report Python version like 'Python 3.x.x :: Anaconda, Inc.'. ```bash conda update conda ``` ```bash conda list ``` ```bash python -V ``` -------------------------------- ### Install Jupyter Lab with conda Source: https://impedancepy.readthedocs.io/en/latest/_sources/getting-started.rst.txt Install Jupyter Lab and Jupyter through conda for interactive EIS analysis. ```bash conda install jupyter jupyterlab ``` -------------------------------- ### Fit a measurement model with CustomCircuit Source: https://impedancepy.readthedocs.io/en/latest/examples/validation_example.html Builds a measurement model with 10 parallel R-C elements, fits it to the example EIS data, and prints the fitted parameters. The circuit string is constructed dynamically and the initial guesses are set with a geometric progression. ```python import matplotlib.pyplot as plt import numpy as np from impedance.models.circuits import CustomCircuit from impedance import preprocessing ``` ```python # Load data from the example EIS result f, Z = preprocessing.readCSV('../../../data/exampleData.csv') # keep only the impedance data in the first quandrant f, Z = preprocessing.ignoreBelowX(f, Z) mask = f < 1000 f = f[mask] Z = Z[mask] ``` ```python N = 10 circuit = 'R_0' initial_guess = [.015] for i in range(N): circuit += f'-p(R_{i % 9 + 1},C_{i % 9 + 1})' initial_guess.append(.03/N) initial_guess.append(10**(3 - 6*i/N)) meas_model = CustomCircuit(initial_guess=initial_guess, circuit=circuit) ``` ```python meas_model.fit(f, Z) print(meas_model) ``` -------------------------------- ### Load template with fitted parameters as initial guesses Source: https://impedancepy.readthedocs.io/en/latest/examples/model_io_example.html Loads a saved model template and sets the fitted_as_initial parameter to True to use previous results as starting guesses. ```python fitted_template = circuits.CustomCircuit() fitted_template.load('fitted_template.json', fitted_as_initial=True) print(fitted_template) ``` -------------------------------- ### Perform Lin-KK fit with linKK Source: https://impedancepy.readthedocs.io/en/latest/examples/validation_example.html Runs the Lin-KK fit with a cutoff value c=0.5, a maximum of 100 RC elements, complex fitting, and an added capacitance. The output shows the number of RC elements (M) and the mu metric, which is 0.31 for this example. ```python M, mu, Z_linKK, res_real, res_imag = linKK(f, Z, c=.5, max_M=100, fit_type='complex', add_cap=True) print('\nCompleted Lin-KK Fit\nM = {:d}\nmu = {:.2f}'.format(M, mu)) ``` -------------------------------- ### Import libraries for measurement model Source: https://impedancepy.readthedocs.io/en/latest/_sources/examples/validation_example.ipynb.txt Imports required for the measurement model example: matplotlib for plotting, numpy, CustomCircuit from impedance.models.circuits, and preprocessing from impedance. ```python import matplotlib.pyplot as plt import numpy as np from impedance.models.circuits import CustomCircuit from impedance import preprocessing ``` -------------------------------- ### Fit new data using previous template Source: https://impedancepy.readthedocs.io/en/latest/examples/model_io_example.html Fits the shifted data Z2 using the previously fitted template as a starting point and plots the results. ```python fig, ax = plt.subplots(figsize=(10,10)) f_pred = np.logspace(5,-2) fitted_template.fit(frequencies, Z2) imported_circuit_init = fitted_template.predict(f_pred, use_initial = True) imported_circuit_fit = fitted_template.predict(f_pred) plot_nyquist(ax, Z) plot_nyquist(ax, Z2) plot_nyquist(ax, imported_circuit_init) plot_nyquist(ax, imported_circuit_fit) ax.legend(['Original Data', 'Shifted Data', 'Template Initial', 'Loaded Template Fit']) plt.show() print(fitted_template) ``` -------------------------------- ### Find .z files matching Circuit*EIS* pattern Source: https://impedancepy.readthedocs.io/en/latest/examples/looping_files_example.html Uses glob with a pattern that matches .z files starting with 'Circuit' and containing 'EIS' to list all matching files in the data directory. The output shows the six files found. ```python directory = r'../../../data/' all_files = glob.glob(os.path.join(directory, 'Circuit*EIS*.z')) all_files ``` ```output ['../../../data/Circuit3_EIS_1.z', '../../../data/Circuit1_EIS_2.z', '../../../data/Circuit2_EIS_1.z', '../../../data/Circuit3_EIS_2.z', '../../../data/Circuit1_EIS_1.z', '../../../data/Circuit2_EIS_2.z'] ``` -------------------------------- ### Import required libraries for impedance plotting Source: https://impedancepy.readthedocs.io/en/latest/examples/plotting_example.html Imports matplotlib, numpy, CustomCircuit, and preprocessing modules needed for the examples on this page. ```python import matplotlib.pyplot as plt import numpy as np from impedance.models.circuits import CustomCircuit from impedance import preprocessing ``` -------------------------------- ### Randles circuit setup and validation Source: https://impedancepy.readthedocs.io/en/latest/_modules/impedance/models/circuits/circuits.html Sets the circuit name and string for a Randles circuit, optionally with a constant phase element (CPE). Then validates that the number of initial guesses plus constants matches the circuit length, raising a ValueError otherwise. ```python if CPE: self.name = 'Randles w/ CPE' self.circuit = 'R0-p(R1-Wo1,CPE1)' else: self.name = 'Randles' self.circuit = 'R0-p(R1-Wo1,C1)' circuit_len = calculateCircuitLength(self.circuit) if len(self.initial_guess) + len(self.constants) != circuit_len: raise ValueError('The number of initial guesses ' + f'({len(self.initial_guess)}) + ' + 'the number of constants ' + f'({len(self.constants)})' + ' must be equal to ' + f'the circuit length ({circuit_len})') ``` -------------------------------- ### Load and preprocess EIS data Source: https://impedancepy.readthedocs.io/en/latest/examples/validation_example.html Loads example EIS data from a CSV file, filters out points with negative real impedance, and keeps only frequencies below 1000 Hz. The resulting f and Z arrays are used as input for the Lin-KK fit. ```python # Load data from the example EIS result f, Z = preprocessing.readCSV('../../../data/exampleData.csv') # keep only the impedance data in the first quandrant f, Z = preprocessing.ignoreBelowX(f, Z) mask = f < 1000 f = f[mask] Z = Z[mask] ``` -------------------------------- ### Load and filter EIS data Source: https://impedancepy.readthedocs.io/en/latest/_sources/examples/validation_example.ipynb.txt Loads example EIS data from a CSV file, filters to the first quadrant using ignoreBelowX, and keeps only frequencies below 1000 Hz. ```python # Load data from the example EIS result f, Z = preprocessing.readCSV('../../../data/exampleData.csv') # keep only the impedance data in the first quandrant f, Z = preprocessing.ignoreBelowX(f, Z) mask = f < 1000 f = f[mask] Z = Z[mask] ``` -------------------------------- ### fit_linKK function definition (partial) Source: https://impedancepy.readthedocs.io/en/latest/_modules/impedance/validation.html Starts the definition of the fit_linKK function that fits the Lin-KK model using linear regression. The function signature and docstring are shown; the body is not displayed in the source. ```python def fit_linKK(f, ts, M, Z, fit_type='real', add_cap=False): """ Fits the linKK model using linear regression ``` -------------------------------- ### Create and fit circuit models Source: https://impedancepy.readthedocs.io/en/latest/_sources/examples/looping_files_example.ipynb.txt Initializes CustomCircuit objects for each dataset and performs the fitting process. ```python from impedance.models.circuits import CustomCircuit # This data comes from dummy circuits I made to check measurement bias in # our potentiostat, so I know a priori its an R-RC circuit circuits = [] circ_string = 'R0-p(R1,C1)' initial_guess = [100, 400, 1e-5] # Now loop through data list to create circuits and fit them for f, Z, filename in zip(freqs, Zs, all_files): name = filename.split('/')[-1] circuit = CustomCircuit(circ_string, initial_guess=initial_guess, name=name) circuit.fit(f, Z) circuits.append(circuit) ``` -------------------------------- ### Create CustomCircuit with circuit string and initial guesses (impedance.py) Source: https://impedancepy.readthedocs.io/en/latest/_sources/getting-started.rst.txt Import the CustomCircuit object, define the circuit string using the syntax of series elements with '-' and parallel elements with 'p( , )', provide an initial guess list in parameter definition order, and instantiate the circuit. ```python from impedance.models.circuits import CustomCircuit circuit = 'R0-p(R1,C1)-p(R2-Wo1,C2)' initial_guess = [.01, .01, 100, .01, .05, 100, 1] circuit = CustomCircuit(circuit, initial_guess=initial_guess) ``` -------------------------------- ### Loop over data files to create and fit CustomCircuit objects Source: https://impedancepy.readthedocs.io/en/latest/examples/looping_files_example.html Creates a list of CustomCircuit objects from a list of impedance data files, fitting each with the same circuit string and initial guess. The loop uses zip over frequencies, impedance data, and filenames, and stores the fitted circuits in a list. ```python from impedance.models.circuits import CustomCircuit # This data comes from dummy circuits I made to check measurement bias in # our potentiostat, so I know a priori its an R-RC circuit circuits = [] circ_string = 'R0-p(R1,C1)' initial_guess = [100, 400, 1e-5] # Now loop through data list to create circuits and fit them for f, Z, filename in zip(freqs, Zs, all_files): name = filename.split('/')[-1] circuit = CustomCircuit(circ_string, initial_guess=initial_guess, name=name) circuit.fit(f, Z) circuits.append(circuit) ``` -------------------------------- ### Import impedance data with preprocessing.readCSV Source: https://impedancepy.readthedocs.io/en/latest/getting-started.html Loads impedance data from a CSV file with columns frequency, Z_real, and Z_imag, then filters to keep only data in the first quadrant. Requires the file exampleData.csv in the current working directory. ```python from impedance import preprocessing # Load data from the example EIS data frequencies, Z = preprocessing.readCSV('./exampleData.csv') # keep only the impedance data in the first quandrant frequencies, Z = preprocessing.ignoreBelowX(frequencies, Z) ``` -------------------------------- ### readCHInstruments function Source: https://impedancepy.readthedocs.io/en/latest/_modules/impedance/preprocessing.html Reads impedance data from a CHInstruments .txt file. It locates the data starting after the 'Freq/Hz' header. ```python def readCHInstruments(filename): """ function for reading the .txt file from CHInstruments Parameters ---------- filename: string Filename of .txt file to extract impedance data from Returns ------- frequencies : np.ndarray Array of frequencies impedance : np.ndarray of complex numbers Array of complex impedances """ with open(filename, 'r') as input_file: lines = input_file.readlines() # Locate the line where the data lives for i, line in enumerate(lines): if line.startswith('Freq/Hz'): # CH instruments has an empty space b/w header # and start of data line start_line = i+2 raw_data = lines[start_line:] f, Z = [], [] for line in raw_data: each = line.split(',') f.append(float(each[0])) Z.append(complex(float(each[1]), float(each[2]))) return np.array(f), np.array(Z) ``` -------------------------------- ### Import visualization tools Source: https://impedancepy.readthedocs.io/en/latest/_sources/examples/looping_files_example.ipynb.txt Import matplotlib and the impedance visualization functions for plotting. ```python import matplotlib.pyplot as plt from impedance.visualization import plot_nyquist, plot_bode ``` -------------------------------- ### BaseCircuit.__init__ Source: https://impedancepy.readthedocs.io/en/latest/_modules/impedance/models/circuits/circuits.html Constructor for any equivalent circuit model. Initializes the circuit with an initial guess, optional constants, and a name. ```APIDOC ## BaseCircuit.__init__ ### Description Initializes a BaseCircuit instance with an initial guess, optional constants, and a name. ### Method Constructor ### Parameters - **initial_guess** (numpy array) - Optional - Initial guess of the circuit values. - **constants** (dict, optional) - Optional - Parameters and values to hold constant during fitting (e.g. {"R0": 0.1}). - **name** (str, optional) - Optional - Name for the circuit. ### Returns None ``` -------------------------------- ### Create and save a circuit template Source: https://impedancepy.readthedocs.io/en/latest/_sources/examples/model_io_example.ipynb.txt Creates a custom circuit with initial guesses, prints it, and saves it as a JSON template using the `circuit.save()` function. ```python test_circuit = circuits.CustomCircuit(initial_guess=[.01, .005, .1, .005, .1, .001, 200], circuit='R0-p(R1,C1)-p(R2,C2)-Wo1') print(test_circuit) test_circuit.save('template_model.json') ``` -------------------------------- ### readVersaStudio(filename) Source: https://impedancepy.readthedocs.io/en/latest/preprocessing.html Reads the .PAR file from VersaStudio. ```APIDOC ## readVersaStudio(filename) ### Description Function for reading the .PAR file from VersaStudio. ### Method Function call ### Endpoint impedance.preprocessing.readVersaStudio ### Parameters - **filename** (string) - Required - Filename of .PAR file to extract impedance data from ### Returns - **frequencies** (np.ndarray) - Array of frequencies - **impedance** (np.ndarray of complex numbers) - Array of complex impedances ``` -------------------------------- ### Importing circuit models Source: https://impedancepy.readthedocs.io/en/latest/examples/fitting_example.html Import the Randles and CustomCircuit classes from the impedance package. ```python from impedance.models.circuits import Randles, CustomCircuit ``` -------------------------------- ### BaseCircuit.load Source: https://impedancepy.readthedocs.io/en/latest/circuits.html Imports a model from JSON. ```APIDOC ## BaseCircuit.load ### Description Imports a model from JSON. ### Method load ### Signature load(filepath, fitted_as_initial=False) ### Parameters - **filepath** (str) - Required - filepath to JSON file to load model from - **fitted_as_initial** (bool) - Optional - If true, loads the model's fitted parameters as initial guesses. Otherwise, loads the model's initial and fitted parameters as a completed model ``` -------------------------------- ### Fit a custom circuit to impedance data Source: https://impedancepy.readthedocs.io/en/latest/examples/plotting_example.html Creates a CustomCircuit with an initial guess and circuit string, fits it to the data, and prints the circuit details. The output shows the circuit string, fit status, initial guesses, and fitted parameters with uncertainties. ```python circuit = CustomCircuit(initial_guess=[.01, .005, .1, .005, .1, .001, 200], circuit='R_0-p(R_1,C_1)-p(R_1,C_1)-Wo_1') circuit.fit(frequencies, Z) print(circuit) ``` ```text Circuit string: R_0-p(R_1,C_1)-p(R_1,C_1)-Wo_1 Fit: True Initial guesses: R_0 = 1.00e-02 [Ohm] R_1 = 5.00e-03 [Ohm] C_1 = 1.00e-01 [F] R_1 = 5.00e-03 [Ohm] C_1 = 1.00e-01 [F] Wo_1_0 = 1.00e-03 [Ohm] Wo_1_1 = 2.00e+02 [sec] Fit parameters: R_0 = 1.65e-02 (+/- 1.54e-04) [Ohm] R_1 = 5.31e-03 (+/- 2.06e-04) [Ohm] C_1 = 2.32e-01 (+/- 1.90e-02) [F] R_1 = 8.77e-03 (+/- 1.89e-04) [Ohm] C_1 = 3.28e+00 (+/- 1.85e-01) [F] Wo_1_0 = 6.37e-02 (+/- 2.03e-03) [Ohm] Wo_1_1 = 2.37e+02 (+/- 1.72e+01) [sec] ``` -------------------------------- ### load method: import model from JSON Source: https://impedancepy.readthedocs.io/en/latest/_modules/impedance/models/circuits/circuits.html Loads a model from a JSON file. Reads the circuit string, initial guess, constants, and name. If the JSON indicates a fit, either uses the fitted parameters as initial guesses (when fitted_as_initial is True) or sets them as the model's fitted parameters and confidence intervals. ```python json_data_file = open(filepath, 'r') json_data = json.load(json_data_file) model_name = json_data["Name"] model_string = json_data["Circuit String"] model_initial_guess = json_data["Initial Guess"] model_constants = json_data["Constants"] self.initial_guess = model_initial_guess self.circuit = model_string print(self.circuit) self.constants = model_constants self.name = model_name if json_data["Fit"]: if fitted_as_initial: self.initial_guess = np.array(json_data['Parameters']) else: self.parameters_ = np.array(json_data["Parameters"]) self.conf_ = np.array(json_data["Confidence"]) ``` -------------------------------- ### Create and activate conda environment Source: https://impedancepy.readthedocs.io/en/latest/_sources/getting-started.rst.txt Creates a conda environment with Python 3 and activates it with name 'impedance-analysis'. ```bash conda create -n impedance-analysis python=3 ``` ```bash conda activate impedance-analysis ``` -------------------------------- ### Initializing Randles circuit Source: https://impedancepy.readthedocs.io/en/latest/examples/fitting_example.html Initialize Randles circuit instances with initial parameter guesses, optionally enabling a constant phase element (CPE). ```python randles = Randles(initial_guess=[.01, .005, .001, 200, .1]) randlesCPE = Randles(initial_guess=[.01, .005, .001, 200, .1, .9], CPE=True) ``` -------------------------------- ### Import model from JSON Source: https://impedancepy.readthedocs.io/en/latest/examples/model_io_example.html Loads a previously saved circuit model template from a JSON file into a new CustomCircuit instance. ```python loaded_template = circuits.CustomCircuit() loaded_template.load('template_model.json') print("Loaded Template") print(loaded_template) ``` -------------------------------- ### Fit Equivalent Circuits to Spectrum Source: https://impedancepy.readthedocs.io/en/latest/examples/fitting_example.html Execute the .fit() method on circuit objects to determine parameters, followed by printing the circuit object to view fit results. ```python randles.fit(frequencies, Z) randlesCPE.fit(frequencies, Z) customCircuit.fit(frequencies, Z) customConstantCircuit.fit(frequencies, Z) print(customConstantCircuit) ``` ```text Circuit string: R_0-p(R_1,C_1)-p(R_2,C_2)-Wo_1 Fit: True Constants: R_0 = 2.00e-02 [Ohm] Wo_1_1 = 2.00e+02 [sec] Initial guesses: R_1 = 5.00e-03 [Ohm] C_1 = 1.00e-01 [F] R_2 = 5.00e-03 [Ohm] C_2 = 1.00e-01 [F] Wo_1_0 = 1.00e-03 [Ohm] Fit parameters: R_1 = 6.79e-03 (+/- 1.08e-03) [Ohm] C_1 = 5.62e+00 (+/- 1.96e+00) [F] R_2 = 3.91e-03 (+/- 1.09e-03) [Ohm] C_2 = 1.36e+00 (+/- 2.61e-01) [F] Wo_1_0 = 5.88e-02 (+/- 1.25e-03) [Ohm] ``` -------------------------------- ### Fit data using an imported template model Source: https://impedancepy.readthedocs.io/en/latest/examples/model_io_example.html Uses a loaded template to fit data and visualize the initial versus fitted circuit response on a Nyquist plot. ```python fig, ax = plt.subplots(figsize=(5,5)) f_pred = np.logspace(5,-2) loaded_template.fit(frequencies, Z) imported_circuit_init = loaded_template.predict(f_pred, use_initial = True) imported_circuit_fit = loaded_template.predict(f_pred) plot_nyquist(ax, Z) plot_nyquist(ax, imported_circuit_init) plot_nyquist(ax, imported_circuit_fit) ax.legend(['Data', 'Loaded Template Initial', 'Loaded Template Fit']) plt.show() print(loaded_template) ``` -------------------------------- ### Construct measurement model circuit Source: https://impedancepy.readthedocs.io/en/latest/_sources/examples/validation_example.ipynb.txt Builds a measurement model circuit with 10 parallel R-C elements and initial guesses, then creates a CustomCircuit instance. ```python N = 10 circuit = 'R_0' initial_guess = [.015] for i in range(N): circuit += f'-p(R_{i % 9 + 1},C_{i % 9 + 1})' initial_guess.append(.03/N) initial_guess.append(10**(3 - 6*i/N)) meas_model = CustomCircuit(initial_guess=initial_guess, circuit=circuit) ``` -------------------------------- ### Print fitted circuits and their output Source: https://impedancepy.readthedocs.io/en/latest/examples/looping_files_example.html Prints each fitted circuit object, showing the circuit name, circuit string, fit status, initial guesses, and fit parameters with uncertainties. The output demonstrates the expected similarity among replicates of the same circuit. ```python for circuit in circuits: print(circuit) ``` ```text Name: Circuit3_EIS_1.z Circuit string: R0-p(R1,C1) Fit: True Initial guesses: R0 = 1.00e+02 [Ohm] R1 = 4.00e+02 [Ohm] C1 = 1.00e-05 [F] Fit parameters: R0 = 1.51e+03 (+/- 2.62e+00) [Ohm] R1 = 4.63e+03 (+/- 3.14e+00) [Ohm] C1 = 2.02e-08 (+/- 5.39e-11) [F] Name: Circuit1_EIS_2.z Circuit string: R0-p(R1,C1) Fit: True Initial guesses: R0 = 1.00e+02 [Ohm] R1 = 4.00e+02 [Ohm] C1 = 1.00e-05 [F] Fit parameters: R0 = 2.91e+01 (+/- 3.63e-02) [Ohm] R1 = 4.67e+01 (+/- 4.69e-02) [Ohm] C1 = 1.04e-05 (+/- 2.95e-08) [F] Name: Circuit2_EIS_2.z Circuit string: R0-p(R1,C1) Fit: True Initial guesses: R0 = 1.00e+02 [Ohm] R1 = 4.00e+02 [Ohm] C1 = 1.00e-05 [F] Fit parameters: R0 = 1.50e+02 (+/- 3.19e-01) [Ohm] R1 = 5.02e+02 (+/- 3.53e-01) [Ohm] C1 = 3.12e-08 (+/- 7.70e-11) [F] ``` -------------------------------- ### Performing Lin-KK fit Source: https://impedancepy.readthedocs.io/en/latest/_sources/examples/validation_example.ipynb.txt Executing the linKK fit with specified parameters and printing the resulting M and mu values. ```python M, mu, Z_linKK, res_real, res_imag = linKK(f, Z, c=.5, max_M=100, fit_type='complex', add_cap=True) print('\nCompleted Lin-KK Fit\nM = {:d}\nmu = {:.2f}'.format(M, mu)) ``` -------------------------------- ### circuit_fit(frequencies, impedances, circuit, initial_guess, constants={}, bounds=None, weight_by_modulus=False, global_opt=False, **kwargs) Source: https://impedancepy.readthedocs.io/en/latest/fitting.html Main function for fitting an equivalent circuit to data. By default, uses scipy.optimize.curve_fit; optionally uses scipy.optimize.basinhopping for global optimization. ```APIDOC ## circuit_fit(frequencies, impedances, circuit, initial_guess, constants={}, bounds=None, weight_by_modulus=False, global_opt=False, **kwargs) ### Description Main function for fitting an equivalent circuit to data. By default, this function uses scipy.optimize.curve_fit to fit the equivalent circuit. This function generally works well for simple circuits. However, the final results may be sensitive to the initial conditions for more complex circuits. In these cases, the scipy.optimize.basinhopping global optimization algorithm can be used to attempt a better fit. ### Parameters - **frequencies** (numpy array) - Required - Frequencies. - **impedances** (numpy array of dtype 'complex128') - Required - Impedances. - **circuit** (string) - Required - String defining the equivalent circuit to be fit. - **initial_guess** (list of floats) - Required - Initial guesses for the fit parameters. - **constants** (dictionary, optional) - Optional - Parameters and their values to hold constant during fitting (e.g. {"RO": 0.1}). Defaults to {}. - **bounds** (2-tuple of array_like, optional) - Optional - Lower and upper bounds on parameters. Defaults to bounds on all parameters of 0 and np.inf, except the CPE alpha which has an upper bound of 1. - **weight_by_modulus** (bool, optional) - Optional - Uses the modulus of each data (|Z|) as the weighting factor. Standard weighting scheme when experimental variances are unavailable. Only applicable when global_opt = False. - **global_opt** (bool, optional) - Optional - If global optimization should be used (uses the basinhopping algorithm). Defaults to False. - **kwargs** - Optional - Keyword arguments passed to scipy.optimize.curve_fit or scipy.optimize.basinhopping. ### Returns - **p_values** (list of floats) - Best fit parameters for specified equivalent circuit. - **p_errors** (list of floats) - One standard deviation error estimates for fit parameters. ### Notes Need to do a better job of handling errors in fitting. Currently, an error of -1 is returned. ``` -------------------------------- ### Fit the circuit to impedance data Source: https://impedancepy.readthedocs.io/en/latest/_sources/getting-started.rst.txt Calls the .fit() method with experimental frequencies and impedance Z to fit the model. The fitted parameters are available via .parameters_ or by printing the circuit object. ```python circuit.fit(frequencies, Z) ``` -------------------------------- ### Fit circuit models and print custom constant circuit Source: https://impedancepy.readthedocs.io/en/latest/_sources/examples/fitting_example.ipynb.txt Fits four circuit models to the same impedance data and prints the custom constant circuit. The output shows the circuit string, fit status, constants, initial guesses, and fitted parameters with uncertainties. ```python randles.fit(frequencies, Z) randlesCPE.fit(frequencies, Z) customCircuit.fit(frequencies, Z) customConstantCircuit.fit(frequencies, Z) print(customConstantCircuit) ``` -------------------------------- ### readPowerSuite(filename) Source: https://impedancepy.readthedocs.io/en/latest/preprocessing.html Reads the .txt file from PowerSuite. ```APIDOC ## readPowerSuite(filename) ### Description Function for reading the .txt file from PowerSuite. ### Method Function call ### Endpoint impedance.preprocessing.readPowerSuite ### Parameters - **filename** (string) - Required - Filename of .txt file to extract impedance data from ### Returns - **frequencies** (np.ndarray) - Array of frequencies - **impedance** (np.ndarray of complex numbers) - Array of complex impedances ``` -------------------------------- ### Initialize circuit_elements dictionary Source: https://impedancepy.readthedocs.io/en/latest/_modules/impedance/models/circuits/elements.html Initializes the circuit_elements dictionary with the series and parallel operators, which are not decorated with metadata. ```python circuit_elements = {"s": s, "p": p} ``` -------------------------------- ### eval_linKK: build and evaluate RC circuit Source: https://impedancepy.readthedocs.io/en/latest/_modules/impedance/validation.html Builds a circuit string from elements, time constants, and frequencies, then evaluates it using circuit_elements. Used for LinKK fitting. ```python def eval_linKK(elements, ts, f): """Builds a circuit of RC elements to be used in LinKK""" elements_list = elements.tolist() ts_list = ts.tolist() f_list = f.tolist() circuit_string = f"s([R({[elements_list[0]]},{f_list})," for Rk, tk in zip(elements_list[1:], ts_list): circuit_string += f"K({[Rk, tk]},{f_list})," circuit_string += f"L({[elements_list[-1]]},{f_list})," if len(elements_list) == (len(ts_list) + 3): circuit_string += f"C({[1 / elements_list[-2]]},{f_list})," circuit_string = circuit_string.strip(',') circuit_string += '])' return eval(circuit_string, circuit_elements) ``` -------------------------------- ### Import EIS data from CSV with impedance.preprocessing Source: https://impedancepy.readthedocs.io/en/latest/_sources/getting-started.rst.txt Loads EIS data from a CSV file with columns frequency, Z_real, and Z_imag, then retains only the impedance data in the first quadrant using preprocessing.readCSV and ignoreBelowX. ```python from impedance import preprocessing # Load data from the example EIS data frequencies, Z = preprocessing.readCSV('./exampleData.csv') # keep only the impedance data in the first quandrant frequencies, Z = preprocessing.ignoreBelowX(frequencies, Z) ``` -------------------------------- ### buildCircuit(circuit, frequencies, *parameters, constants=None, eval_string='', index=0) Source: https://impedancepy.readthedocs.io/en/latest/fitting.html Recursive function that transforms a circuit, parameters, and frequencies into a string that can be evaluated. ```APIDOC ## buildCircuit(circuit, frequencies, *parameters, constants=None, eval_string='', index=0) ### Description Recursive function that transforms a circuit, parameters, and frequencies into a string that can be evaluated. ### Parameters - **circuit** (str) - Required - Circuit string. - **frequencies** (list/tuple/array of floats) - Required - Frequencies. - **parameters** (list/tuple/array of floats) - Required - Parameters. - **constants** (dict) - Optional - Constants. ### Returns - **eval_string** (str) - Python expression for calculating the resulting fit. - **index** (int) - Tracks parameter index through recursive calling of the function. ``` -------------------------------- ### readFile(filename, instrument=None) Source: https://impedancepy.readthedocs.io/en/latest/preprocessing.html A wrapper for reading in many common types of impedance files. ```APIDOC ## readFile(filename, instrument=None) ### Description A wrapper for reading in many common types of impedance files. ### Method Function call ### Endpoint impedance.preprocessing.readFile ### Parameters - **filename** (string) - Required - Filename to extract impedance data from - **instrument** (string) - Optional - Type of instrument file ### Returns - **frequencies** (np.ndarray) - Array of frequencies - **impedance** (np.ndarray of complex numbers) - Array of complex impedances ``` -------------------------------- ### Fit Impedance Model Source: https://impedancepy.readthedocs.io/en/latest/_sources/examples/validation_example.ipynb.txt Fits the impedance model to the provided frequency (f) and impedance (Z) data and prints the model summary. ```python meas_model.fit(f, Z) print(meas_model) ``` -------------------------------- ### Display fit results Source: https://impedancepy.readthedocs.io/en/latest/_sources/examples/looping_files_example.ipynb.txt Prints the fit parameters for the processed circuit objects. ```python for circuit in circuits: print(circuit) ``` -------------------------------- ### Import linKK and plotting libraries Source: https://impedancepy.readthedocs.io/en/latest/examples/validation_example.html Imports the required libraries and the linKK function from the impedance.validation module. The sys.path.append line adds the parent directory to the module search path so the impedance package can be imported. ```python import matplotlib.pyplot as plt import numpy as np import sys sys.path.append('../../../') from impedance.validation import linKK ``` -------------------------------- ### Fit custom circuit and plot Nyquist Source: https://impedancepy.readthedocs.io/en/latest/_sources/examples/fitting_example.ipynb.txt Creates a custom circuit with initial guesses and predicts over a frequency range, then plots the Nyquist result. Requires prior imports of numpy, matplotlib, and the plotting function. ```python customElementCircuit = CustomCircuit(initial_guess=[0.1, 0.1], circuit='R_0-H_1') f_pred = np.linspace(0, 1) customElementCircuit_fit = customElementCircuit.predict(f_pred) fig, ax = plt.subplots(figsize=(5,5)) plot_nyquist(customElementCircuit_fit, fmt='-.', ax=ax) plt.show() ``` -------------------------------- ### Import required modules for file looping Source: https://impedancepy.readthedocs.io/en/latest/examples/looping_files_example.html Imports the glob, numpy, and os modules needed for file discovery and array handling in the subsequent steps. ```python import glob import numpy as np import os ``` -------------------------------- ### Using custom elements in CustomCircuit Source: https://impedancepy.readthedocs.io/en/latest/examples/fitting_example.html Integrate custom elements into a CustomCircuit model and visualize the prediction. ```python customElementCircuit = CustomCircuit(initial_guess=[0.1, 0.1], circuit='R_0-H_1') f_pred = np.linspace(0, 1) customElementCircuit_fit = customElementCircuit.predict(f_pred) fig, ax = plt.subplots(figsize=(5,5)) plot_nyquist(customElementCircuit_fit, fmt='-.', ax=ax) plt.show() ``` ```text /Users/matt/Dropbox/matt/projects/impedance.py/ECSHackWeek/impedance.py/impedance/models/circuits/circuits.py:166: UserWarning: Simulating circuit based on initial parameters warnings.warn("Simulating circuit based on initial parameters") ``` -------------------------------- ### Build circuit evaluation string Source: https://impedancepy.readthedocs.io/en/latest/_modules/impedance/models/circuits/fitting.html Builds an evaluation string for a circuit by iterating over parsed elements, handling nested circuits and assigning parameters. Used in the circuit fitting process. ```python parallel = parse_circuit(circuit, parallel=True) series = parse_circuit(circuit, series=True) if series is not None and len(series) > 1: eval_string += "s([" split = series elif parallel is not None and len(parallel) > 1: eval_string += "p([" split = parallel elif series == parallel: # only single element split = series for i, elem in enumerate(split): if ',' in elem or '-' in elem: eval_string, index = buildCircuit(elem, frequencies, *parameters, constants=constants, eval_string=eval_string, index=index) else: param_string = "" raw_elem = get_element_from_name(elem) elem_number = check_and_eval(raw_elem).num_params param_list = [] for j in range(elem_number): if elem_number > 1: current_elem = elem + '_{}'.format(j) else: current_elem = elem if current_elem in constants.keys(): param_list.append(constants[current_elem]) else: param_list.append(parameters[index]) index += 1 param_string += str(param_list) new = raw_elem + '(' + param_string + ',' + str(frequencies) + ')' eval_string += new if i == len(split) - 1: if len(split) > 1: # do not add closing brackets if single element eval_string += '])' else: eval_string += ',' return eval_string, index ``` -------------------------------- ### Predict and Visualize Circuit Models Source: https://impedancepy.readthedocs.io/en/latest/examples/fitting_example.html Generate predictions using the .predict() method and visualize the results on a Nyquist plot using plot_nyquist. ```python import matplotlib.pyplot as plt import numpy as np from impedance.visualization import plot_nyquist f_pred = np.logspace(5,-2) randles_fit = randles.predict(f_pred) randlesCPE_fit = randlesCPE.predict(f_pred) customCircuit_fit = customCircuit.predict(f_pred) customConstantCircuit_fit = customConstantCircuit.predict(f_pred) fig, ax = plt.subplots(figsize=(5,5)) plot_nyquist(Z, ax=ax) plot_nyquist(randles_fit, fmt='-', ax=ax) plot_nyquist(randlesCPE_fit, fmt='-', ax=ax) plot_nyquist(customCircuit_fit, fmt='-', ax=ax) plot_nyquist(customConstantCircuit_fit, fmt='-', ax=ax) ax.legend(['Data', 'Randles', 'Randles w/ CPE', 'Custom Circuit', 'Custom Circuit w/ Constant R0 and W1_1']) plt.show() ``` -------------------------------- ### get_tc_distribution function for time constants Source: https://impedancepy.readthedocs.io/en/latest/_modules/impedance/validation.html Computes the logarithmically spaced time constants for the Lin-KK method based on the frequency range and the number of RC elements M. The first and last time constants are set to the extremes derived from the minimum and maximum frequencies. ```python 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 ``` -------------------------------- ### Export model to JSON Source: https://impedancepy.readthedocs.io/en/latest/examples/model_io_example.html Creates a CustomCircuit instance and exports it to a human-readable JSON file using the save method. ```python test_circuit = circuits.CustomCircuit(initial_guess=[.01, .005, .1, .005, .1, .001, 200], circuit='R0-p(R1,C1)-p(R2,C2)-Wo1') print(test_circuit) test_circuit.save('template_model.json') ``` -------------------------------- ### CustomCircuit class with circuit string parsing Source: https://impedancepy.readthedocs.io/en/latest/_modules/impedance/models/circuits/circuits.html Defines a customizable equivalent circuit model. The circuit string uses '-' for series and parentheses for parallel elements, with optional integer suffixes for tracking. Validates that initial guesses plus constants match the circuit length. ```python class CustomCircuit(BaseCircuit): def __init__(self, circuit='', **kwargs): """ Constructor for a customizable equivalent circuit model Parameters ---------- initial_guess: numpy array Initial guess of the circuit values circuit: string A string that should be interpreted as an equivalent circuit Notes ----- A custom circuit is defined as a string comprised of elements in series (separated by a `-`) and elements in parallel (grouped as (x,y)). Each element can be appended with an integer (e.g. R0) or an underscore and an integer (e.g. CPE_1) to make keeping track of multiple elements of the same type easier. Example: Randles circuit is given by 'R0-p(R1-Wo1,C1)' """ super().__init__(**kwargs) self.circuit = circuit.replace(" ", "") circuit_len = calculateCircuitLength(self.circuit) if len(self.initial_guess) + len(self.constants) != circuit_len: raise ValueError('The number of initial guesses ' + f'({len(self.initial_guess)}) + ' + 'the number of constants ' + f'({len(self.constants)})' + ' must be equal to ' + f'the circuit length ({circuit_len})') ``` -------------------------------- ### readVersaStudio Function Source: https://impedancepy.readthedocs.io/en/latest/_modules/impedance/preprocessing.html Reads impedance data from a VersaStudio .PAR file. ```python def readVersaStudio(filename): """ function for reading the .PAR file from VersaStudio Parameters ---------- filename: string Filename of .PAR file to extract impedance data from Returns ------- frequencies : np.ndarray Array of frequencies impedance : np.ndarray of complex numbers Array of complex impedances """ from re import split with open(filename, 'r', encoding="utf8") as input_file: lines = input_file.readlines() # List to track [segment index, segment start line, segment end line] segments = list([]) for i, line in enumerate(lines): if "Segments" in line: if not segments: segments = [[int(j) for j in split(r'[=\n]', line) if j.isdigit()]] elif [int(j) for j in split(r'[=\n]', line) if j.isdigit()]: segments.append([int(j) for j in split(r'[=\n]', line) if j.isdigit()]) if segments: for j in segments: if '' in line: segments[j[0]].append(i) if '' in line: segments[j[0]].append(i) # Started building for option of multiple segments, # but that may be an unlikely scenario # For the time being, assume only 1 segment of actual data (Segment1) # Removing segments without apparent data # for i in segments: # if np.size(i)==1: # segments.remove(i) # for i in segments: # data_dum=lines[i[1]+4:i[2]] # f, Z= [], [] # for line in data_dum: # each=line.split(',') # f.append(float(each[9])) # Z.append(complex(float(each[14]),float(each[15]))) raw_data = lines[segments[1][1]+4:segments[1][2]] f, Z = [], [] for line in raw_data: each = line.split(',') f.append(float(each[9])) Z.append(complex(float(each[14]), float(each[15]))) return np.array(f), np.array(Z) ``` -------------------------------- ### readCHInstruments(filename) Source: https://impedancepy.readthedocs.io/en/latest/preprocessing.html Reads the .txt file from CHInstruments. ```APIDOC ## readCHInstruments(filename) ### Description Function for reading the .txt file from CHInstruments. ### Method Function call ### Endpoint impedance.preprocessing.readCHInstruments ### Parameters - **filename** (string) - Required - Filename of .txt file to extract impedance data from ### Returns - **frequencies** (np.ndarray) - Array of frequencies - **impedance** (np.ndarray of complex numbers) - Array of complex impedances ``` -------------------------------- ### readFile wrapper function Source: https://impedancepy.readthedocs.io/en/latest/_modules/impedance/preprocessing.html A wrapper function to read impedance data from supported instrument files or CSV files. ```python def readFile(filename, instrument=None): """ A wrapper for reading in many common types of impedance files Parameters ---------- filename: string Filename to extract impedance data from instrument: string Type of instrument file Returns ------- frequencies : np.ndarray Array of frequencies impedance : np.ndarray of complex numbers Array of complex impedances """ supported_types = ['gamry', 'autolab', 'parstat', 'zplot', 'versastudio', 'powersuite', 'biologic', 'chinstruments'] if instrument is not None: assert instrument in supported_types, \ '{} is not a supported instrument ({})'.format(instrument, supported_types) if instrument == 'gamry': f, Z = readGamry(filename) elif instrument == 'autolab': f, Z = readAutolab(filename) elif instrument == 'biologic': f, Z = readBioLogic(filename) elif instrument == 'parstat': f, Z = readParstat(filename) elif instrument == 'zplot': f, Z = readZPlot(filename) elif instrument == 'versastudio': f, Z = readVersaStudio(filename) elif instrument == 'powersuite': f, Z = readPowerSuite(filename) elif instrument == 'chinstruments': f, Z = readCHInstruments(filename) elif instrument is None: f, Z = readCSV(filename) return f, Z ``` -------------------------------- ### Printing circuit properties Source: https://impedancepy.readthedocs.io/en/latest/examples/fitting_example.html Print the circuit object to view its defined properties, constants, and initial guesses. ```python print(customConstantCircuit) ``` ```text Circuit string: R_0-p(R_1,C_1)-p(R_2,C_2)-Wo_1 Fit: False Constants: R_0 = 2.00e-02 [Ohm] Wo_1_1 = 2.00e+02 [sec] Initial guesses: R_1 = 5.00e-03 [Ohm] C_1 = 1.00e-01 [F] R_2 = 5.00e-03 [Ohm] C_2 = 1.00e-01 [F] Wo_1_0 = 1.00e-03 [Ohm] ``` -------------------------------- ### BaseCircuit.save Source: https://impedancepy.readthedocs.io/en/latest/circuits.html Exports a model to JSON. ```APIDOC ## BaseCircuit.save ### Description Exports a model to JSON. ### Method save ### Signature save(filepath) ### Parameters - **filepath** (str) - Required - Destination for exporting model object ``` -------------------------------- ### Import NumPy Source: https://impedancepy.readthedocs.io/en/latest/_modules/impedance/models/circuits/elements.html Imports NumPy for array operations used throughout the circuit element definitions. ```python import numpy as np ```