thevenin#

Summary

The Thevenin equivalent circuit model is a common low-fidelity battery model consisting of a single resistor in series with any number of RC pairs, i.e., parallel resistor-capacitor pairs. This Python package contains an API for building and running experiments using Thevenin models. When referring to the model itself, we use capitalized “Thevenin”, and for the package lowercase thevenin.

Accessing the Documentation

Documentation is accessible via Python’s help() function which prints docstrings from a package, module, function, class, etc. You can also access the documentation by visiting the website, hosted on Read the Docs. The website includes search functionality and more detailed examples.

Submodules#

Classes#

CycleSolution

All-step solution.

Experiment

Experiment builder.

Prediction

Prediction model wrapper.

Simulation

Simulation model wrapper.

StepSolution

Single-step solution.

TransientState

Transient state for predictions.

Functions#

download_templates([path])

Copy example templates to into a local directory.

list_templates()

List names of available example templates.

load_templates(*names)

Load example templates by name.

print_templates(name)

Print example templates.

Package Contents#

class thevenin.CycleSolution(*soln, t_shift=0.001)[source]#

All-step solution.

A solution instance with all experiment steps stitch together into a single cycle.

Parameters:
  • *soln (StepSolution) – All unpacked StepSolution instances to stitch together. The given steps should be given in the same sequential order that they were run.

  • t_shift (float) – Time (in seconds) to shift step solutions by when stitching them together. If zero the end time of each step overlaps the starting time of its following step. The default is 1e-3.

append_soln(soln, t_shift=0.001)[source]#

Append another solution object to the current instance. Appending the instance itself is also allowed, which is helpful to visualize multiple cycles.

Parameters:
  • soln (StepSolution | CycleSolution) – A solution (step or cycle) to append to the end of the current instance.

  • t_shift (float, optional) – Time (in seconds) to add between the current solution and new appended solution. If zero, the current final time and new initial time will exactly overlap. The default is 1e-3.

Returns:

None.

Raises:
  • TypeError – ‘soln’ input must be StepSolution, CycleSolution.

  • ValueError – ‘soln’ input is incompatible because it came from a simulation with different num_RC_pairs.

Notes

The t_shift input only affects the shift between the current and new solutions. For example if two CycleSolution had original shifts of 1 and 5 for their respective steps when stitched, this appended case will still have 1 for the first few steps, t_shift between, and 5 for the shifts between the latter half’s steps.

Appending solutions cannot simply be undone. If you think you may want to go back to an instance prior to appending another solution then you should create a copy to operate on. Use copy.deepcopy from Python’s standard library to make sure the copy is memory safe, i.e., the two instances do not share any common memory.

get_steps(idx)[source]#

Return a subset of the solution.

Parameters:

idx (int | tuple) – The step index (int) or range (first, last) to return.

Returns:

StepSolution | CycleSolution – The returned solution subset. A StepSolution is returned if ‘idx’ is an int, and a CycleSolution will be returned for the range of requested steps when ‘idx’ is a tuple.

plot(x, y, **kwargs)#

Plot any two variables in ‘vars’ against each other.

Parameters:
  • x (str) – A variable key in ‘vars’ to be used for the x-axis.

  • y (str) – A variable key in ‘vars’ to be used for the y-axis.

  • **kwargs (dict, optional) – Keyword arguments to pass through to plt.plot(). For more info please refer to documentation for maplotlib.pyplot.plot().

Returns:

None.

property solvetime: str#

Print a statement specifying how long IDASolver spent integrating.

Returns:

solvetime (str) – An f-string with the total solver integration time in seconds.

class thevenin.Experiment(**kwargs)[source]#

Experiment builder.

A class to define an experimental protocol. Use the add_step() method sequential steps to run. Each step defines a control mode, a constant or time-dependent load profile, a time span, and optional limiting criteria to stop the step early if a specified event/state is detected. Note that Experiment is designed to only interface with the thevenin.Simulation model wrapper.

Parameters:

**kwargs (dict, optional) – IDASolver keyword arguments that span all steps.

See also

IDASolver

The solver class, with documentation for most keyword arguments that you might want to adjust.

Simulation

The correct model interface to use with the Experiment class.

