A battery pack simulation tool that uses the PyBaMM framework

Related tags

Hardware liionpack
Overview

Python application Documentation Status codecov

Overview of liionpack

liionpack takes a 1D PyBaMM model and makes it into a pack. You can either specify the configuration e.g. 16 cells in parallel and 2 in series (16p2s) or load a netlist

Installation

Follow the steps given below to install the liionpack Python package. The package must be installed to run the included examples. It is recommended to create a virtual environment for the installation.

# Clone the repository
$ git clone https://github.com/pybamm-team/liionpack.git

# Create a virtual environment in the repository directory
$ cd liionpack
$ python -m venv .venv

# Activate the virtual environment and upgrade pip if venv installed an old version
$ source .venv/bin/activate
$ pip install --upgrade pip

# Install the required packages
$ pip install -r requirements.txt

# Install the liionpack package from within the repository
$ pip install -e .

Alternatively, use Conda to create a virtual environment then install the liionpack package.

# Clone the repository
$ git clone https://github.com/pybamm-team/liionpack.git

# Create a Conda virtual environment
$ cd liionpack
$ conda env create -f environment.yml

# Activate the conda environment
$ conda activate lipack

# Install the liionpack package from within the repository
$ pip install -e .

Example Usage

The following code block illustrates how to use liionpack to perform a simulation:

import liionpack as lp
import numpy as np
import pybamm

# Generate the netlist
netlist = lp.setup_circuit(Np=16, Ns=2, Rb=1e-4, Rc=1e-2, Ri=5e-2, V=3.2, I=80.0)

output_variables = [  
    'X-averaged total heating [W.m-3]',
    'Volume-averaged cell temperature [K]',
    'X-averaged negative particle surface concentration [mol.m-3]',
    'X-averaged positive particle surface concentration [mol.m-3]',
    ]

# Heat transfer coefficients
htc = np.ones(32) * 10

# Cycling protocol
protocol = lp.generate_protocol()

# PyBaMM parameters
chemistry = pybamm.parameter_sets.Chen2020
parameter_values = pybamm.ParameterValues(chemistry=chemistry)

# Solve pack
output = lp.solve(netlist=netlist,
                  parameter_values=parameter_values,
                  protocol=protocol,
                  output_variables=output_variables,
                  htc=htc)
