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.

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 | Callable) – Value of boundary contion mode, in the appropriate units.

  • tspan (tuple | 1D np.array) – Relative times for recording solution [s]. Providing a tuple as (t_max: float, Nt: int) or (t_max: float, dt: float) constructs tspan using np.linspace or np.arange, respectively. Given an array uses the values supplied as the evaluation times. Arrays must be monotonically increasing and start with zero. See the 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.

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

  • TypeError – ‘tspan[1]’ must be type int or float.

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

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

  • ValueError – ‘tspan’ array length must be at least two.

  • 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.

Solution times are constructed and saved depending on the ‘tspan’ input types that were supplied:

  • Given (float, int):

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

  • Given (float, float):

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

    In this case, ‘t_max’ is also appended to the end. This results in the final ‘dt’ being different from the others if ‘t_max’ is not evenly divisible by the given ‘dt’.

  • Given 1D np.array:

    When you provide a numpy array it is checked for compatibility. If the array is not 1D, is not monotonically increasing, or starts with a value other than zero then an error 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:

**kwargs (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.

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 by a previous solution, but you cannot individually overwrite and set any internal state variables. If you are interested in having more control, see the Prediction class instead, which is intended more for step-by-step predictions used in prediction-correction algorithms like Kalman filters.

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(initial_state=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:

initial_state (bool | Solution) – 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. However, there is limited control over how users can set the state. It can either be set to a rested condition based on ‘soc0’, or it can be initialized based on a Solution 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 given a solution from a 2RC-pair circuit.

run(exp, 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(exp, 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.

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.

This class allows the user to manage the state when working with the Prediction class. The user only has control over independent state variables (i.e., soc, T_cell, hyst, eta_j).

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 (np.ndarray | None) – RC pair overpotentials [V].

See also

Prediction

The model wrapper that interfaces with TransientState.

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].