add_step(mode, value, tspan, limits=None, **kwargs)[source]#

Add a step to the experiment.

Parameters:
  • mode (str) – Control mode, {‘current_A’, ‘current_C’, ‘voltage_V’, ‘power_W’}.

  • value (float or Callable) – Value of boundary contion mode, in the appropriate units.

  • tspan (float or tuple[float, float] or 1D np.array) – Relative times for recording solution [s]. Providing a float will result in the solver picking time steps to save on its own. A tuple is interpreted as (tmax, dt) where the first element is the max time for the step (in seconds) and the second is the time interval between steps (also seconds). You can also provide any custom array of times at which to save the solution by providing a 1D np.array; however, the first element must be zero and the array must be in a monotonically increasing order, and there must be at least three elements. An array like np.array([0, tmax]) will will result in the solver choosing its own time steps, similar to just providing a float. See notes for more information.

  • limits (tuple[str, float], optional) – Stopping criteria for the new step, must be entered in sequential name/value pairs. Allowable names are {‘soc’, ‘temperature_K’, ‘current_A’, ‘current_C’, ‘voltage_V’, ‘power_W’, ‘capacity_Ah’, ‘time_s’, ‘time_min’, ‘time_h’}. Values for each limit should immediately follow a corresponding name and match its units. Time limits are in reference to total experiment time. The default is None.

  • **kwargs (dict, optional) – IDASolver keyword arguments specific to the new step only.

Returns:

None.

Raises:
  • ValueError – ‘mode’ is invalid.

  • ValueError – A ‘limits’ name is invalid.

  • TypeError – ‘tspan’ must be type float, tuple, or np.array.

  • ValueError – ‘tspan’ tuple must be length 2.

  • TypeError – ‘tspan’ tuple values must be type float.

  • ValueError – ‘tspan[1]’ must be less than ‘tspan[0]’ when given a tuple.

  • ValueError – ‘tspan’ array must be one-dimensional.

  • ValueError – ‘tspan[0]’ must be zero when given an array.

  • ValueError – ‘tspan’ arrays must be monotonically increasing.

See also

IDASolver

The solver class, with documentation for most keyword arguments that you might want to adjust.

Notes

For time-dependent loads, use a Callable for value with a function signature like def load(t: float) -> float, where t is the step’s relative time, in seconds.

When tspan is given as a 2-tuple, like (tmax, dt), the time span is constructed as:

tspan = np.arange(0., tspan[0], tspan[1])

In the case where tmax is not an integer multiple of dt, a final time point is appended to ensure that tspan[-1] == tmax. If this is too restrictive, you can instead provide a custom 1D np.array for the tspan argument. However, the array is checked to make sure the first element is zero and the array is monotonically increasing. If either of these checks fail, a ValueError is raised.

print_steps()[source]#

Prints a formatted/readable list of steps.

Returns:

None.

property num_steps: int#

Return number of steps.

Returns:

num_steps (int) – Number of steps.

property steps: list[dict]#

Return steps list.

Returns:

steps (list[dict]) – List of the step dictionaries.

class thevenin.Prediction(params='params.yaml')[source]#

Prediction model wrapper.

This class is primarily intended for prediction-correction algorithms, e.g., Kalman filters. The interface is set up to let the user manage the internal state via the TransientState class. In addition, the model is only designed to run current-based loads in a step-by-step fashion. If you are looking to simulate more complex protocols and use the resulting timeseries data you should use the Simulation class instead.

Note that this class and the Simulation class share the same params inputs. This is for convenience so that users can easily switch between the two. However, the ‘soc0’ value has no real meaning for the prediction interface. Instead, the user manages the state of charge for each step through the TransientState interface. This means that you can effectively ignore the ‘soc0’ input when using this class.

Models can be constructed using either a dictionary or a ‘.yaml’ file. Note that the number of Rj and Cj attributes must be consistent with the ‘num_RC_pairs’ value. See the notes for more information on the callable parameters.

Parameters:

params (dict | str) –

Mapping of model parameter names to their values. Can be either a dict or absolute/relative file path to a yaml file (str). The keys/value pair descriptions are given below. The default uses an internal yaml file.

Key

Value

type, units

num_RC_pairs

