Skip to content

Circuit fitting

pypalmsens.fitting

Define classes for circuit fitting.

Classes:

  • CircuitModel

    Fit an equivalent circuit model.

  • FitResult

    Container for fitting results.

  • Parameter

    Set or update Parameter attributes.

  • Parameters

    Tuple-like container class for parameters.

CircuitModel dataclass

CircuitModel(cdc: str, algorithm: Literal['leastsq', 'nelder-mead'] = 'leastsq', max_iterations: int = 500, min_delta_error: float = 1e-09, min_delta_step: float = 1e-12, min_freq: None | float = None, max_freq: None | float = None, tolerance: float = 0.0001, lambda_start: float = 0.01, lambda_factor: float = 10.0, _last_result: None | FitResult = None, _last_psfitter: None | FitAlgorithm = None)

Fit an equivalent circuit model.

The class takes a CDC string as a required argument to set up the model.

The other parameters are optional and can be used to tweak the minimization. The model supports fitting over a specified frequency range and adjustment of exit conditions (i.e. max # iterations, min delta error, min parameter step size).

Optionally you can change the initial values of the parameters, their min/max bounds or fix their value.

Example:

model = CircuitModel('R(RC)')
result = model.fit(eis_data)

Methods:

  • default_parameters

    Get default parameters. Use this to modify parameter values.

  • fit

    Fit circuit model.

Attributes:

algorithm class-attribute instance-attribute

algorithm: Literal['leastsq', 'nelder-mead'] = 'leastsq'

Name of the fitting method to use.

Valid values are: leastsq (Levenberg-Marquardt), nelder-mead

cdc instance-attribute

cdc: str

Sets the circuit specified in the CDC string.

For more information, see: https://www.utwente.nl/en/tnw/ims/publications/downloads/cdc-explained.pdf

lambda_factor class-attribute instance-attribute

lambda_factor: float = 10.0

Lambda Scaling Factor. Levenberg-Marquardt only (default = 10).

lambda_start class-attribute instance-attribute

lambda_start: float = 0.01

Start lambda value. Levenberg-Marquardt only (default = 0.01).

last_psfitter property

last_psfitter

Store reference to last SDK fitting object.

last_result property

last_result

Store last fit result.

max_freq class-attribute instance-attribute

max_freq: None | float = None

Maximum fitting frequency in Hz.

max_iterations class-attribute instance-attribute

max_iterations: int = 500

Maximum number of iterations.

Minimization terminates once it reaches this number of steps (default = 500).

min_delta_error class-attribute instance-attribute

min_delta_error: float = 1e-09

Minimum convergence error.

Minimization converges if the residual (squared difference) falls below this value (default = 1e-9).

min_delta_step class-attribute instance-attribute

min_delta_step: float = 1e-12

Minimum convergence step.

Minimization converges if the difference in parameter values falls below this value (default = 1e-12).

min_freq class-attribute instance-attribute

min_freq: None | float = None

Minimum fitting frequency in Hz.

tolerance class-attribute instance-attribute

tolerance: float = 0.0001

Convergence tolerance. Nelder-Mead only (default = 1e-4).

default_parameters

default_parameters() -> Parameters

Get default parameters. Use this to modify parameter values.

Returns:

  • parameters ( Parameters ) –

    Default parameters for CDC.

Source code in src/pypalmsens/_fitting.py
377
378
379
380
381
382
383
384
385
def default_parameters(self) -> Parameters:
    """Get default parameters. Use this to modify parameter values.

    Returns
    -------
    parameters : Parameters
        Default parameters for CDC.
    """
    return Parameters(self.cdc)

fit

fit(data: EISData, *, parameters: None | Sequence[float] | Parameters = None) -> FitResult

Fit circuit model.

Parameters:

  • data
    (EISData) –

    Input data.

  • parameters
    (Optional[Sequence[float] | Parameters], default: None ) –

    Optional initial parameters for fit. Can be passed as Parameters object or list of values.

Returns:

  • result ( FitResult ) –

    Returns dataclass with fit results. Can also be accessed via .last_result.

Source code in src/pypalmsens/_fitting.py
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
def fit(
    self, data: EISData, *, parameters: None | Sequence[float] | Parameters = None
) -> FitResult:
    """Fit circuit model.

    Parameters
    ----------
    data : EISData
        Input data.
    parameters : Optional[Sequence[float] | Parameters]
        Optional initial parameters for fit. Can be passed as
        `Parameters` object or list of values.

    Returns
    -------
    result : FitResult
        Returns dataclass with fit results. Can also be accessed via `.last_result`.
    """
    if not data.frequency_type == 'scan':
        raise ValueError(
            f'Fit only supports EIS scans at a fixed potential, got {data.frequency_type=}.'
        )
    if not data.scan_type == 'fixed':
        raise ValueError(
            f'Fit only supports EIS scans at a fixed potential, got {data.scan_type=}.'
        )

    opts = self._psfitoptions(data=data, parameters=parameters)

    fitter = PalmSens.Fitting.FitAlgorithm.FromAlgorithm(opts)
    fitter.ApplyFitCircuit()
    self._last_psfitter = fitter
    if self.algorithm == 'nelder-mead':
        self._last_result = FitResult.from_psfitresult_nelder_mead(
            fitter.FitResult, cdc=self.cdc
        )
    else:
        self._last_result = FitResult.from_psfitresult(fitter.FitResult, cdc=self.cdc)

    return self._last_result

FitResult dataclass

FitResult(cdc: str, parameters: list[float], error: list[float] | None, chisq: float, n_iter: int, exit_code: str)

Container for fitting results.

Methods:

Attributes:

cdc instance-attribute

cdc: str

Circuit model CDC values.

chisq instance-attribute

chisq: float

Chi-squared goodness of fit statistic.

error instance-attribute

error: list[float] | None

Error (%) on parameters.

exit_code instance-attribute

exit_code: str

Exit code for the minimization.

n_iter instance-attribute

n_iter: int

Total number of iterations.

parameters instance-attribute

parameters: list[float]

Optimized parameters for CDC.

from_eisdata classmethod

from_eisdata(data: EISData)

Construct fitresulf from EISData.

Source code in src/pypalmsens/_fitting.py
168
169
170
171
172
173
174
175
176
177
178
@classmethod
def from_eisdata(cls, data: EISData):
    """Construct fitresulf from EISData."""
    return cls(
        cdc=data.cdc,
        parameters=data.cdc_values,
        chisq=0,
        error=[0.0 for _ in data.cdc_values],
        exit_code='',
        n_iter=0,
    )

from_psfitresult classmethod

from_psfitresult(result: FitResult, cdc: str)

Construct fitresult from SDK FitResult.

Source code in src/pypalmsens/_fitting.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
@classmethod
def from_psfitresult(cls, result: PalmSens.Fitting.FitResult, cdc: str):
    """Construct fitresult from SDK FitResult."""
    if result.ParameterSDs:
        error = list(result.ParameterSDs)
    else:
        error = None

    return cls(
        cdc=cdc,
        chisq=result.ChiSq,
        exit_code=result.ExitCode.ToString(),
        n_iter=result.NIterations - 1,
        parameters=list(result.FinalParameters),
        error=error,
    )

from_psfitresult_nelder_mead classmethod

from_psfitresult_nelder_mead(result: FitResult, cdc: str)

Construct fitresult from SDK FitResult.

Source code in src/pypalmsens/_fitting.py
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
@classmethod
def from_psfitresult_nelder_mead(cls, result: PalmSens.Fitting.FitResult, cdc: str):
    """Construct fitresult from SDK FitResult."""
    exit_codes = {
        0: 'None',
        1: 'RelativeGradient',
        2: 'LackOfProgress',
        3: 'AbsoluteGradient',
        4: 'WeakWolfeCriteria',
        5: 'BoundTolerance',
        6: 'StrongWolfeCriteria',
        7: 'Converged',
    }

    return cls(
        cdc=cdc,
        chisq=result.ChiSq,
        exit_code=exit_codes.get(int(result.ExitCode), 'Unknown'),
        n_iter=result.NIterations - 1,
        parameters=list(result.FinalParameters),
        error=None,
    )

get_bode_phase

get_bode_phase(data: EISData) -> tuple[Curve, Curve]

Calculate observed and calculated Bode curve phase vs Frequency.

Parameters:

  • data
    (EISData) –

    Input EIS data.

Returns:

  • calc, meas : tuple[Curve, Curve]

    Returns the nyquist curve calculated from the model parameters and the measured curve from the EIS data.

Source code in src/pypalmsens/_fitting.py
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
def get_bode_phase(self, data: EISData) -> tuple[Curve, Curve]:
    """Calculate observed and calculated Bode curve phase vs Frequency.

    Parameters
    ----------
    data : EISData
        Input EIS data.

    Returns
    -------
    calc, meas : tuple[Curve, Curve]
        Returns the nyquist curve calculated from the model parameters
        and the measured curve from the EIS data.
    """
    psmodel = self.get_psmodel(data=data)
    curves = psmodel.GetCurvePhaseOverFrequency(False)
    calc, meas = (Curve(pscurve=pscurve) for pscurve in curves)
    return calc, meas

get_bode_z

get_bode_z(data: EISData) -> tuple[Curve, Curve]

Calculate observed and calculated Bode curve Z vs Frequency.

Parameters:

  • data
    (EISData) –

    Input EIS data.

Returns:

  • calc, meas : tuple[Curve, Curve]

    Returns the nyquist curve calculated from the model parameters and the measured curve from the EIS data.

Source code in src/pypalmsens/_fitting.py
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
def get_bode_z(self, data: EISData) -> tuple[Curve, Curve]:
    """Calculate observed and calculated Bode curve Z vs Frequency.

    Parameters
    ----------
    data : EISData
        Input EIS data.

    Returns
    -------
    calc, meas : tuple[Curve, Curve]
        Returns the nyquist curve calculated from the model parameters
        and the measured curve from the EIS data.
    """
    psmodel = self.get_psmodel(data=data)
    curves = psmodel.GetCurveZabsOverFrequency(False)
    calc, meas = (Curve(pscurve=pscurve) for pscurve in curves)
    return calc, meas

get_nyquist

get_nyquist(data: EISData) -> tuple[Curve, Curve]

Calculate observed and calculated nyquist curves.

Parameters:

  • data
    (EISData) –

    Input EIS data.

Returns:

  • calc, meas : tuple[Curve, Curve]

    Returns the nyquist curve calculated from the model parameters and the measured curve from the EIS data.

Source code in src/pypalmsens/_fitting.py
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
def get_nyquist(self, data: EISData) -> tuple[Curve, Curve]:
    """Calculate observed and calculated nyquist curves.

    Parameters
    ----------
    data : EISData
        Input EIS data.

    Returns
    -------
    calc, meas : tuple[Curve, Curve]
        Returns the nyquist curve calculated from the model parameters
        and the measured curve from the EIS data.
    """
    psmodel = self.get_psmodel(data=data)
    curves = psmodel.GetNyquist()
    calc, meas = (Curve(pscurve=pscurve) for pscurve in curves)
    return calc, meas

get_psmodel

get_psmodel(data: EISData) -> CircuitModel

Get SDK Circuit model object.

Source code in src/pypalmsens/_fitting.py
180
181
182
183
184
185
186
def get_psmodel(self, data: EISData) -> PalmSens.Fitting.Models.CircuitModel:
    """Get SDK Circuit model object."""
    psmodel = PalmSens.Fitting.Models.CircuitModel()
    psmodel.SetEISdata(data._pseis)
    psmodel.SetCircuit(self.cdc)
    psmodel.SetInitialParameters(self.parameters)
    return psmodel

plot_bode

plot_bode(data: EISData) -> Figure

Make bode plot using matplotlib.

Parameters:

  • data
    (EISData) –

    Input EIS data.

Returns:

  • fig ( Figure ) –

    Returns matplotlib figure object. use fig.show() to render plot.

Source code in src/pypalmsens/_fitting.py
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
def plot_bode(self, data: EISData) -> figure.Figure:
    """Make bode plot using matplotlib.

    Parameters
    ----------
    data : EISData
        Input EIS data.

    Returns
    -------
    fig : fig.Figure
        Returns matplotlib figure object. use `fig.show()` to render plot.
    """
    import matplotlib.pyplot as plt

    calc_z, meas_z = self.get_bode_z(data=data)
    calc_ph, meas_ph = self.get_bode_phase(data=data)
    fig, ax1 = plt.subplots()
    _ = ax1.set_title('Bode plot')
    _ = ax1.set_xscale('log')

    _ = calc_z.plot(ax=ax1, legend=False, color='C0')
    _ = meas_z.plot(ax=ax1, marker='^', linestyle='None', color='C0', legend=False)

    ax2 = ax1.twinx()
    _ = calc_ph.plot(ax=ax2, legend=False, color='C1')
    _ = meas_ph.plot(ax=ax2, marker='^', linestyle='None', color='C1', legend=False)

    h1, l1 = ax1.get_legend_handles_labels()
    h2, l2 = ax2.get_legend_handles_labels()

    _ = ax2.legend(h1 + h2, l1 + l2, loc='upper right')

    return fig

plot_nyquist

plot_nyquist(data: EISData) -> Figure

Make nyquist plot using matplotlib.

Parameters:

  • data
    (EISData) –

    Input EIS data.

Returns:

  • fig ( Figure ) –

    Returns matplotlib figure object. use fig.show() to render plot.

Source code in src/pypalmsens/_fitting.py
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
def plot_nyquist(self, data: EISData) -> figure.Figure:
    """Make nyquist plot using matplotlib.

    Parameters
    ----------
    data : EISData
        Input EIS data.

    Returns
    -------
    fig : fig.Figure
        Returns matplotlib figure object. use `fig.show()` to render plot.
    """
    import matplotlib.pyplot as plt

    calc, meas = self.get_nyquist(data=data)
    fig, ax = plt.subplots()
    _ = ax.set_title('Nyquist plot')

    _ = calc.plot(ax=ax)
    _ = meas.plot(ax=ax, marker='^', linestyle='None')

    return fig

Parameter dataclass

Parameter(symbol: str, value: None | float = None, min: None | float = None, max: None | float = None, fixed: None | bool = None)

Set or update Parameter attributes.

Attributes:

  • fixed (None | bool) –

    If True, fix the value for this parameter.

  • max (None | float) –

    Maximum (upper bound) for the parameter.

  • min (None | float) –

    Minimum (lower bound) for the parameter.

  • symbol (str) –

    Name of the parameter (not used in minimization).

  • value (None | float) –

    Initial value of the parameter.

fixed class-attribute instance-attribute

fixed: None | bool = None

If True, fix the value for this parameter.

max class-attribute instance-attribute

max: None | float = None

Maximum (upper bound) for the parameter.

min class-attribute instance-attribute

min: None | float = None

Minimum (lower bound) for the parameter.

symbol instance-attribute

symbol: str

Name of the parameter (not used in minimization).

value class-attribute instance-attribute

value: None | float = None

Initial value of the parameter.

Parameters

Parameters(cdc: str)

Tuple-like container class for parameters.

This class is instantiated from the CDC code and contains default parameters. This ensures that the length and type of parameters match that of CircuitModel. Update the parameters in this class and pass to CircuitModel.fit().

Parameters:

  • cdc

    (str) –

    Genererate fitting parameters for this CDC.

Attributes:

  • cdc (str) –

    CDC code used to generate parameter listing.

Source code in src/pypalmsens/_fitting.py
73
74
75
76
77
78
79
80
81
def __init__(self, cdc: str):
    self.cdc: str = cdc
    """CDC code used to generate parameter listing."""

    model = PalmSens.Fitting.Models.CircuitModel()
    model.SetCircuit(cdc)
    self._parameters: list[Parameter] = [
        Parameter._import(psparam) for psparam in model.InitialParameters
    ]

cdc instance-attribute

cdc: str = cdc

CDC code used to generate parameter listing.