Comments
  • Casadi solver does not scale with CPU cores

    Casadi solver does not scale with CPU cores

    I ran a 32 battery cell example (Np = 16, Ns = 2) on a 64 CPU core workstation where I adjusted the nproc parameter from 2 to 64. Based on the results shown below, it appears that the Casadi solver does not scale beyond 16 CPU cores. I get similar elapsed times for anything above 16 cores. I'm not sure how the Casadi map function executes in parallel so I have no idea how to optimize the problem to take advantage of many CPUs. Has anyone used liionpack with more than 16 CPU cores and did you notice any speed improvements above 16 cores?

    Figure_1

    opened by wigging 44
  • Use Dask to run the cell simulations in parallel

    Use Dask to run the cell simulations in parallel

    This isn't really an issue but more of a feature request or enhancement. Would it be possible to use Dask to run the cell simulations in parallel? I created a basic example (see below) of running the SPMe model for several discharges in parallel using Dask. Compare elapsed time with and without Dask by commenting out the appropriate section in main(). Elapsed times are given in the table below when running on an 8-core CPU.

    Dask is made for massive parallelization and it's fairly easy to setup for CPU and GPU clusters. If it can be used with liionpack then it could provide a huge performance boost for large pack simulations. I haven't tried Casadi's parallel features beyond running on a single CPU but I don't think it will be easy to scale compared to using Dask.

    | Example | Elapsed time | | -------- | ------------- | | No Dask | 8.57 seconds | | Dask | 3.83 seconds |

    import matplotlib.pyplot as plt
    import pybamm
    import time
    from dask.distributed import Client
    
    def generate_plots(discharge, t, capacity, current, voltage):
    
        def styleplot(ax):
            ax.legend(loc='best')
            ax.grid(color='0.9')
            ax.set_frame_on(False)
            ax.tick_params(color='0.9')
    
        _, ax = plt.subplots(tight_layout=True)
        for i in range(len(discharge)):
            ax.plot(t[i], current[i], label=f'{discharge[i]} A')
        ax.set_xlabel('Time [s]')
        ax.set_ylabel('Current [A]')
        styleplot(ax)
    
        _, ax = plt.subplots(tight_layout=True)
        for i in range(len(discharge)):
            ax.plot(t[i], voltage[i], label=f'{discharge[i]} A')
        ax.set_xlabel('Time [s]')
        ax.set_ylabel('Terminal voltage [V]')
        styleplot(ax)
    
        _, ax = plt.subplots(tight_layout=True)
        for i in range(len(discharge)):
            ax.plot(capacity[i], voltage[i], label=f'{discharge[i]} A')
        ax.set_xlabel('Discharge capacity [Ah]')
        ax.set_ylabel('Terminal voltage [V]')
        styleplot(ax)
    
        plt.show()
    
    def run_simulation(dis, t_eval):
    
        model = pybamm.lithium_ion.SPMe()
    
        param = model.default_parameter_values
        param['Current function [A]'] = '[input]'
    
        sim = pybamm.Simulation(model, parameter_values=param)
        sim.solve(t_eval, inputs={'Current function [A]': dis})
    
        return sim.solution
    
    def main(client):
        tic = time.perf_counter()
    
        discharge = [4, 3.5, 3, 2.5, 2, 1.8, 1.5, 1]  # discharge currents [A]
        t_eval = [0, 4000]                            # evaluation time [s]
    
        # No Dask
        # ------------------------------------------------------------------------
    
        # label = 'no Dask'
    
        # sols = []
        # for dis in discharge:
        #     sol = run_simulation(dis, t_eval)
        #     sols.append(sol)
    
        # Dask
        # ------------------------------------------------------------------------
    
        label = 'Dask'
    
        lazy_sols = client.map(run_simulation, discharge, t_eval=t_eval)
        sols = client.gather(lazy_sols)
    
        # ------------------------------------------------------------------------
    
        t = []
        capacity = []
        current = []
        voltage = []
    
        for sol in sols:
            t.append(sol['Time [s]'].entries)
            capacity.append(sol['Discharge capacity [A.h]'].entries)
            current.append(sol['Current [A]'].entries)
            voltage.append(sol["Terminal voltage [V]"].entries)
    
        toc = time.perf_counter()
        print(f'Elapsed time ({label}) = {toc - tic:.2f} s')
    
        generate_plots(discharge, t, capacity, current, voltage)
    
    if __name__ == '__main__':
        client = Client()
        print(client)
        main(client)
    
    opened by wigging 35
  • [Bug]: Inconsistent results when using Ray manager

    [Bug]: Inconsistent results when using Ray manager

    liionpack Version

    0.3.2

    Python Version

    3.9.12

    Describe the bug

    I get inconsistent results and sometimes errors when using the Ray manager. I don't have these issues when using the Casadi manager. See the example below for more information.

    Steps to Reproduce

    This example works fine when using the Casadi manager.

    import liionpack as lp
    import pybamm
    import numpy as np
    
    def main():
    
        # Define parameters
        Np = 16
        Ns = 2
        Iapp = 20
        nproc = 8
        manager = 'casadi'
    
        # Generate the netlist
        netlist = lp.setup_circuit(Np=Np, Ns=Ns)
    
        # Define additional output variables
        output_variables = [
            'Volume-averaged cell temperature [K]']
    
        # Define a cycling experiment using PyBaMM
        experiment = pybamm.Experiment([
            f'Charge at {Iapp} A for 30 minutes',
            'Rest for 15 minutes',
            f'Discharge at {Iapp} A for 30 minutes',
            'Rest for 30 minutes'],
            period='10 seconds')
    
        # Define the PyBaMM parameters
        parameter_values = pybamm.ParameterValues("Chen2020")
        inputs = {"Total heat transfer coefficient [W.m-2.K-1]": np.ones(Np * Ns) * 10}
    
        # Solve the pack
        output = lp.solve(netlist=netlist,
                          sim_func=lp.thermal_simulation,
                          parameter_values=parameter_values,
                          experiment=experiment,
                          output_variables=output_variables,
                          initial_soc=0.5,
                          inputs=inputs,
                          nproc=nproc,
                          manager=manager)
    
        # Plot the pack and individual cell results
        lp.plot_pack(output)
        lp.plot_cells(output)
        lp.show_plots()
    
    if __name__ == '__main__':
        main()
    

    Here are the plots generated from the above example.

    casadi

    I get the following plots using the Ray manager for the same example.

    ray

    Sometimes I get the error shown below when using the Ray manager and I eventually have to abort the simulation.

    Stepping simulation:  52%|█████████████████████████████████████████▏                                      | 325/631 [00:10<00:09, 32.88it/s](RayActor pid=94841) psetup failed: .../casadi/interfaces/sundials/cvodes_interface.cpp:650: 'jacF' calculation failed
    (RayActor pid=94841) psetup failed: .../casadi/interfaces/sundials/cvodes_interface.cpp:650: 'jacF' calculation failed
    (RayActor pid=94841) psetup failed: .../casadi/interfaces/sundials/cvodes_interface.cpp:650: 'jacF' calculation failed
    (RayActor pid=94841) psetup failed: .../casadi/interfaces/sundials/cvodes_interface.cpp:650: 'jacF' calculation failed
    (RayActor pid=94841) psetup failed: .../casadi/interfaces/sundials/cvodes_interface.cpp:650: 'jacF' calculation failed
    

    Expected behaviour

    Simulation results when using the Casadi or Ray manager should be the same.

    Relevant log output

    No response

    Additional context

    No response

    bug 
    opened by wigging 15
  • Problem faced in liionpack simulation

    Problem faced in liionpack simulation

    I'm facing some problems regarding the experiment simulation as "keyerror". I'm new to this programing and can't crack it. Please let me know where I'm writing wrong code? Thanks in advance Untitled.pdf

    opened by Ashuprateek 11
  • Save simulation output

    Save simulation output

    Added some utility functions to save the simulation output to CSV, NumPy .npy, and compressed NumPy .npz files. Also added an example of using the save functions.

    I did a comparison of the disk usage for these different file formats (see below). The compressed .npz format can save a lot of storage space for large pack simulations. There are other file formats such as Feather and Parquet but using those formats would introduce more dependencies to liionpack.

    Below are disk storage results for a 16p2s single charge/discharge simulation. Simulation outputs written to file are time, terminal voltage, pack terminal voltage, pack current, open circuit voltage, cell resistance, and cell current.

    | Filetype | Disk space | |---------|-----------:| | csv | 552 KB | | npy | 188 KB | | npz | 88 KB |

    Below are disk storage results for a 400p125s single charge/discharge simulation. Simulation outputs written to file are time, terminal voltage, pack terminal voltage, pack current, open circuit voltage, cell resistance, and cell current.

    | Filetype | Disk space | |---------|-----------:| | csv | 803 MB | | npy | 246 MB | | npz | 33 MB |

    opened by wigging 10
  • Different Initial condition for the cells in pack

    Different Initial condition for the cells in pack

    I tried to populate different initial electrolyte concentration across the cells in the pack. I have an array of initial electrolyte concentration (initial_eleConc) similar to total heat transfer coefficient (htc). The changes I made to utils.py, simulations.py, solver_utils.py are given below.

    Changed line 220, and added line 229 in utils.py.

    # -*- coding: utf-8 -*-
    """
    Created on Thu Sep 23 10:33:13 2021
    
    @author: Tom
    """
    from scipy.interpolate import interp1d, interp2d
    import numpy as np
    import pandas as pd
    import os
    import liionpack
    from skspatial.objects import Plane
    from skspatial.objects import Points
    
    ROOT_DIR = os.path.dirname(os.path.abspath(liionpack.__file__))
    CIRCUIT_DIR = os.path.join(ROOT_DIR, "circuits")
    DATA_DIR = os.path.join(ROOT_DIR, "data")
    INIT_FUNCS = os.path.join(ROOT_DIR, "init_funcs")
    
    
    def interp_current(df):
        r"""
        Returns an interpolation function for current w.r.t time
    
        Parameters
        ----------
        df : pandas.DataFrame or Dict
            Contains data for 'Time' and 'Cells Total Current' from which to
            construct an interpolant function
    
        Returns
        -------
        f : function
            interpolant function of total cell current with time.
    
        """
        t = df["Time"]
        I = df["Cells Total Current"]
        f = interp1d(t, I)
        return f
    
    
    def _z_from_plane(X, Y, plane):
        r"""
        Given X and Y and a plane provide Z
        X - temperature
        Y - flow rate
        Z - heat transfer coefficient
    
        Parameters
        ----------
        X : float (array)
            x-coordinate.
        Y : float (array)
            z-coordinate.
        plane : skspatial.object.Plane
            plane returned from read_cfd_data.
    
        Returns
        -------
        z : float (array)
            z-coordinate.
    
        """
        a, b, c = plane.normal
        d = plane.point.dot(plane.normal)
        z = (d - a * X - b * Y) / c
        return z
    
    
    def _fit_plane(xv, yv, dbatt):
        r"""
        Private method to fit plane to CFD data
    
        Parameters
        ----------
        xv : ndarray
            temperature meshgrid points.
        yv : ndarray
            flow_rate meshgrid points.
        dbatt : ndarray
            cfd data for heat transfer coefficient.
    
        Returns
        -------
        plane : skspatial.object.Plane
            htc varies linearly with temperature and flow rate so relationship
            describes a plane
    
        """
        nx, ny = xv.shape
        pts = []
        for i in range(nx):
            for j in range(ny):
                pts.append([xv[i, j], yv[i, j], dbatt[i, j]])
    
        points = Points(pts)
        plane = Plane.best_fit(points, tol=1e-6)
        return plane
    
    
    def read_cfd_data(data_dir=None, filename="cfd_data.xlsx", fit="linear"):
        r"""
        A very bespoke function to read heat transfer coefficients from an excel
        file
    
        Parameters
        ----------
        data_dir : str, optional
            Path to data file. The default is None. If unspecified the module
            liionpack.DATA_DIR folder will be used
        filename : str, optional
            The default is 'cfd_data.xlsx'.
        fit : str
            options are 'linear' (default) and 'interpolated'.
        Returns
        -------
        funcs : list
            an interpolant is returned for each cell in the excel file.
    
        """
        if data_dir is None:
            data_dir = liionpack.DATA_DIR
        fpath = os.path.join(data_dir, filename)
        ncells = 32
        flow_bps = np.array(pd.read_excel(fpath, sheet_name="massflow_bps", header=None))
        temp_bps = np.array(pd.read_excel(fpath, sheet_name="temperature_bps", header=None))
        xv, yv = np.meshgrid(temp_bps, flow_bps)
        data = np.zeros([len(temp_bps), len(flow_bps), ncells])
        fits = []
        for i in range(ncells):
            data[:, :, i] = np.array(
                pd.read_excel(fpath, sheet_name="cell" + str(i + 1), header=None)
            )
            # funcs.append(interp2d(xv, yv, data[:, :, i], kind='linear'))
            if fit == "linear":
                fits.append(_fit_plane(xv, yv, data[:, :, i]))
            elif fit == "interpolated":
                fits.append(interp2d(xv, yv, data[:, :, i], kind="linear"))
    
        return data, xv, yv, fits
    
    
    def get_linear_htc(planes, T, Q):
        r"""
        A very bespoke function that is called in the solve process to update the
        heat transfer coefficients for every battery - assuming linear relation
        between temperature, flow rate and heat transfer coefficient.
    
        Parameters
        ----------
        planes : list
            each element of the list is a plane equation describing linear relation
            between temperature, flow rate and heat transfer coefficient.
        T : float array
            The temperature of each battery.
        Q : float
            The flow rate for the system.
    
        Returns
        -------
        htc : float
            Heat transfer coefficient for each battery.
    
        """
        ncell = len(T)
        htc = np.zeros(ncell)
        for i in range(ncell):
            htc[i] = _z_from_plane(T[i], Q, planes[i])
        return htc
    
    
    def get_interpolated_htc(funcs, T, Q):
        r"""
        A very bespoke function that is called in the solve process to update the
        heat transfer coefficients for every battery
    
        Parameters
        ----------
        funcs : list
            each element of the list is an interpolant function.
        T : float array
            The temperature of each battery.
        Q : float
            The flow rate for the system.
    
        Returns
        -------
        htc : float
            Heat transfer coefficient for each battery.
    
        """
        ncell = len(T)
        htc = np.zeros(ncell)
        for i in range(ncell):
            htc[i] = funcs[i](T[i], Q)
        return htc
    
    
    def build_inputs_dict(I_batt, htc,initial_eleConc):
        r"""
        Function to convert inputs and external_variable arrays to list of dicts
        As expected by the casadi solver. These are then converted back for mapped
        solving but stored individually on each returned solution.
        Can probably remove this process later
    
        Parameters
        ----------
        I_batt : float array
            The input current for each battery.
        htc : float array
            the heat transfer coefficient for each battery.
    
        Returns
        -------
        inputs_dict : list
            each element of the list is an inputs dictionary corresponding to each
            battery.
    
    
        """
        inputs_dict = []
        for i in range(len(I_batt)):
            inputs_dict.append(
                {
                    # 'Volume-averaged cell temperature': T_batt[i],
                    "Current": I_batt[i],
                    "Total heat transfer coefficient [W.m-2.K-1]": htc[i],
                    "Initial concentration in electrolyte [mol.m-3]": initial_eleConc[i],
                }
            )
        return inputs_dict
    

    Added line 73 in simulations.py.

    # -*- coding: utf-8 -*-
    """
    Created on Wed Sep 22 15:37:51 2021
    
    @author: tom
    """
    
    import pybamm
    
    
    def _current_function(t):
        r"""
        Internal function to make current an input parameter
    
        Parameters
        ----------
        t : float
            Dummy time parameter.
    
        Returns
        -------
        TYPE
            DESCRIPTION.
    
        """
        return pybamm.InputParameter("Current")
    
    
    def create_simulation(parameter_values=None, experiment=None, make_inputs=False):
        r"""
        Create a PyBaMM simulation set up for interation with liionpack
    
        Parameters
        ----------
        parameter_values : pybamm.ParameterValues class
            DESCRIPTION. The default is None.
        experiment : pybamm.Experiment class
            DESCRIPTION. The default is None.
        make_inputs : bool, optional
            Changes "Current function [A]" and "Total heat transfer coefficient
            [W.m-2.K-1]" to be inputs that are controlled by liionpack.
            The default is False.
    
        Returns
        -------
        sim : pybamm.Simulation
            A simulation that can be solved individually or passed into the
            liionpack solve method
    
        """
        # Create the pybamm model
        model = pybamm.lithium_ion.SPMe(
            options={
                "thermal": "lumped",
            }
        )
        # geometry = model.default_geometry
        if parameter_values is None:
            # load parameter values and process model and geometry
            chemistry = pybamm.parameter_sets.Chen2020
            parameter_values = pybamm.ParameterValues(chemistry=chemistry)
        # Change the current function to be an input as this is set by the external circuit
        if make_inputs:
            parameter_values.update(
                {
                    "Current function [A]": _current_function,
                }
            )
            parameter_values.update(
                {
                    "Current": "[input]",
                    "Total heat transfer coefficient [W.m-2.K-1]": "[input]",
                    "Initial concentration in electrolyte [mol.m-3]":"[input]",
                },
                check_already_exists=False,
            )
    
        solver = pybamm.CasadiSolver(mode="safe")
        sim = pybamm.Simulation(
            model=model,
            experiment=experiment,
            parameter_values=parameter_values,
            solver=solver,
        )
        return sim
    
    
    if __name__ == "__main__":
        sim = create_simulation()
        sim.solve([0, 1800])
        sim.plot()
    
    

    Changed lines 86, 122,163,256,266 in solver_utils.py.

    # -*- coding: utf-8 -*-
    """
    Created on Thu Sep 23 10:44:31 2021
    
    @author: Tom
    """
    import casadi
    import pybamm
    import numpy as np
    import time as ticker
    import liionpack as lp
    
    
    def _mapped_step(model, solutions, inputs_dict, integrator, variables, t_eval):
        r"""
        Internal function to process the model for one timestep in a mapped way.
        Mapped versions of the integrator and variables functions should already
        have been made.
    
        Parameters
        ----------
        model : pybamm.Model
            The built model
        solutions : list of pybamm.Solution objects for each battery
            Used to get the last state of the system and use as x0 and z0 for the
            casadi integrator
        inputs_dict : list of inputs_dict objects for each battery
            DESCRIPTION.
        integrator : mapped casadi.integrator
            Produced by _create_casadi_objects
        variables : mapped variables evaluator
            Produced by _create_casadi_objects
        t_eval : float array of times to evaluate
            Produced by _create_casadi_objects
    
        Returns
        -------
        sol : list
            solutions that have been stepped forward by one timestep
        var_eval : list
            evaluated variables for final state of system
    
        """
        len_rhs = model.concatenated_rhs.size
        N = len(solutions)
        if solutions[0] is None:
            # First pass
            x0 = casadi.horzcat(*[model.y0[:len_rhs] for i in range(N)])
            z0 = casadi.horzcat(*[model.y0[len_rhs:] for i in range(N)])
        else:
            x0 = casadi.horzcat(*[sol.y[:len_rhs, -1] for sol in solutions])
            z0 = casadi.horzcat(*[sol.y[len_rhs:, -1] for sol in solutions])
        # t_min = [0.0]*N
        t_min = 0.0
        inputs = []
        for temp in inputs_dict:
            inputs.append(casadi.vertcat(*[x for x in temp.values()] + [t_min]))
        ninputs = len(temp.values())
        inputs = casadi.horzcat(*inputs)
        # p = casadi.horzcat(*zip(inputs, external_variables, [t_min]*N))
        # inputs_with_tmin = casadi.vertcat(inputs, np.asarray(t_min))
        # Call the integrator once, with the grid
        timer = pybamm.Timer()
        tic = timer.time()
        casadi_sol = integrator(x0=x0, z0=z0, p=inputs)
        integration_time = timer.time()
        nt = len(t_eval)
        xf = casadi_sol["xf"]
        # zf = casadi_sol["zf"]
        sol = []
        xend = []
        for i in range(N):
            start = i * nt
            y_sol = xf[:, start:start + nt]
            xend.append(y_sol[:, -1])
            # Not sure how to index into zf - need an example
            sol.append(pybamm.Solution(t_eval, y_sol, model, inputs_dict[i]))
            sol[-1].integration_time = integration_time
        toc = timer.time()
        lp.logger.debug(f"Mapped step completed in {toc - tic}")
        xend = casadi.horzcat(*xend)
        var_eval = variables(0, xend, 0, inputs[0:ninputs, :])
        return sol, var_eval
    
    
    def _create_casadi_objects(I_init, htc,initial_eleConc, sim, dt, Nspm, nproc, variable_names):
        r"""
        Internal function to produce the casadi objects in their mapped form for
        parallel evaluation
    
        Parameters
        ----------
        I_init : float
            initial guess for current of a battery (not used for simulation).
        htc : float
            initial guess for htc of a battery (not used for simulation).
        sim : pybamm.Simulation
            A PyBaMM simulation object that contains the model, parameter_values,
            solver, solution etc.
        dt : float
            The time interval for a single timestep. Fixed throughout the simulation
        Nspm : int
            Number of individual batteries in the pack.
        nproc : int
            Number of parallel processes to map to.
        variable_names : list
            Variables to evaluate during solve. Must be a valid key in the
            model.variables
    
        Returns
        -------
        integrator : mapped casadi.integrator
            Solves an initial value problem (IVP) coupled to a terminal value
            problem with differential equation given as an implicit ODE coupled
            to an algebraic equation and a set of quadratures
        variables_fn : mapped variables evaluator
            evaluates the simulation and output variables. see casadi function
        t_eval : float array of times to evaluate
            times to evaluate in a single step, starting at zero for each step
    
        """
        inputs = {"Current": I_init, "Total heat transfer coefficient [W.m-2.K-1]": htc,"Initial concentration in electrolyte [mol.m-3]": initial_eleConc}
        solver = sim.solver
        # solve model for 1 second to initialise the circuit
        t_eval = np.linspace(0, 1, 2)
        # Initial solution - this builds the model behind the scenes
        sim.solve(t_eval, inputs=inputs)
        # step model
        # Code to create mapped integrator
        t_eval = np.linspace(0, dt, 11)
        t_eval_ndim = t_eval / sim.model.timescale.evaluate()
        inp_and_ext = inputs
        # No external variables - Temperature solved as lumped model in pybamm
        # External variables could (and should) be used if battery thermal problem
        # Includes conduction with any other circuits or neighboring batteries
        # inp_and_ext.update(external_variables)
    
        integrator = solver.create_integrator(
            sim.built_model, inputs=inp_and_ext, t_eval=t_eval_ndim
        )
        integrator = integrator.map(Nspm, "thread", nproc)
    
        # Variables function for parallel evaluation
        casadi_objs = sim.built_model.export_casadi_objects(variable_names=variable_names)
        variables = casadi_objs["variables"]
        t, x, z, p = (
            casadi_objs["t"],
            casadi_objs["x"],
            casadi_objs["z"],
            casadi_objs["inputs"],
        )
        variables_stacked = casadi.vertcat(*variables.values())
        variables_fn = casadi.Function("variables", [t, x, z, p], [variables_stacked])
        variables_fn = variables_fn.map(Nspm, "thread", nproc)
        return integrator, variables_fn, t_eval
    
    
    def solve(
        netlist=None,
        parameter_values=None,
        experiment=None,
        I_init=1.0,
        htc=None,initial_eleConc=None,
        initial_soc=0.5,
        nproc=12,
        output_variables=None,
    ):
        r"""
        Solves a pack simulation
    
        Parameters
        ----------
        netlist : pandas.DataFrame
            A netlist of circuit elements with format. desc, node1, node2, value.
            Produced by liionpack.read_netlist or liionpack.setup_circuit
        parameter_values : pybamm.ParameterValues class
            A dictionary of all the model parameters
        experiment : pybamm.Experiment class
            The experiment to be simulated. experiment.period is used to
            determine the length of each timestep.
        I_init : float, optional
            Initial guess for single battery current [A]. The default is 1.0.
        htc : float array, optional
            Heat transfer coefficient array of length Nspm. The default is None.
        initial_soc : float
            The initial state of charge for every battery. The default is 0.5
        nproc : int, optional
            Number of processes to start in parallel for mapping. The default is 12.
        output_variables : list, optional
            Variables to evaluate during solve. Must be a valid key in the
            model.variables
    
        Raises
        ------
        Exception
            DESCRIPTION.
    
        Returns
        -------
        output : ndarray shape [# variable, # steps, # batteries]
            simulation output array
    
        """
    
        if netlist is None or parameter_values is None or experiment is None:
            raise Exception("Please supply a netlist, paramater_values, and experiment")
    
        # Get netlist indices for resistors, voltage sources, current sources
        Ri_map = netlist["desc"].str.find("Ri") > -1
        V_map = netlist["desc"].str.find("V") > -1
        I_map = netlist["desc"].str.find("I") > -1
    
        Nspm = np.sum(V_map)
    
        protocol = lp.generate_protocol_from_experiment(experiment)
        dt = experiment.period
        Nsteps = len(protocol)
    
        # Solve the circuit to initialise the electrochemical models
        V_node, I_batt = lp.solve_circuit(netlist)
    
        sim = lp.create_simulation(parameter_values, make_inputs=True)
        lp.update_init_conc(sim, SoC=initial_soc)
    
        v_cut_lower = parameter_values["Lower voltage cut-off [V]"]
        v_cut_higher = parameter_values["Upper voltage cut-off [V]"]
    
        # The simulation output variables calculated at each step for each battery
        # Must be a 0D variable i.e. battery wide volume average - or X-averaged for 1D model
        variable_names = [
            "Terminal voltage [V]",
            "Measured battery open circuit voltage [V]",
            "Local ECM resistance [Ohm]",
        ]
        if output_variables is not None:
            for out in output_variables:
                if out not in variable_names:
                    variable_names.append(out)
            # variable_names = variable_names + output_variables
        Nvar = len(variable_names)
        # Storage variables for simulation data
        shm_i_app = np.zeros([Nsteps, Nspm], dtype=float)
        shm_Ri = np.zeros([Nsteps, Nspm], dtype=float)
        output = np.zeros([Nvar, Nsteps, Nspm], dtype=float)
    
        # Initialize currents in battery models
        shm_i_app[0, :] = I_batt * -1
    
        time = 0
        # step = 0
        end_time = dt * Nsteps
        step_solutions = [None] * Nspm
        V_terminal = []
        record_times = []
    
        integrator, variables_fn, t_eval = _create_casadi_objects(
            I_init, htc[0],initial_eleConc[0], sim, dt, Nspm, nproc, variable_names
        )
    
        sim_start_time = ticker.time()
    
        for step in range(Nsteps):
            step_solutions, var_eval = _mapped_step(
                sim.built_model,
                step_solutions,
                lp.build_inputs_dict(shm_i_app[step, :], htc,initial_eleConc),
                integrator,
                variables_fn,
                t_eval,
            )
            output[:, step, :] = var_eval
    
            time += dt
            # Calculate internal resistance and update netlist
            temp_v = output[0, step, :]
            temp_ocv = output[1, step, :]
            temp_Ri = np.abs(output[2, step, :])
            shm_Ri[step, :] = temp_Ri
    
            netlist.loc[V_map, ("value")] = temp_ocv
            netlist.loc[Ri_map, ("value")] = temp_Ri
            netlist.loc[I_map, ("value")] = protocol[step]
    
            # print('Stepping time', np.around(ticker.time()-tic, 2), 's')
            if np.any(temp_v < v_cut_lower):
                print("Low V limit reached")
                break
            if np.any(temp_v > v_cut_higher):
                print("High V limit reached")
                break
            # step += 1
            if time <= end_time:
                record_times.append(time)
                V_node, I_batt = lp.solve_circuit(netlist)
                V_terminal.append(V_node.max())
            if time < end_time:
                shm_i_app[step + 1, :] = I_batt[:] * -1
        all_output = {}
        all_output["Time [s]"] = np.asarray(record_times)
        all_output["Pack current [A]"] = np.asarray(protocol[: step + 1])
        all_output["Pack terminal voltage [V]"] = np.asarray(V_terminal)
        all_output["Cell current [A]"] = shm_i_app[: step + 1, :]
        for j in range(Nvar):
            all_output[variable_names[j]] = output[j, : step + 1, :]
    
        toc = ticker.time()
        pybamm.logger.notice(
            "Solve circuit time " + str(np.around(toc - sim_start_time, 3)) + "s"
        )
        return all_output
    
    

    The example code I ran is:

    import liionpack as lp
    import numpy as np
    import pybamm
    
    # Generate the netlist
    netlist = lp.setup_circuit(Np=16, Ns=2, Rb=1e-4, Rc=1e-2, Ri=5e-2, V=3.2, I=80.0)
    
    output_variables = [  
        'X-averaged total heating [W.m-3]',
        'Volume-averaged cell temperature [K]',
        'X-averaged negative particle surface concentration [mol.m-3]',
        'X-averaged positive particle surface concentration [mol.m-3]',
        'X-averaged electrolyte concentration [mol.m-3]',
        ]
    
    # Heat transfer coefficients
    htc = np.ones(32) * 10
    #initial_eleConc=np.ones(32) * 1000.0
    initial_eleConc=np.array([1000., 1000., 2000., 2000., 1000., 1000., 1000., 1000., 1000.,
           1000., 1000., 1000., 1000., 1000., 1000., 1000., 1000., 1000.,
           1000., 1000., 1000., 1000., 1000., 3000., 3000., 3000., 1000.,
           1000., 1000., 1000., 1000., 1000.])
    
    # Cycling experiment, using PyBaMM
    experiment = pybamm.Experiment(
        ["Charge at 50 A for 30 minutes", "Rest for 15 minutes", "Discharge at 50 A for 30 minutes", "Rest for 30 minutes"],
        period="10 seconds",
    )
    
    # PyBaMM parameters
    chemistry = pybamm.parameter_sets.Chen2020
    parameter_values = pybamm.ParameterValues(chemistry=chemistry)
    
    # Solve pack
    output = lp.solve(netlist=netlist,
                      parameter_values=parameter_values,
                      experiment=experiment,
                      output_variables=output_variables,
                      htc=htc,initial_eleConc=initial_eleConc)
    lp.plot_output(output)
    

    However, in the simulation the first value of initial electrolyte concentration, i.e., initial_eleConc[0] is populating across all the cells. Here is the output showing that X-averaged electrolyte concentration  mol m-3 : I think the issue is in line 256 of solver_utils.py, i.e.

    integrator, variables_fn, t_eval = _create_casadi_objects(
            I_init, htc[0],initial_eleConc[0], sim, dt, Nspm, nproc, variable_names
        )
    

    where it is only taking the first element of the array initial_eleConc[0]. I gave initial_eleConc array similar to htc(heat transfer coefficient) array. As htc array is going into lumped model, so every cell can have their own individual heat transfer coefficient. In contrast, the parameters in the base model seems not populating across the cells.

    opened by ksnvikrant 10
  • [Bug]: lp make lcapy circuit

    [Bug]: lp make lcapy circuit

    liionpack Version

    0.3

    Python Version

    3.8

    Describe the bug

    I try to make a circuit from lcapy to connect to liionpack, my code is import pandas as pd import liionpack as lp net_left = lp.setup_circuit(Np=1, Ns=1, terminals="left") netlist = pd.DataFrame({"desc": ["V1"], "node1": [1], "node2": [5], "value": [10]}) aa = pd.DataFrame(data = netlist) lp.make_lcapy_circuit(aa) lp.show_plots() but my code is error, i don't realy know about parameters or what must be fill on lp.make_lcapy_circuit() Thank you very much

    Steps to Reproduce

    No response

    Expected behaviour

    No response

    Relevant log output

    Traceback (most recent call last):
      File "C:\Users\user\AppData\Local\Programs\Python\Python38\lib\site-packages\pandas\core\indexes\base.py", line 3621, in get_loc
        return self._engine.get_loc(casted_key)
      File "pandas\_libs\index.pyx", line 136, in pandas._libs.index.IndexEngine.get_loc
      File "pandas\_libs\index.pyx", line 163, in pandas._libs.index.IndexEngine.get_loc
      File "pandas\_libs\hashtable_class_helper.pxi", line 5198, in pandas._libs.hashtable.PyObjectHashTable.get_item
      File "pandas\_libs\hashtable_class_helper.pxi", line 5206, in pandas._libs.hashtable.PyObjectHashTable.get_item
    KeyError: 'node1_x'
    
    The above exception was the direct cause of the following exception:
    
    Traceback (most recent call last):
      File "c:\Users\user\Documents\TUGAS AKHIR\print circuit.py", line 9, in <module>
        lp.make_lcapy_circuit(aa)
      File "C:\Users\user\AppData\Local\Programs\Python\Python38\lib\site-packages\liionpack\netlist_utils.py", line 626, in make_lcapy_circuit
        I_xs = [net2[I_map]["node1_x"].values[0], net2[I_map]["node2_x"].values[0]]
      File "C:\Users\user\AppData\Local\Programs\Python\Python38\lib\site-packages\pandas\core\frame.py", line 3505, in __getitem__
        indexer = self.columns.get_loc(key)
      File "C:\Users\user\AppData\Local\Programs\Python\Python38\lib\site-packages\pandas\core\indexes\base.py", line 3623, in get_loc
        raise KeyError(key) from err
    KeyError: 'node1_x'
    

    Additional context

    No response

    bug 
    opened by SyarifulAzis1018 8
  • [Bug]: pip install in Windows

    [Bug]: pip install in Windows

    liionpack Version

    v0.3

    Python Version

    3.7.7

    Describe the bug

    When trying to pip install liionpack on Windows it does not work because ray can't be pip installed (it needs to use conda, see here. Not sure where ray is used. If it is not fundamental, can we make it optional (similar to scikits-ode solvers in PyBaMM)?

    Steps to Reproduce

    1. Get a Windows machine
    2. Run pip install liionpack
    3. See the error

    Expected behaviour

    liionpack should be installed without errors.

    Relevant log output

    ERROR: Could not find a version that satisfies the requirement ray (from liionpack) (from versions: none)
    ERROR: No matching distribution found for ray
    

    Additional context

    No response

    bug 
    opened by brosaplanella 8
  • [Bug]: can't conda install via environment.yml on mac

    [Bug]: can't conda install via environment.yml on mac

    liionpack Version

    n/a

    Python Version

    3.9

    Describe the bug

    Install command suggested in the readme fails

    $ conda env create -f environment.yml
    Collecting package metadata (repodata.json): done
    Solving environment: failed
    
    ResolvePackageNotFound: 
      - redis
    

    Steps to Reproduce

    No response

    Expected behaviour

    Installs

    Relevant log output

    No response

    Additional context

    Mac OS on M1

    Possibly related to https://stackoverflow.com/questions/58219956/how-to-fix-resolvepackagenotfound-error-when-creating-conda-environment

    bug 
    opened by tinosulzer 8
  • Event handling

    Event handling

    Currently there is none except monitoring the voltage limits on all cells manually and stopping the simulation. Would be amazing if we can leverage some of the event handling in PyBaMM

    enhancement 
    opened by TomTranter 8
  • External variables

    External variables

    I am trying to understand external variables and if we really need them or if the same results can be achieved using the appropriate inputs and models. It would be nice if we could remove them, since they introduce some technical debt into both PyBaMM and liionpack (e.g. currently #196 is failing because of external variables, and in pybamm it's causing issues with dimensional models).

    In liionpack I see the main use case is to use a model with options={"thermal": "lumped", "external submodels": ["thermal"]}, then providing "Volume-averaged cell temperature" as an external variable, changing over different steps. This could just as easily be achieved by setting "Ambient temperature [K]" as an input changing over different steps. If the goal is to also track the change in temperature from the cell model, the lumped model (not external) with changing ambient temperature should also achieve this. It's not clear to me why we should update the "volume-averaged cell temperature" instead of the "ambient temperature".

    Am I missing something?

    @Scottmar93 @TomTranter

    opened by tinosulzer 7
  • [Bug]: Update thermal external simulation

    [Bug]: Update thermal external simulation

    liionpack Version

    0.3.2

    Python Version

    3.8.3

    Describe the bug

    External thermal simulation needs updating to use input instead of external submodel

    Steps to Reproduce

    No response

    Expected behaviour

    No response

    Relevant log output

    No response

    Additional context

    No response

    bug 
    opened by TomTranter 0
  • [Bug]: Prada2013 not working

    [Bug]: Prada2013 not working

    liionpack Version

    June 2022

    Python Version

    3.8

    Describe the bug

    parameter_values = pybamm.ParameterValues("Prada2013")

    does not work. It gives: pybamm.expression_tree.exceptions.SolverError: Events ['Maximum voltage'] are non-positive at initial conditions

    I tried to vary the Voltage in the netlist, but all values give the same error

    Steps to Reproduce

    take any working example and replace the parameters with Prada2013

    Expected behaviour

    same as Pybamm

    Relevant log output

    No response

    Additional context

    No response

    bug 
    opened by skywo1f 0
  • [Bug]: running an experiment where amperage is described in C ratings causes errors

    [Bug]: running an experiment where amperage is described in C ratings causes errors

    liionpack Version

    June 2022

    Python Version

    3.8

    Describe the bug

    running an experiment where amperage is described in C ratings causes errors

    
    experiment = pybamm.Experiment(
        [
            
                "Charge at 1 C until 3.65 V",
                "Hold at 3.65 V until C/10",
                "Rest for 5 minutes",
                "Discharge at 3 C until 2.5 V",
                "Rest for 5 minutes",
            
        ],period="10 seconds"
        )
    
    

    causes:

    Traceback (most recent call last):
      File "testA.py", line 44, in <module>
        output = lp.solve(netlist=netlist,
      File "/home/iviti/batterySims/liionpack/liionpack/solver_utils.py", line 436, in solve
        output = rm.solve(
      File "/home/iviti/batterySims/liionpack/liionpack/solvers.py", line 317, in solve
        self.protocol = lp.generate_protocol_from_experiment(experiment, flatten=True)
      File "/home/iviti/batterySims/liionpack/liionpack/protocols.py", line 28, in generate_protocol_from_experiment
        if t % dt != 0:
    TypeError: unsupported operand type(s) for %: 'NoneType' and 'float'
    
    

    Steps to Reproduce

    run any experiment where discharging or charging is described in terms of C

    Expected behaviour

    This works in regular pybamm

    Relevant log output

    No response

    Additional context

    No response

    bug 
    opened by skywo1f 0
  • [Bug]: TypeError: solve() got an unexpected keyword argument 'htc'

    [Bug]: TypeError: solve() got an unexpected keyword argument 'htc'

    liionpack Version

    June 2022

    Python Version

    3.8

    Describe the bug

    Got this error when running the basic example from the readme:

    TypeError: solve() got an unexpected keyword argument 'htc'

    Steps to Reproduce

    follow steps to create environment, activate environment, copy README code into test.py run test.py

    Expected behaviour

    run the basic example

    Relevant log output

    ~/batterySims/liionpack$ python3 testLiion.py 
    Traceback (most recent call last):
      File "testLiion.py", line 30, in <module>
        output = lp.solve(netlist=netlist, parameter_values=parameter_values, experiment=experiment, output_variables=output_variables,htc=htc)
    TypeError: solve() got an unexpected keyword argument 'htc'
    

    Additional context

    If I remove that htc line, then the simulation seems to run (for Mohtat2020, not Prada2013) No response

    bug 
    opened by skywo1f 8
  • Easy statistical distributions

    Easy statistical distributions

    Description

    Add a nice way to pass a statistical distribution as an input for varying circuit and battery parameters. For example if you want to create a pack with a single particle model and vary the particle radius according to a statistical distribution we could add a helper function or an example of how to do that properly.

    Motivation

    This will help to model packs with real world statistical variation in parameters

    Possible Implementation

    It is already possible to pass an array of values as an input. We could add an example showing how to populate this array from a distribution or could go one step further and hand the selection of values from the distribution to the solver algorithms by passing the distribution object. This might be useful for comparing multiple different variations...

    Additional context

    No response

    opened by TomTranter 3
Releases(v0.3.3)
Owner
PyBaMM Team
PyBaMM Team
E-Ink Magic Calendar that automatically syncs to Google Calendar and runs off a battery powered Raspberry Pi Zero

E-Ink Magic Calendar that automatically syncs to Google Calendar and runs off a battery powered Raspberry Pi Zero

null 2.8k Dec 30, 2022
Event-based hardware simulation framework

An event-based multi-device simulation framework providing configuration and orchestration of complex multi-device simulations.

Diamond Light Source Controls Group 3 Feb 1, 2022
Modeling and Simulation of Satellite Servicing Manipulators

Modeling and Simulation of Satellite Servicing Manipulators Final Project for the course ENPM662: Introduction to Robot Modeling (Fall 2021). This pro

Adarsh M 1 Jan 24, 2022
A versatile program that uses the raspberry pi camera and provides it as a service

PiCameleon Is a daemon program meant to provide the RaspberryPi Camera as a service while running according to a configuration.

André Esser 52 Oct 16, 2022
Uses the Duke Energy Gateway to import near real time energy usage into Home Assistant

Duke Energy Gateway This is a custom integration for Home Assistant. It pulls near-real-time energy usage from Duke Energy via the Duke Energy Gateway

Michael Meli 28 Dec 23, 2022
This repo uses a stereo camera and gray-code-based structured light to realize dense 3D reconstruction.

Structured-light-stereo This repo uses a stereo camera and gray-code-based structured light to realize dense 3D reconstruction. . How to use: STEP 1:

FEI 20 Dec 31, 2022
PyLog - Simple keylogger that uses pynput to listen to keyboard input.

Simple keylogger that uses pynput to listen to keyboard input. Outputs to a text file and the terminal. Press the escape key to stop.

null 1 Dec 29, 2021
Philippe 1 Jan 9, 2022
CO2Ampel - This RaspberryPi project uses weather data to estimate the share of renewable energy in the power grid

CO2Ampel This RaspberryPi project uses weather data to estimate the share of ren

Felix 4 Jan 19, 2022
FERM: A Framework for Efficient Robotic Manipulation

Framework for Efficient Robotic Manipulation FERM is a framework that enables robots to learn tasks within an hour of real time training.

Ruihan (Philip) Zhao 111 Dec 31, 2022
Robot Framework keyword library wrapper for atlassian-python-api

Robot Framework keyword library wrapper for atlassian-python-api

Marcin Koperski 3 Jul 29, 2022
Python information display framework aimed at e-ink devices

My display, using a Raspberry Pi Zero W and Waveshare 6" e-paper hat infodisplay Modular information display framework aimed at e-ink devices. Built u

Niek Blankers 3 Apr 8, 2022
Tool to create 3D printable terrain with integrated path/road part files (Single material 3d printer)

BACKGROUND This has been an ongoing project of mine for a few months now. I run trails a lot and original the goal was to create a function to combine

null 9 Apr 26, 2022
gdsfactory is an EDA (electronics design automation) tool to Layout Integrated Circuits.

gdsfactory 3.5.5 gdsfactory is an EDA (electronics design automation) tool to Layout Integrated Circuits. It is build on top of phidl gdspy and klayou

null 147 Jan 4, 2023
MPY tool - manage files on devices running MicroPython

mpytool MPY tool - manage files on devices running MicroPython It is an alternative to ampy Target of this project is to make more clean code, faster,

Pavel Revak 5 Aug 17, 2022
This tool emulates an EMV-CAP device, to illustrate the article "Banque en ligne : à la decouverte d'EMV-CAP" published in MISC

About This tool emulates an EMV-CAP device, to illustrate the article "Banque en ligne : à la decouverte d'EMV-CAP" published in MISC, issue #56 and f

Philippe Teuwen 28 Nov 21, 2022
MCRPC (Minecraft Resource Pack Comparator) checks your resource pack against any version of Minecraft to show resources missing from your pack for that version.

Minecraft Resource Pack Comparator MCRPC checks your resource pack against any version of Minecraft to show resources missing from your pack for that

null 3 Nov 3, 2022
In this project, we are going to display the battery notification and the time left for the battery to drain out using the battery capacity value.

In this project, we are going to display the battery notification and the time left for the battery to drain out using the battery capacity value.

Ritoban Biswas 1 Dec 20, 2021
Battery conservation Python script for ubuntu to enable battery conservation mode at 60% 80% or 90%

Description Batteryconservation is a small python script wich creates an appindicator for ubuntu which can be used to enable / disable battery conserv

null 3 Jan 4, 2022
An example repository for how to generate results using PyBaMM

PyBaMM results This repository provides a template for generating results (for example, for a paper) using PyBaMM Installation Install PyBaMM using a

PyBaMM Team 7 Oct 9, 2022