number of RC pairs

int, -

soc0

initial state of charge

float, -

capacity

maximum battery capacity

float, Ah

ce

coulombic efficiency

float, -

gamma

hysteresis approach rate

float, -

mass

total battery mass

float, kg

isothermal

flag for isothermal model

bool, -

Cp

specific heat capacity

float, J/kg/K

T_inf

room/air temperature

float, K

h_therm

convective coefficient

float, W/m2/K

A_therm

heat loss area

float, m2

ocv

open circuit voltage

Callable, V

M_hyst

max hysteresis magnitude

Callable, V

R0

series resistance

Callable, Ohm

Rj

resistance in RCj

Callable, Ohm

Cj

capacity in RCj

Callable, F

Raises:
  • TypeError – ‘params’ must be type dict or str.

  • ValueError – ‘params’ contains invalid and/or excess key/value pairs.

Notes

The ‘ocv’ and ‘M_hyst’ properties need to be callables with signatures like f(soc: float) -> float, where ‘soc’ is the state of charge. All other properties that require callables (e.g., R0, Rj, and Cj) need signatures like f(soc: float, T_cell: float) -> float, with ‘T_cell’ being the cell temperature in K.

Rj and Cj are not real property names. These are used generally in the documentation. If num_RC_pairs=1 then in addition to R0, you should define R1 and C1. If num_RC_pairs=2 then you should also give R2 and C2, etc. For the special case where num_RC_pairs=0, you should not provide any resistance or capacitance values besides the series resistance R0, which is always required.

While most parameters can be dynamically updated, the num_RC_pairs attribute is read-only. Consequently, you cannot add nor remove Rj and Cj attributes. However, modifying the values of Rj and Cj functions is allowed. If you need a circuit with a different number of RC pairs then you will need to create a separate instance.

pre()[source]#

Pre-process and prepare the model to make predictions. Specifically, this method builds pointers so it can manage mapping the state back and forth between the solver-required array format and the user-managed TransientState class.

Returns:

None.

Notes

This method runs during the class initialization. It generally does not have to be run again unless you want to re-run internal checks on the class instance.

set_options(**options)[source]#

Set the solver options for the underlying ODE integrator.

Parameters:

**options (dict, optional) – CVODESolver keyword arguments that span all steps. You can re-run this method between prediction steps if you need different settings per step.

See also

CVODESolver

The solver class, with documentation for most keyword arguments that you might want to adjust.

take_step(state, current, delta_t)[source]#

Take a step forward in time to predict the new state and voltage given a starting state, demand current, and time step.

Parameters:
  • state (TransientState) – Description of the starting state.

  • current (float | Callable) – Demand current [A]. For a dynamic current, use a callable with a signature like def current(t: float) -> float, where the input time is in seconds relative to the overall step.

  • delta_t (float) – Magnitude of time step, in seconds.

Returns:

TransientState – Predicted state at the end of the time step.

to_simulation(state0=True)[source]#

Generate a Simulation class instance with the same properties as the current Prediction.

Parameters:

state0 (bool | Solution | TransientState) – Control how the model state is initialized. If True (default), the state is set to a rested condition at ‘soc0’. If False, the state is left alone and only internal checks are run. Given a Solution instance, the state is set to the final state of the solution. See the notes for more information.

Returns:

Simulation – An instance of the Simulation interface, initialized with the same properties as the current Prediction instance.

Notes

When initializing based on a Solution instance, the solution must be the same size as the current model. In other words, a 1RC-pair model cannot be initialized by a solution from a 2RC-pair circuit. Similarly, the eta_j size from a TransientState instance must match the size of RC pairs in the current model.

property classname: str#

Return the name of the class.

property num_RC_pairs: int#

Return the number of RC pairs.

class thevenin.Simulation(params='params.yaml')[source]#

Simulation model wrapper.

This class is primarily intended for full timeseries simulations. Use the Experiment class to provide the details of an experiment. Note that this version of the model interface manages its own internal state. The user has limited ability to directly control the state. At the beginning of all simulations, the model is assumed in a fully rested state at the user-supplied state-of-charge soc0. Through the pre-processor method pre() you can manually force the state to start at a value given a previous solution, or using a TransientState instance for maximum control.

Models can be constructed using either a dictionary or a ‘.yaml’ file. Note that the number of Rj and Cj attributes must be consistent with the ‘num_RC_pairs’ value. See the notes for more information on the callable parameters.

Parameters:

params (dict | str) –

Mapping of model parameter names to their values. Can be either a dict or absolute/relative file path to a yaml file (str). The keys/value pair descriptions are given below. The default uses an internal yaml file.

Key

Value

type, units

num_RC_pairs

number of RC pairs

int, -

soc0

initial state of charge

float, -

capacity

maximum battery capacity

float, Ah

ce

coulombic efficiency

float, -

gamma

hysteresis approach rate

float, -

mass

total battery mass

float, kg

isothermal

flag for isothermal model

bool, -

Cp

specific heat capacity

float, J/kg/K

T_inf

room/air temperature

float, K

h_therm

convective coefficient

float, W/m2/K

A_therm

heat loss area

float, m2

ocv

open circuit voltage

Callable, V

M_hyst

max hysteresis magnitude

Callable, V

R0

series resistance

Callable, Ohm

Rj

resistance in RCj

Callable, Ohm

Cj

capacity in RCj

Callable, F

Raises:
  • TypeError – ‘params’ must be type dict or str.

  • ValueError – ‘params’ contains invalid and/or excess key/value pairs.

Notes

The ‘ocv’ and ‘M_hyst’ properties need to be callables with signatures like f(soc: float) -> float, where ‘soc’ is the state of charge. All other properties that require callables (e.g., R0, Rj, and Cj) need signatures like f(soc: float, T_cell: float) -> float, with ‘T_cell’ being the cell temperature in K.

Rj and Cj are not real property names. These are used generally in the documentation. If num_RC_pairs=1 then in addition to R0, you should define R1 and C1. If num_RC_pairs=2 then you should also give R2 and C2, etc. For the special case where num_RC_pairs=0, you should not provide any resistance or capacitance values besides the series resistance R0, which is always required.

While most parameters can be dynamically updated, the num_RC_pairs attribute is read-only. Consequently, you cannot add nor remove Rj and Cj attributes. However, modifying the values of Rj and Cj functions is allowed. If you need a circuit with a different number of RC pairs then you will need to create a separate instance.

pre(state0=True)[source]#

Pre-process and prepare the model for running experiments.

This method builds solution pointers, registers algebraic variable indices, stores the mass matrix, and initializes the hidden state.

Parameters:

state0 (bool | Solution | TransientState) – Control how the model state is initialized. If True (default), the state is set to a rested condition at ‘soc0’. If False, the state is left alone and only internal checks are run. Given a Solution instance, the state is set to the final state of the solution. See the notes for more information.

Returns:

None.

Notes

This method runs during the class initialization. It generally does not have to be run again unless you want to reset the internal hidden state. Using the state0 argument, you can set the state back to a rested condition at ‘soc0’, to the final value from a Solution, or to a user-defined state given by a TransientState instance.

When initializing based on a Solution instance, the solution must be the same size as the current model. In other words, a 1RC-pair model cannot be initialized by a solution from a 2RC-pair circuit. Similarly, the eta_j size from a TransientState instance must match the size of RC pairs in the current model.

run(expr, reset_state=True, t_shift=0.001)[source]#

Run a full experiment.

Parameters:
  • expr (Experiment) – An experiment instance.

  • reset_state (bool) – If True (default), the internal state of the model will be reset back to a rested condition at ‘soc0’ at the end of all steps. When False, the state does not reset. Instead it will update to match the final state of the last experimental step.

  • t_shift (float) – Time (in seconds) to shift step solutions by when stitching them together. If zero the end time of each step overlaps the starting time of its following step. The default is 1e-3.

Returns:

CycleSolution – A stitched solution with all experimental steps.

Warning

The default behavior resets the model’s internal state back to a rested condition at ‘soc0’ by calling the pre() method at the end of all steps. This means that if you run a second experiment afterward, it will not start where the previous one left off. Instead, it will start from the original rested condition that the model initialized with. You can bypass this by using reset_state=False, which keeps the state at the end of the final experimental step.

See also

Experiment

Build an experiment.

CycleSolution

Wrapper for an all-steps solution.

run_step(expr, stepidx)[source]#

Run a single experimental step.

Parameters:
  • expr (Experiment) – An experiment instance.

  • stepidx (int) – Step index to run. The first step has index 0.

Returns:

StepSolution – Solution to the experimental step.

Warning

The model’s internal state is changed at the end of each experimental step. Consequently, you should not run steps out of order. You should always start with stepidx = 0 and then progress to the subsequent steps afterward. If at any time you want to reset the internal hidden state back to a rested condition, use pre().

See also

Experiment

Build an experiment.

StepSolution

Wrapper for a single-step solution.

Notes

Using run() loops through all steps in an experiment and stitches their solutions together. Most of the time, this is more convenient. However, running step-by-step provides maximum control to fine tune solver options. It also allows for complex analyses and/or control decisions to be performed between experimental steps.

to_prediction()[source]#

Generate a Prediction class instance with the same properties as the current Simulation.

Returns:

Prediction – An instance of the Prediction interface, initialized with the same properties as the current Simulation instance.

property classname: str#

Return the name of the class.

property num_RC_pairs: int#

Return the number of RC pairs.

class thevenin.StepSolution(sim, ida_soln, timer)[source]#

Single-step solution.

A solution instance for a single experimental step.

Parameters:
  • sim (Simulation) – The simulation instance that was run to produce the solution.

  • ida_soln (IDAResult) – The unformatted solution returned by IDASolver.

  • timer (float) – Amount of time it took for IDASolver to perform the integration.

plot(x, y, **kwargs)#

Plot any two variables in ‘vars’ against each other.

Parameters:
  • x (str) – A variable key in ‘vars’ to be used for the x-axis.

  • y (str) – A variable key in ‘vars’ to be used for the y-axis.

  • **kwargs (dict, optional) – Keyword arguments to pass through to plt.plot(). For more info please refer to documentation for maplotlib.pyplot.plot().

Returns:

None.

property solvetime: str#

Print a statement specifying how long IDASolver spent integrating.

Returns:

solvetime (str) – An f-string with the solver integration time in seconds.

class thevenin.TransientState(soc, T_cell, hyst, eta_j)[source]#

Transient state for predictions.

A container that allows users to manage the internal hidden states of model classes. Users only have control over independent state variables (i.e., soc, T_cell, hyst, eta_j). The Prediction interface requires an instance of the TransientState each time a prediction step is made. You can optionally use instances of this class to define the starting state of Simulation instances using their pre() method. In general, however, the model interface for simulations is more limited when it comes to user-defined states.

The read-only voltage property will return None if the state was user defined. If instead the state was returned by the Prediction class, the value will be the predicted voltage after a given step.

Parameters:
  • soc (float) – State of charge [-].

  • T_cell (float) – Temperature of the cell [K].

  • hyst (float) – Hysteresis voltage [V].

  • eta_j (1D np.array | None) – RC pair overpotentials [V].

See also

Simulation

The model wrapper used for timeseries simulations.

Prediction

The model wrapper used for step-by-step predictions.

property num_RC_pairs: int#

Number of RC pairs in the circuit.

property voltage: float | None#

None if user-defined state. Otherwise, predicted voltage [V].

thevenin.download_templates(path=None)[source]#

Copy example templates to into a local directory.

Parameters:

path (str or PathLike or None, optional) – Path to parent directory where a new thevenin_templates folder will be created and example templates will be copied to. If None (default), the current working directory is used.

thevenin.list_templates()[source]#

List names of available example templates.

Returns:

names (list[str]) – A list of example file names from an internal resources folder.

thevenin.load_templates(*names)[source]#

Load example templates by name.

Parameters:

*names (str) – One or more names of example template files to load. See options with list_templates().

Returns:

templates (dict or tuple[dict]) – A single template dictionary if one name is provided, or a tuple of template dictionaries in the same order as the given names.

Raises:

FileNotFoundError – Requested template name is not available.

thevenin.print_templates(name)[source]#

Print example templates.

Parameters:

name (str) – Name of a template file to print. See options with list_templates().

Raises:

FileNotFoundError – Requested template name is not available.