PyBaMM has a number of optional dependencies for different functionalities.
If the optional dependency is not installed, PyBaMM will raise an ImportError when the method requiring that dependency is called.
If you are using pip, optional PyBaMM dependencies can be installed or managed in a file (e.g., setup.py, or pyproject.toml)
as optional extras (e.g.,``pybamm[dev,plot]``). All optional dependencies can be installed with pybamm[all],
and specific sets of dependencies are listed in the sections below.
We recommend to install PyBaMM within a virtual environment, in order
not to alter any distribution Python files.
First, make sure you are using Python 3.8, 3.9, 3.10, 3.11, or 3.12.
To create a virtual environment env within your current directory type:
virtualenvenv
You can then “activate” the environment using:
sourceenv/bin/activate
Now all the calls to pip described below will install PyBaMM and its
dependencies into the environment env. When you are ready to exit
the environment and go back to your original system, just type:
deactivate
PyBaMM can be installed via pip. On macOS, it is necessary to install the SUNDIALS
library beforehand.
In a terminal, run the following command:
pipinstallpybamm
In a terminal, run the following commands:
brewinstallsundials
pipinstallpybamm
PyBaMM’s required dependencies (such as numpy, casadi, etc) will be
installed automatically when you install PyBaMM using pip.
system (under ~/.local), before installing scikits.odes. (Alternatively, one can install SUNDIALS without this script and run pipinstallpybamm[odes] to install pybamm with scikits.odes.)
The pybamm_install_odes command, installed with PyBaMM, automatically downloads and installs the SUNDIALS library on your
system (under ~/.local), before installing scikits.odes . (Alternatively, one can install SUNDIALS without this script and run pipinstallpybamm[odes] to install pybamm with scikits.odes)
To avoid installation failures when using pipinstallpybamm[odes], make sure to set the SUNDIALS_INST environment variable. If you have installed SUNDIALS using Homebrew, set the variable to the appropriate location. For example:
exportSUNDIALS_INST=$(brew--prefixsundials)
Ensure that the path matches the installation location on your system. You can verify the installation location by running:
brewinfosundials
Look for the installation path, and use that path to set the SUNDIALS_INST variable.
Note: The location where Homebrew installs SUNDIALS might vary based on the system architecture (ARM or Intel). Adjust the path in the exportSUNDIALS_INST command accordingly.
To avoid manual setup of path the pybamm_install_odes is recommended for a smoother installation process, as it takes care of automatically downloading and installing the SUNDIALS library on your system.
Users can install jax and jaxlib to use the Jax solver.
Note
The Jax solver is only supported for Python versions 3.9 through 3.12.
pipinstall"pybamm[jax]"
The pipinstall"pybamm[jax]" command automatically downloads and installs pybamm and the compatible versions of jax and jaxlib on your system. (pybamm_install_jax is deprecated.)
To use PyBaMM, you must have Python 3.8, 3.9, 3.10, 3.11, or 3.12 installed.
To install Python 3 download the installation files from Python’s
website. Make sure to
tick the box on AddPython3.XtoPATH. For more detailed
instructions please see the official Python on Windows
guide.
Launch the Command Prompt and go to the directory where you want to
install PyBaMM. You can find a reminder of how to navigate the terminal
here.
We recommend to install PyBaMM within a virtual environment, in order
not to alter any distribution python files.
To install virtualenv, type:
python-mpipinstallvirtualenv
To create a virtual environment env within your current directory
type:
python-mvirtualenvenv
You can then “activate” the environment using:
env\Scripts\activate.bat
Now all the calls to pip described below will install PyBaMM and its
dependencies into the environment env. When you are ready to exit
the environment and go back to your original system, just type:
deactivate
PyBaMM can be installed via pip:
pipinstallpybamm
PyBaMM’s dependencies (such as numpy, scipy, etc) will be
installed automatically when you install PyBaMM using pip.
Users can install jax and jaxlib to use the Jax solver.
Note
The Jax solver is only supported for Python versions 3.9 through 3.12.
pipinstall"pybamm[jax]"
The pipinstall"pybamm[jax]" command automatically downloads and installs pybamm and the compatible versions of jax and jaxlib on your system. (pybamm_install_jax is deprecated.)
If you want to install the optional PyBaMM solvers, you have to use the
Windows Subsystem for Linux (WSL). You can find the installation
instructions here.
Install from source (Windows Subsystem for Linux)#
To make it easier to install PyBaMM, we recommend using the Windows Subsystem for Linux (WSL) along with Visual Studio Code. This guide will walk you through the process.
This page describes the build and installation of PyBaMM from the source code, available on GitHub. Note that this is not the recommended approach for most users and should be reserved to people wanting to participate in the development of PyBaMM, or people who really need to use bleeding-edge feature(s) not yet available in the latest released version. If you do not fall in the two previous categories, you would be better off installing PyBaMM using pip or conda.
Lastly, familiarity with the Python ecosystem is recommended (pip, virtualenvs).
Here is a gentle introduction/refresher: Python Virtual Environments: A Primer.
The following instructions are valid for both GNU/Linux distributions and MacOS.
If you are running Windows, consider using the Windows Subsystem for Linux (WSL).
To obtain the PyBaMM source code, clone the GitHub repository
gitclonehttps://github.com/pybamm-team/PyBaMM.git
or download the source archive on the repository’s homepage.
Finally, we recommend using Nox.
You can install it with
python3.X-mpipinstall--usernox
Depending on your operating system, you may or may not have pip installed along Python.
If pip is not found, you probably want to install the python3-pip package.
PyBaMM comes with a DAE solver based on the IDA solver provided by the SUNDIALS library.
To use this solver, you must make sure that you have the necessary SUNDIALS components
installed on your system.
The IDA-based solver is currently unavailable on windows.
If you are running windows, you can simply skip this section and jump to Installing PyBaMM.
# in the PyBaMM/ directory
nox-spybamm-requires
This will download, compile and install the SuiteSparse and SUNDIALS libraries.
Both libraries are installed in ~/.local.
It is recommended to use --verbose or -v to see outputs of all commands run.
This creates a virtual environment venv/ inside the PyBaMM/ directory.
It comes ready with PyBaMM and some useful development tools like pre-commit and ruff.
From the PyBaMM/ directory, you can install PyBaMM using
pipinstall.
If you intend to contribute to the development of PyBaMM, it is convenient to
install in “editable mode”, along with all the optional dependencies and useful
tools for development and documentation:
pipinstall-e.[all,dev,docs]
If you are using zsh, you would need to use different pattern matching:
# in the PyBaMM/ directory
pythonrun-tests.py--unit
The above starts a sub-process using the current python interpreter (i.e. using your current
Python environment) and run the unit tests. This can take a few minutes.
You can also use the test runner to run the doctests:
pythonrun-tests.py--doctest
There is more to the PyBaMM test runner. To see a list of all options, type
This will build the documentation and serve it locally (thanks to sphinx-autobuild) for preview.
The preview will be updated automatically following changes.
Here are some additional useful commands you can run with Nox:
--verboseor-v: Enables verbose mode, providing more detailed output during the execution of Nox sessions.
--listor-l: Lists all available Nox sessions and their descriptions.
--stop-on-first-error: Stops the execution of Nox sessions immediately after the first error or failure occurs.
--envdir<path>: Specifies the directory where Nox creates and manages the virtual environments used by the sessions. In this case, the directory is set to <path>.
--install-only: Skips the test execution and only performs the installation step defined in the Nox sessions.
--nocolor: Disables the color output in the console during the execution of Nox sessions.
--reportoutput.json: Generates a JSON report of the Nox session execution and saves it to the specified file, in this case, “output.json”.
nox-sdocs--non-interactive: Builds the documentation without serving it locally (using sphinx-build instead of sphinx-autobuild).
Problem: I have made edits to source files in PyBaMM, but these are
not being used when I run my Python script.
Solution: Make sure you have installed PyBaMM using the -e flag,
i.e. pipinstall-e.. This sets the installed location of the
source files to your current directory.
Problem: Errors when solving model
ValueError:Integratornameidadoesnotexist, or
ValueError:Integratornamecvodedoesnotexist.
Solution: This could mean that you have not installed
scikits.odes correctly, check the instructions given above and make
sure each command was successful.
One possibility is that you have not set your LD_LIBRARY_PATH to
point to the sundials library, type echo$LD_LIBRARY_PATH and make
sure one of the directories printed out corresponds to where the
SUNDIALS libraries are located.
Another common reason is that you forget to install a BLAS library such
as OpenBLAS before installing SUNDIALS. Check the cmake output when you
configured SUNDIALS, it might say:
If this is the case, on a Debian or Ubuntu system you can install
OpenBLAS using sudoapt-getinstalllibopenblas-dev (or
brewinstallopenblas for Mac OS) and then re-install SUNDIALS using
the instructions above.
This page describes the build and installation of PyBaMM using a Dockerfile, available on GitHub. Note that this is not the recommended approach for most users and should be reserved to people wanting to participate in the development of PyBaMM, or people who really need to use bleeding-edge feature(s) not yet available in the latest released version. If you do not fall in the two previous categories, you would be better off installing PyBaMM using pip or conda.
Before you begin, make sure you have Docker installed on your system. You can download and install Docker from the official Docker website.
Ensure Docker installation by running:
After building the Docker images with the desired solvers, use the dockerrun command followed by the desired image name. For example, to run a container from the image built with all optional solvers:
dockerrun-itpybamm:all
Activate PyBaMM development environment inside docker container using:
condaactivatepybamm
If you want to exit the Docker container’s shell, you can simply type:
You might require re-configuring git while running the docker container for the first time.
You can run gitconfig--list to ensure if you have desired git configuration already.
You can easily use Visual Studio Code inside a running Docker container by attaching it directly. This provides a seamless development environment within the container. Here’s how:
Install the “Docker” extension from Microsoft in your local Visual Studio Code if it’s not already installed.
Pull and run the Docker image containing PyBaMM development environment.
In your local Visual Studio Code, open the “Docker” extension by clicking on the Docker icon in the sidebar.
Under the “Containers” section, you’ll see a list of running containers. Right-click the running PyBaMM container.
Select “Attach Visual Studio Code” from the context menu.
Visual Studio Code will now connect to the container, and a new VS Code window will open up, running inside the container. You can now edit, debug, and work on your code using VS Code as if you were working directly on your local machine.
The easiest way to use PyBaMM is to run a 1C constant-current discharge with a model of your choice with all the default settings:
importpybammmodel=pybamm.lithium_ion.DFN()# Doyle-Fuller-Newman modelsim=pybamm.Simulation(model)sim.solve([0,3600])# solve for 1 hoursim.plot()
or simulate an experiment such as a constant-current discharge followed by a constant-current-constant-voltage charge:
importpybammexperiment=pybamm.Experiment([("Discharge at C/10 for 10 hours or until 3.3 V","Rest for 1 hour","Charge at 1 A until 4.1 V","Hold at 4.1 V until 50 mA","Rest for 1 hour",)]*3,)model=pybamm.lithium_ion.DFN()sim=pybamm.Simulation(model,experiment=experiment,solver=pybamm.CasadiSolver())sim.solve()sim.plot()
However, much greater customisation is available. It is possible to change the physics, parameter values, geometry, submesh type, number of submesh points, methods for spatial discretisation and solver for integration (see DFN script or notebook).
For new users we recommend the Getting Started guides. These are intended to be very simple step-by-step guides to show the basic functionality of PyBaMM, and can either be downloaded and used locally, or used online through Google Colab.
Further details can be found in a number of detailed examples, hosted on
GitHub. In addition, full details of classes and methods can be found in the API documentation.
Additional supporting material can be found
here.
PyBaMM (Python Battery Mathematical Modelling) is an open-source battery simulation package
written in Python. Our mission is to accelerate battery modelling research by
providing open-source tools for multi-institutional, interdisciplinary collaboration.
Broadly, PyBaMM consists of
a framework for writing and solving systems of differential equations,
a library of battery models and parameters, and
specialized tools for simulating battery-specific experiments and visualizing the results.
Together, these enable flexible model definitions and fast battery simulations, allowing users to
explore the effect of different battery designs and modeling assumptions under a variety of operating scenarios.
NOTE: This user-guide is a work-in-progress, we hope that this brief but incomplete overview will be useful to you.
The core of the framework is a custom computer algebra system to define mathematical equations,
and a domain specific modeling language to combine these equations into systems of differential equations
(usually partial differential equations for variables depending on space and time).
The expression tree example gives an introduction to the computer algebra system, and the Getting Started tutorials
walk through creating models of increasing complexity.
Once a model has been defined symbolically, PyBaMM solves it using the Method of Lines. First, the equations are discretised in the spatial dimension, using the finite volume method. Then, the resulting system is solved using third-party numerical solvers. Depending on the form of the model, the system can be ordinary differential equations (ODEs) (if only model.rhs is defined), or algebraic equations (if only model.algebraic is defined), or differential-algebraic equations (DAEs) (if both model.rhs and model.algebraic are defined). Jupyter notebooks explaining the solvers can be found here.
PyBaMM contains an extensive library of battery models and parameters.
The bulk of the library consists of models for lithium-ion, but there are also some other chemistries (lead-acid, lithium metal).
Models are first divided broadly into common named models of varying complexity, such as the single particle model (SPM) or Doyle-Fuller-Newman model (DFN).
Most options can be applied to any model, but some are model-specific (an error will be raised if you attempt to set an option is not compatible with a model).
See Base Battery Model for a list of options.
The parameter library is simply a collection of python files each defining a complete set of parameters
for a particular battery chemistry, covering all major lithium-ion chemistries (NMC, LFP, NCA, …).
External parameter sets can be linked using entry points (see Parameters Sets).
One of PyBaMM’s unique features is the Experiment class, which allows users to define synthetic experiments using simple instructions in English
pybamm.Experiment([("Discharge at C/10 for 10 hours or until 3.3 V","Rest for 1 hour","Charge at 1 A until 4.1 V","Hold at 4.1 V until 50 mA","Rest for 1 hour",)]*3,)
The above instruction will conduct a standard discharge / rest / charge / rest cycle three times, with a 10 hour discharge and 1 hour rest at the end of each cycle.
The Simulation class handles simulating an Experiment, as well as calculating additional outputs such as capacity as a function of cycle number. For example, the following code will simulate the experiment above and plot the standard output variables:
importpybammimportmatplotlib.pyplotasplt# load model and parameter valuesmodel=pybamm.lithium_ion.DFN()sim=pybamm.Simulation(model,experiment=experiment)solution=sim.solve()solution.plot()
Users are not limited to these tools and can plot the output of a simulation solution by accessing the underlying numpy array for the solution variables as
PyBaMM uses a set of pre-commit hooks and the pre-commit bot to format and prettify the codebase. The hooks can be installed locally using -
pipinstallpre-commit
pre-commitinstall
This would run the checks every time a commit is created locally. The checks will only run on the files modified by that commit, but the checks can be triggered for all the files using -
pre-commitrun--all-files
If you would like to skip the failing checks and push the code for further discussion, use the --no-verify option with gitcommit.
Commit your changes to your branch with useful, descriptive commit messages: Remember these are publicly visible and should still make sense a few months ahead in time. While developing, you can keep using the GitHub issue you’re working on as a place for discussion. Refer to your commits when discussing specific lines of code.
If you want to add a dependency on another library, or re-use code you found somewhere else, have a look at these guidelines.
PyBaMM has online documentation at http://docs.pybamm.org/. To make sure any new methods or classes you added show up there, please read the documentation section.
If you added a major new feature, perhaps it should be showcased in an example notebook.
Once a PR has been created, it will be reviewed by any member of the community. Changes might be suggested which you can make by simply adding new commits to the branch. When everything’s finished, someone with the right GitHub permissions will merge your changes into PyBaMM main repository.
Finally, if you really, really, really love developing PyBaMM, have a look at the current project infrastructure.
PyBaMM follows the PEP8 recommendations for coding style. These are very common guidelines, and community tools have been developed to check how well projects implement them. We recommend using pre-commit hooks to check your code before committing it. See installing and using pre-commit section for more details.
We use ruff to check our PEP8 adherence. To try this on your system, navigate to the PyBaMM directory in a console and type
python-mpipinstallpre-commit
pre-commitrunruff
ruff is configured inside the file pre-commit-config.yaml, allowing us to ignore some errors. If you think this should be added or removed, please submit an issue
When you commit your changes they will be checked against ruff automatically (see Pre-commit checks).
Naming is hard. In general, we aim for descriptive class, method, and argument names. Avoid abbreviations when possible without making names overly long, so mean is better than mu, but a class name like MyClass is fine.
Class names are CamelCase, and start with an upper case letter, for example MyOtherClass. Method and variable names are lower case, and use underscores for word separation, for example x or iteration_count.
While it’s a bad idea for developers to “reinvent the wheel”, it’s important for users to get a reasonably sized download and an easy install. In addition, external libraries can sometimes cease to be supported, and when they contain bugs it might take a while before fixes become available as automatic downloads to PyBaMM users.
For these reasons, all dependencies in PyBaMM should be thought about carefully, and discussed on GitHub.
Direct inclusion of code from other packages is possible, as long as their license permits it and is compatible with ours, but again should be considered carefully and discussed in the group. Snippets from blogs and stackoverflow can often be included without attribution, but if they solve a particularly nasty problem (or are very hard to read) it’s often a good idea to attribute (and document) them, by making a comment with a link in the source code.
On the other hand… We do want to compare several tools, to generate documentation, and to speed up development. For this reason, the dependency structure is split into 4 parts:
Core PyBaMM: A minimal set, including things like NumPy, SciPy, etc. All infrastructure should run against this set of dependencies, as well as any numerical methods we implement ourselves.
Extras: Other inference packages and their dependencies. Methods we don’t want to implement ourselves, but do want to provide an interface to can have their dependencies added here.
Documentation generating code: Everything you need to generate and work on the docs.
Development code: Everything you need to do PyBaMM development (so all of the above packages, plus ruff and other testing tools).
Only ‘core pybamm’ is installed by default. The others have to be specified explicitly when running the installation command.
PyBaMM utilizes optional dependencies to allow users to choose which additional libraries they want to use. Managing these optional dependencies and their imports is essential to provide flexibility to PyBaMM users.
PyBaMM provides a utility function have_optional_dependency, to check for the availability of optional dependencies within methods. This function can be used to conditionally import optional dependencies only if they are available. Here’s how to use it:
Optional dependencies should never be imported at the module level, but always inside methods. For example:
This allows people to (1) use PyBaMM without importing optional dependencies by default and (2) configure module-dependent functionalities in their scripts, which must be done before e.g. print_citations method is first imported.
Writing Tests for Optional Dependencies
Whenever a new optional dependency is added for optional functionality, it is recommended to write a corresponding unit test in test_util.py. This ensures that an error is raised upon the absence of said dependency. Here’s an example:
fromtestsimportTestCaseimportpybammclassTestUtil(TestCase):deftest_optional_dependency(self):# Test that an error is raised when pybtex is not availablewithself.assertRaisesRegex(ModuleNotFoundError,"Optional dependency pybtex is not available"):sys.modules["pybtex"]=Nonepybamm.function_using_pybtex(x,y,z)# Test that the function works when pybtex is availablesys.modules["pybtex"]=pybamm.util.have_optional_dependency("pybtex")pybamm.function_using_pybtex(x,y,z)
All code requires testing. We use the unittest package for our tests. (These tests typically just check that the code runs without error, and so, are more debugging than testing in a strict sense. Nevertheless, they are very useful to have!)
We also use pytest along with the nbmake and the pytest-xdist plugins to test the example notebooks.
If you have nox installed, to run unit tests, type
Every new feature should have its own test. To create ones, have a look at the test directory and see if there’s a test for a similar method. Copy-pasting this is a good way to start.
Next, add some simple (and speedy!) tests of your main features. If these run without exceptions that’s a good start! Next, check the output of your methods using any of these assert methods.
The tests are divided into unit tests, whose aim is to check individual bits of code (e.g. discretising a gradient operator, or solving a simple ODE), and integration tests, which check how parts of the program interact as a whole (e.g. solving a full model).
If you want to check integration tests as well as unit tests, type
nox-stests
When you commit anything to PyBaMM, these checks will also be run automatically (see infrastructure).
You may also use an appropriate glob pattern to run all notebooks matching a particular folder or name pattern.
To edit the structure and how the Jupyter notebooks get rendered in the Sphinx documentation (using nbsphinx), install Pandoc on your system, either using conda (through the conda-forge channel)
Often, the code you write won’t pass the tests straight away, at which stage it will become necessary to debug.
The key to successful debugging is to isolate the problem by finding the smallest possible example that causes the bug.
In practice, there are a few tricks to help you to do this, which we give below.
Once you’ve isolated the issue, it’s a good idea to add a unit test that replicates this issue, so that you can easily check whether it’s been fixed, and make sure that it’s easily picked up if it crops up again.
This also means that, if you can’t fix the bug yourself, it will be much easier to ask for help (by opening a bug-report issue).
Run individual test scripts instead of the whole test suite:
pythontests/unit/path/to/test
You can also run an individual test from a particular script, e.g.
If you want to run several, but not all, the tests from a script, you can restrict which tests are run from a particular script by using the skipping decorator:
@unittest.skip("")deftest_bit_of_code(self):...
or by just commenting out all the tests you don’t want to run.
Set break points, either in your IDE or using the Python debugging module. To use the latter, add the following line where you want to set the break point
importipdbipdb.set_trace()
This will start the Python interactive debugger. If you want to be able to use magic commands from ipython, such as %timeit, then set
This will start the debugger at the point where the ValueError was raised, and allow you to investigate further. Sometimes, it is more informative to put the try-except block further up the call stack than exactly where the error is raised.
Warnings. If functions are raising warnings instead of errors, it can be hard to pinpoint where this is coming from. Here, you can use the warnings module to convert warnings to errors:
importwarningswarnings.simplefilter("error")
Then you can use a try-except block, as in a., but with, for example, RuntimeWarning instead of ValueError.
Stepping through the expression tree. Most calls in PyBaMM are operations on expression trees. To view an expression tree in ipython, you can use the render command:
expression_tree.render()
You can then step through the expression tree, using the children attribute, to pinpoint exactly where a bug is coming from. For example, if expression_tree.jac(y) is failing, you can check expression_tree.children[0].jac(y), then expression_tree.children[0].children[0].jac(y), etc.
To isolate whether a bug is in a model, its Jacobian or its simplified version, you can set the use_jacobian and/or use_simplify attributes of the model to False (they are both True by default for most models).
If a model isn’t giving the answer you expect, you can try comparing it to other models. For example, you can investigate parameter limits in which two models should give the same answer by setting some parameters to be small or zero. The StandardOutputComparison class can be used to compare some standard outputs from battery models.
To get more information about what is going on under the hood, and hence understand what is causing the bug, you can set the logging level to DEBUG by adding the following line to your test or script:
pybamm.set_logging_level("DEBUG")
In models that inherit from pybamm.BaseBatteryModel (i.e. any battery model), you can use self.process_parameters_and_discretise to process a symbol and see what it will look like.
as above, and then use some of the profiling tools. In order of increasing detail:
Simple timer. In ipython, the command
%timecommand_to_time()
tells you how long the line command_to_time() takes. You can use %timeit instead to run the command several times and obtain more accurate timings.
Simple profiler. Using %prun instead of %time will give a brief profiling report 3. Detailed profiler. You can install the detailed profiler snakeviz through pip:
pipinstallsnakeviz
and then, in ipython, run
%load_extsnakeviz%snakevizcommand_to_time()
This will open a window in your browser with detailed profiling information.
First and foremost, every method and every class should have a docstring that describes in plain terms what it does, and what the expected input and output is.
These docstrings can be fairly simple, but can also make use of reStructuredText, a markup language designed specifically for writing technical documentation. For example, you can link to other classes and methods by writing :class:`pybamm.Model` and :meth:`run()`.
In addition, we write a (very) small bit of documentation in separate reStructuredText files in the docs directory. Most of what these files do is simply import docstrings from the source code. But they also do things like add tables and indexes. If you’ve added a new class to a module, search the docs directory for that module’s .rst file and add your class (in alphabetical order) to its index. If you’ve added a whole new module, copy-paste another module’s file and add a link to your new file in the appropriate index.rst file.
Using Sphinx the documentation in docs can be converted to HTML, PDF, and other formats. In particular, we use it to generate the documentation on http://docs.pybamm.org/
To test and debug the documentation, it’s best to build it locally. To do this, navigate to your PyBaMM directory in a console, and then type (on GNU/Linux, macOS, and Windows):
nox-sdocs
And then visit the webpage served at http://127.0.0.1:8000. Each time a change to the documentation source is detected, the HTML is rebuilt and the browser automatically reloaded. In CI, the docs are built and tested using the docs session in the noxfile.py file with warnings turned into errors, to fail the build. The warnings can be removed or ignored by adding the appropriate warning identifier to the suppress_warnings list in docs/conf.py.
Major PyBaMM features are showcased in Jupyter notebooks stored in the docs/source/examples directory. Which features are “major” is of course wholly subjective, so please discuss on GitHub first!
All example notebooks should be listed in docs/source/examples/index.rst. Please follow the (naming and writing) style of existing notebooks where possible.
We aim to recognize all contributions by automatically generating citations to the relevant papers on which different parts of the code are built.
These will change depending on what models and solvers you use.
Adding the command
pybamm.print_citations()
to the end of a script will print all citations that were used by that script. This will print BibTeX information to the terminal; passing a filename to print_citations will print the BibTeX information to the specified file instead.
When you contribute code to PyBaMM, you can add your own papers that you would like to be cited if that code is used. First, add the BibTeX for your paper to CITATIONS.bib. Then, add the line
Installation of PyBaMM and its dependencies is handled via pip and setuptools. It uses CMake to compile C++ extensions using pybind11 and casadi. The installation process is described in detail in the source installation page and is configured through the CMakeLists.txt file.
Configuration files:
setup.pypyproject.tomlMANIFEST.in
Note: MANIFEST.in is used to include and exclude non-Python files and auxiliary package data for PyBaMM when distributing it. If a file is not included in MANIFEST.in, it will not be included in the source distribution (SDist) and subsequently not be included in the binary distribution (wheel).
Each change pushed to the PyBaMM GitHub repository will trigger the test and benchmark suites to be run, using GitHub Actions.
Tests are run for different operating systems, and for all Python versions officially supported by PyBaMM. If you opened a Pull Request, feedback is directly available on the corresponding page. If all tests pass, a green tick will be displayed next to the corresponding test run. If one or more test(s) fail, a red cross will be displayed instead.
Similarly, the benchmark suite is automatically run for the most recently pushed commit. Benchmark results are compared to the results available for the latest commit on the develop branch. Should any significant performance regression be found, a red cross will be displayed next to the benchmark run.
In all cases, more details can be obtained by clicking on a specific run.
Configuration files for various GitHub actions workflow can be found in .github/worklfows.
Code coverage (how much of our code is actually seen by the (linux) unit tests) is tested using Codecov, a report is visible on https://codecov.io/gh/pybamm-team/PyBaMM.
GitHub does some magic with particular filenames. In particular:
The first page people see when they go to our GitHub page displays the contents of README.md, which is written in the Markdown format. Some guidelines can be found here.
The license for using PyBaMM is stored in LICENSE, and automatically linked to by GitHub.
This file, CONTRIBUTING.md is recognised as the contribution guidelines and a link is automatically displayed when new issues or pull requests are created.
PyBaMM ships with example notebooks that demonstrate how to use it and reveal some of its
functionalities and its inner workings. For more examples, see the Examples section.
Tip
An interactive online version of this notebook is available, which can be
accessed via
Welcome to PyBaMM! In this notebook, we will run your first PyBaMM model in just a few simple lines.
To run through this jupyter notebook simply shift-enter to run the cells. If you are unfamiliar with Jupyter notebooks we recommend checking out this cheat sheet.
We begin by importing the PyBaMM library into this notebook:
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybamm
Note: you may need to restart the kernel to use updated packages.
We now load the model that we wish to run. For this notebook, we choose the Doyle-Fuller-Newman (DFN) model:
[2]:
model=pybamm.lithium_ion.DFN()
We now use this model to create a PyBaMM Simulation, which is used to process and solve the model:
[3]:
sim=pybamm.Simulation(model)
We can then call ‘solve’ on our simulation object to solve the model, passing the window of time to solve for in seconds (here 1 hour):
[4]:
sim.solve([0,3600])
[4]:
<pybamm.solvers.solution.Solution at 0x7f2af3787950>
Finally, we can call ‘plot’ to generate a dynamic plot of the key variables:
[5]:
sim.plot()
In this tutorial, we have solved a model with the inbuilt default settings. However, PyBaMM is designed to be highly customisable. Over the course of the getting started tutorials, we will see how various settings can be changed so that the model is appropriate for your situation.
In Tutorial 2 we cover how to simulate and compare different models.
If you write a paper that uses PyBaMM, we would be grateful if you could cite the papers relevant to your code. These will change depending on what models and solvers you use. To find out which papers you should cite, you can run:
[6]:
pybamm.print_citations()
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[5] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). ECSarXiv. February, 2020. doi:10.1149/osf.io/67ckj.
Alternatively, you can print the citations in BibTeX format by running
pybamm.print_citations(output_format="bibtex")
In both cases, you can pass the extra argument filename to store the citations into a file.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In Tutorial 1, we saw how to run a PyBaMM simulation of the DFN model. However, PyBaMM includes other standard electrochemical models such as the Single Particle Model (SPM) and the Single Particle Model with electrolyte (SPMe). In this tutorial, we will see how to simulate and compare these three models.
Again, the first step is to import the pybamm library into the notebook:
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybamm
Note: you may need to restart the kernel to use updated packages.
We start creating a list of all the models we wish to solve
We can now pass our list of simulations to the dynamic plot method, which will plot the different outputs in the same figure
[4]:
pybamm.dynamic_plot(sims,time_unit="seconds")
[4]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7fab1bee6490>
In this tutorial we have seen how easy it is to run and compare different electrochemical models. In Tutorial 3 we show how to create different plots using PyBaMM’s built-in plotting capability.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[5] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). ECSarXiv. February, 2020. doi:10.1149/osf.io/67ckj.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In Tutorial 2, we made use of PyBaMM’s automatic plotting function when comparing models. This gave a good quick overview of many of the key variables in the model. However, by passing in just a few arguments it is easy to plot any of the many other variables that may be of interest to you. We start by building and solving a model as before:
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportmatplotlib.pyplotaspltmodel_dfn=pybamm.lithium_ion.DFN()sim_dfn=pybamm.Simulation(model_dfn)sim_dfn.solve([0,3600])
Note: you may need to restart the kernel to use updated packages.
[1]:
<pybamm.solvers.solution.Solution at 0x139a534f0>
We now want to plot a selection of the model variables. To see a full list of the available variables just type:
[2]:
model_dfn.variable_names()
[2]:
['Time [s]',
'Time [min]',
'Time [h]',
'x [m]',
'x_n [m]',
'x_s [m]',
'x_p [m]',
'r_n [m]',
'r_p [m]',
'Current variable [A]',
'Total current density [A.m-2]',
'Current [A]',
'C-rate',
'Discharge capacity [A.h]',
'Discharge energy [W.h]',
'Throughput energy [W.h]',
'Throughput capacity [A.h]',
'Porosity',
'Negative electrode porosity',
'X-averaged negative electrode porosity',
'Separator porosity',
'X-averaged separator porosity',
'Positive electrode porosity',
'X-averaged positive electrode porosity',
'Porosity change',
'Negative electrode porosity change [s-1]',
'X-averaged negative electrode porosity change [s-1]',
'Separator porosity change [s-1]',
'X-averaged separator porosity change [s-1]',
'Positive electrode porosity change [s-1]',
'X-averaged positive electrode porosity change [s-1]',
'Negative electrode interface utilisation variable',
'X-averaged negative electrode interface utilisation variable',
'Negative electrode interface utilisation',
'X-averaged negative electrode interface utilisation',
'Positive electrode interface utilisation variable',
'X-averaged positive electrode interface utilisation variable',
'Positive electrode interface utilisation',
'X-averaged positive electrode interface utilisation',
'Negative particle crack length [m]',
'X-averaged negative particle crack length [m]',
'Negative particle cracking rate [m.s-1]',
'X-averaged negative particle cracking rate [m.s-1]',
'Positive particle crack length [m]',
'X-averaged positive particle crack length [m]',
'Positive particle cracking rate [m.s-1]',
'X-averaged positive particle cracking rate [m.s-1]',
'Negative electrode active material volume fraction',
'X-averaged negative electrode active material volume fraction',
'Negative electrode capacity [A.h]',
'Negative particle radius',
'Negative particle radius [m]',
'X-averaged negative particle radius [m]',
'Negative electrode surface area to volume ratio [m-1]',
'X-averaged negative electrode surface area to volume ratio [m-1]',
'Negative electrode active material volume fraction change [s-1]',
'X-averaged negative electrode active material volume fraction change [s-1]',
'Loss of lithium due to loss of active material in negative electrode [mol]',
'Positive electrode active material volume fraction',
'X-averaged positive electrode active material volume fraction',
'Positive electrode capacity [A.h]',
'Positive particle radius',
'Positive particle radius [m]',
'X-averaged positive particle radius [m]',
'Positive electrode surface area to volume ratio [m-1]',
'X-averaged positive electrode surface area to volume ratio [m-1]',
'Positive electrode active material volume fraction change [s-1]',
'X-averaged positive electrode active material volume fraction change [s-1]',
'Loss of lithium due to loss of active material in positive electrode [mol]',
'Separator pressure [Pa]',
'X-averaged separator pressure [Pa]',
'negative electrode transverse volume-averaged velocity [m.s-1]',
'X-averaged negative electrode transverse volume-averaged velocity [m.s-1]',
'separator transverse volume-averaged velocity [m.s-1]',
'X-averaged separator transverse volume-averaged velocity [m.s-1]',
'positive electrode transverse volume-averaged velocity [m.s-1]',
'X-averaged positive electrode transverse volume-averaged velocity [m.s-1]',
'Transverse volume-averaged velocity [m.s-1]',
'negative electrode transverse volume-averaged acceleration [m.s-2]',
'X-averaged negative electrode transverse volume-averaged acceleration [m.s-2]',
'separator transverse volume-averaged acceleration [m.s-2]',
'X-averaged separator transverse volume-averaged acceleration [m.s-2]',
'positive electrode transverse volume-averaged acceleration [m.s-2]',
'X-averaged positive electrode transverse volume-averaged acceleration [m.s-2]',
'Transverse volume-averaged acceleration [m.s-2]',
'Negative electrode volume-averaged velocity [m.s-1]',
'Negative electrode volume-averaged acceleration [m.s-2]',
'X-averaged negative electrode volume-averaged acceleration [m.s-2]',
'Negative electrode pressure [Pa]',
'X-averaged negative electrode pressure [Pa]',
'Positive electrode volume-averaged velocity [m.s-1]',
'Positive electrode volume-averaged acceleration [m.s-2]',
'X-averaged positive electrode volume-averaged acceleration [m.s-2]',
'Positive electrode pressure [Pa]',
'X-averaged positive electrode pressure [Pa]',
'Negative particle stoichiometry',
'Negative particle concentration',
'Negative particle concentration [mol.m-3]',
'X-averaged negative particle concentration',
'X-averaged negative particle concentration [mol.m-3]',
'R-averaged negative particle concentration',
'R-averaged negative particle concentration [mol.m-3]',
'Average negative particle concentration',
'Average negative particle concentration [mol.m-3]',
'Negative particle surface stoichiometry',
'Negative particle surface concentration',
'Negative particle surface concentration [mol.m-3]',
'X-averaged negative particle surface concentration',
'X-averaged negative particle surface concentration [mol.m-3]',
'Negative electrode extent of lithiation',
'X-averaged negative electrode extent of lithiation',
'Minimum negative particle concentration',
'Maximum negative particle concentration',
'Minimum negative particle concentration [mol.m-3]',
'Maximum negative particle concentration [mol.m-3]',
'Minimum negative particle surface concentration',
'Maximum negative particle surface concentration',
'Minimum negative particle surface concentration [mol.m-3]',
'Maximum negative particle surface concentration [mol.m-3]',
'Positive particle stoichiometry',
'Positive particle concentration',
'Positive particle concentration [mol.m-3]',
'X-averaged positive particle concentration',
'X-averaged positive particle concentration [mol.m-3]',
'R-averaged positive particle concentration',
'R-averaged positive particle concentration [mol.m-3]',
'Average positive particle concentration',
'Average positive particle concentration [mol.m-3]',
'Positive particle surface stoichiometry',
'Positive particle surface concentration',
'Positive particle surface concentration [mol.m-3]',
'X-averaged positive particle surface concentration',
'X-averaged positive particle surface concentration [mol.m-3]',
'Positive electrode extent of lithiation',
'X-averaged positive electrode extent of lithiation',
'Minimum positive particle concentration',
'Maximum positive particle concentration',
'Minimum positive particle concentration [mol.m-3]',
'Maximum positive particle concentration [mol.m-3]',
'Minimum positive particle surface concentration',
'Maximum positive particle surface concentration',
'Minimum positive particle surface concentration [mol.m-3]',
'Maximum positive particle surface concentration [mol.m-3]',
'Negative electrode potential [V]',
'X-averaged negative electrode potential [V]',
'Negative electrode ohmic losses [V]',
'X-averaged negative electrode ohmic losses [V]',
'Gradient of negative electrode potential [V.m-1]',
'Positive electrode potential [V]',
'X-averaged positive electrode potential [V]',
'Positive electrode ohmic losses [V]',
'X-averaged positive electrode ohmic losses [V]',
'Gradient of positive electrode potential [V.m-1]',
'Porosity times concentration [mol.m-3]',
'Negative electrode porosity times concentration [mol.m-3]',
'Separator porosity times concentration [mol.m-3]',
'Positive electrode porosity times concentration [mol.m-3]',
'Total lithium in electrolyte [mol]',
'Electrolyte potential [V]',
'X-averaged electrolyte potential [V]',
'X-averaged electrolyte overpotential [V]',
'Gradient of electrolyte potential [V.m-1]',
'Negative electrolyte potential [V]',
'X-averaged negative electrolyte potential [V]',
'Gradient of negative electrolyte potential [V.m-1]',
'Separator electrolyte potential [V]',
'X-averaged separator electrolyte potential [V]',
'Gradient of separator electrolyte potential [V.m-1]',
'Positive electrolyte potential [V]',
'X-averaged positive electrolyte potential [V]',
'Gradient of positive electrolyte potential [V.m-1]',
'Ambient temperature [K]',
'Cell temperature [K]',
'Negative current collector temperature [K]',
'Positive current collector temperature [K]',
'X-averaged cell temperature [K]',
'Volume-averaged cell temperature [K]',
'Negative electrode temperature [K]',
'X-averaged negative electrode temperature [K]',
'Separator temperature [K]',
'X-averaged separator temperature [K]',
'Positive electrode temperature [K]',
'X-averaged positive electrode temperature [K]',
'Ambient temperature [C]',
'Cell temperature [C]',
'Negative current collector temperature [C]',
'Positive current collector temperature [C]',
'X-averaged cell temperature [C]',
'Volume-averaged cell temperature [C]',
'Negative electrode temperature [C]',
'X-averaged negative electrode temperature [C]',
'Separator temperature [C]',
'X-averaged separator temperature [C]',
'Positive electrode temperature [C]',
'X-averaged positive electrode temperature [C]',
'Negative current collector potential [V]',
'Inner SEI thickness [m]',
'Outer SEI thickness [m]',
'X-averaged inner SEI thickness [m]',
'X-averaged outer SEI thickness [m]',
'SEI [m]',
'Total SEI thickness [m]',
'X-averaged SEI thickness [m]',
'X-averaged total SEI thickness [m]',
'X-averaged negative electrode resistance [Ohm.m2]',
'Inner SEI interfacial current density [A.m-2]',
'X-averaged inner SEI interfacial current density [A.m-2]',
'Outer SEI interfacial current density [A.m-2]',
'X-averaged outer SEI interfacial current density [A.m-2]',
'SEI interfacial current density [A.m-2]',
'X-averaged SEI interfacial current density [A.m-2]',
'Inner SEI on cracks thickness [m]',
'Outer SEI on cracks thickness [m]',
'X-averaged inner SEI on cracks thickness [m]',
'X-averaged outer SEI on cracks thickness [m]',
'SEI on cracks [m]',
'Total SEI on cracks thickness [m]',
'X-averaged SEI on cracks thickness [m]',
'X-averaged total SEI on cracks thickness [m]',
'Inner SEI on cracks interfacial current density [A.m-2]',
'X-averaged inner SEI on cracks interfacial current density [A.m-2]',
'Outer SEI on cracks interfacial current density [A.m-2]',
'X-averaged outer SEI on cracks interfacial current density [A.m-2]',
'SEI on cracks interfacial current density [A.m-2]',
'X-averaged SEI on cracks interfacial current density [A.m-2]',
'Lithium plating concentration [mol.m-3]',
'X-averaged lithium plating concentration [mol.m-3]',
'Dead lithium concentration [mol.m-3]',
'X-averaged dead lithium concentration [mol.m-3]',
'Lithium plating thickness [m]',
'X-averaged lithium plating thickness [m]',
'Dead lithium thickness [m]',
'X-averaged dead lithium thickness [m]',
'Loss of lithium to lithium plating [mol]',
'Loss of capacity to lithium plating [A.h]',
'Negative electrode lithium plating reaction overpotential [V]',
'X-averaged negative electrode lithium plating reaction overpotential [V]',
'Lithium plating interfacial current density [A.m-2]',
'X-averaged lithium plating interfacial current density [A.m-2]',
'Negative crack surface to volume ratio [m-1]',
'Negative electrode roughness ratio',
'X-averaged negative electrode roughness ratio',
'Positive crack surface to volume ratio [m-1]',
'Positive electrode roughness ratio',
'X-averaged positive electrode roughness ratio',
'Electrolyte transport efficiency',
'Negative electrolyte transport efficiency',
'X-averaged negative electrolyte transport efficiency',
'Separator electrolyte transport efficiency',
'X-averaged separator electrolyte transport efficiency',
'Positive electrolyte transport efficiency',
'X-averaged positive electrolyte transport efficiency',
'Electrode transport efficiency',
'Negative electrode transport efficiency',
'X-averaged negative electrode transport efficiency',
'Separator electrode transport efficiency',
'X-averaged separator electrode transport efficiency',
'Positive electrode transport efficiency',
'X-averaged positive electrode transport efficiency',
'Separator volume-averaged velocity [m.s-1]',
'Separator volume-averaged acceleration [m.s-2]',
'X-averaged separator volume-averaged acceleration [m.s-2]',
'Volume-averaged velocity [m.s-1]',
'Volume-averaged acceleration [m.s-1]',
'X-averaged volume-averaged acceleration [m.s-1]',
'Pressure [Pa]',
'Negative electrode open-circuit potential [V]',
'X-averaged negative electrode open-circuit potential [V]',
'Negative electrode entropic change [V.K-1]',
'X-averaged negative electrode entropic change [V.K-1]',
'Positive electrode open-circuit potential [V]',
'X-averaged positive electrode open-circuit potential [V]',
'Positive electrode entropic change [V.K-1]',
'X-averaged positive electrode entropic change [V.K-1]',
'Negative electrode effective conductivity',
'Negative electrode current density [A.m-2]',
'Positive electrode effective conductivity',
'Positive electrode current density [A.m-2]',
'Electrode current density [A.m-2]',
'Positive current collector potential [V]',
'Local voltage [V]',
'Voltage [V]',
'Contact overpotential [V]',
'Electrolyte concentration concatenation [mol.m-3]',
'Negative electrolyte concentration [mol.m-3]',
'X-averaged negative electrolyte concentration [mol.m-3]',
'Separator electrolyte concentration [mol.m-3]',
'X-averaged separator electrolyte concentration [mol.m-3]',
'Positive electrolyte concentration [mol.m-3]',
'X-averaged positive electrolyte concentration [mol.m-3]',
'Negative electrolyte concentration',
'Negative electrolyte concentration [Molar]',
'X-averaged negative electrolyte concentration',
'X-averaged negative electrolyte concentration [Molar]',
'Separator electrolyte concentration',
'Separator electrolyte concentration [Molar]',
'X-averaged separator electrolyte concentration',
'X-averaged separator electrolyte concentration [Molar]',
'Positive electrolyte concentration',
'Positive electrolyte concentration [Molar]',
'X-averaged positive electrolyte concentration',
'X-averaged positive electrolyte concentration [Molar]',
'Electrolyte concentration [mol.m-3]',
'X-averaged electrolyte concentration [mol.m-3]',
'Electrolyte concentration',
'Electrolyte concentration [Molar]',
'X-averaged electrolyte concentration',
'X-averaged electrolyte concentration [Molar]',
'Electrolyte current density [A.m-2]',
'X-averaged concentration overpotential [V]',
'X-averaged electrolyte ohmic losses [V]',
'Negative electrode surface potential difference [V]',
'X-averaged negative electrode surface potential difference [V]',
'Positive electrode surface potential difference [V]',
'X-averaged positive electrode surface potential difference [V]',
'Ohmic heating [W.m-3]',
'X-averaged Ohmic heating [W.m-3]',
'Volume-averaged Ohmic heating [W.m-3]',
'Irreversible electrochemical heating [W.m-3]',
'X-averaged irreversible electrochemical heating [W.m-3]',
'Volume-averaged irreversible electrochemical heating [W.m-3]',
'Reversible heating [W.m-3]',
'X-averaged reversible heating [W.m-3]',
'Volume-averaged reversible heating [W.m-3]',
'Total heating [W.m-3]',
'X-averaged total heating [W.m-3]',
'Volume-averaged total heating [W.m-3]',
'Current collector current density [A.m-2]',
'Inner SEI concentration [mol.m-3]',
'X-averaged inner SEI concentration [mol.m-3]',
'Outer SEI concentration [mol.m-3]',
'X-averaged outer SEI concentration [mol.m-3]',
'SEI concentration [mol.m-3]',
'X-averaged SEI concentration [mol.m-3]',
'Loss of lithium to SEI [mol]',
'Loss of capacity to SEI [A.h]',
'X-averaged negative electrode SEI interfacial current density [A.m-2]',
'Negative electrode SEI interfacial current density [A.m-2]',
'Positive electrode SEI interfacial current density [A.m-2]',
'X-averaged positive electrode SEI volumetric interfacial current density [A.m-2]',
'Positive electrode SEI volumetric interfacial current density [A.m-3]',
'Negative electrode SEI volumetric interfacial current density [A.m-3]',
'X-averaged negative electrode SEI volumetric interfacial current density [A.m-3]',
'Inner SEI on cracks concentration [mol.m-3]',
'X-averaged inner SEI on cracks concentration [mol.m-3]',
'Outer SEI on cracks concentration [mol.m-3]',
'X-averaged outer SEI on cracks concentration [mol.m-3]',
'SEI on cracks concentration [mol.m-3]',
'X-averaged SEI on cracks concentration [mol.m-3]',
'Loss of lithium to SEI on cracks [mol]',
'Loss of capacity to SEI on cracks [A.h]',
'X-averaged negative electrode SEI on cracks interfacial current density [A.m-2]',
'Negative electrode SEI on cracks interfacial current density [A.m-2]',
'Positive electrode SEI on cracks interfacial current density [A.m-2]',
'X-averaged positive electrode SEI on cracks volumetric interfacial current density [A.m-2]',
'Positive electrode SEI on cracks volumetric interfacial current density [A.m-3]',
'Negative electrode SEI on cracks volumetric interfacial current density [A.m-3]',
'X-averaged negative electrode SEI on cracks volumetric interfacial current density [A.m-3]',
'Negative electrode lithium plating interfacial current density [A.m-2]',
'X-averaged negative electrode lithium plating interfacial current density [A.m-2]',
'Lithium plating volumetric interfacial current density [A.m-3]',
'X-averaged lithium plating volumetric interfacial current density [A.m-3]',
'X-averaged positive electrode lithium plating interfacial current density [A.m-2]',
'X-averaged positive electrode lithium plating volumetric interfacial current density [A.m-3]',
'Positive electrode lithium plating interfacial current density [A.m-2]',
'Positive electrode lithium plating volumetric interfacial current density [A.m-3]',
'Negative electrode lithium plating volumetric interfacial current density [A.m-3]',
'X-averaged negative electrode lithium plating volumetric interfacial current density [A.m-3]',
'Negative electrode interfacial current density [A.m-2]',
'X-averaged negative electrode interfacial current density [A.m-2]',
'X-averaged negative electrode total interfacial current density [A.m-2]',
'X-averaged negative electrode total volumetric interfacial current density [A.m-3]',
'Negative electrode exchange current density [A.m-2]',
'X-averaged negative electrode exchange current density [A.m-2]',
'Negative electrode reaction overpotential [V]',
'X-averaged negative electrode reaction overpotential [V]',
'Negative electrode volumetric interfacial current density [A.m-3]',
'X-averaged negative electrode volumetric interfacial current density [A.m-3]',
'SEI film overpotential [V]',
'X-averaged SEI film overpotential [V]',
'Positive electrode interfacial current density [A.m-2]',
'X-averaged positive electrode interfacial current density [A.m-2]',
'X-averaged positive electrode total interfacial current density [A.m-2]',
'X-averaged positive electrode total volumetric interfacial current density [A.m-3]',
'Positive electrode exchange current density [A.m-2]',
'X-averaged positive electrode exchange current density [A.m-2]',
'Positive electrode reaction overpotential [V]',
'X-averaged positive electrode reaction overpotential [V]',
'Positive electrode volumetric interfacial current density [A.m-3]',
'X-averaged positive electrode volumetric interfacial current density [A.m-3]',
'Negative particle rhs [mol.m-3.s-1]',
'Negative particle bc [mol.m-2]',
'Negative particle effective diffusivity [m2.s-1]',
'X-averaged negative particle effective diffusivity [m2.s-1]',
'Negative particle flux [mol.m-2.s-1]',
'Negative electrode stoichiometry',
'Negative electrode volume-averaged concentration',
'Negative electrode volume-averaged concentration [mol.m-3]',
'Total lithium in primary phase in negative electrode [mol]',
'Positive particle rhs [mol.m-3.s-1]',
'Positive particle bc [mol.m-2]',
'Positive particle effective diffusivity [m2.s-1]',
'X-averaged positive particle effective diffusivity [m2.s-1]',
'Positive particle flux [mol.m-2.s-1]',
'Positive electrode stoichiometry',
'Positive electrode volume-averaged concentration',
'Positive electrode volume-averaged concentration [mol.m-3]',
'Total lithium in primary phase in positive electrode [mol]',
'Electrolyte flux [mol.m-2.s-1]',
'Electrolyte diffusion flux [mol.m-2.s-1]',
'Electrolyte migration flux [mol.m-2.s-1]',
'Electrolyte convection flux [mol.m-2.s-1]',
'Sum of negative electrode electrolyte reaction source terms [A.m-3]',
'Sum of x-averaged negative electrode electrolyte reaction source terms [A.m-3]',
'Sum of negative electrode volumetric interfacial current densities [A.m-3]',
'Sum of x-averaged negative electrode volumetric interfacial current densities [A.m-3]',
'Sum of positive electrode electrolyte reaction source terms [A.m-3]',
'Sum of x-averaged positive electrode electrolyte reaction source terms [A.m-3]',
'Sum of positive electrode volumetric interfacial current densities [A.m-3]',
'Sum of x-averaged positive electrode volumetric interfacial current densities [A.m-3]',
'Interfacial current density [A.m-2]',
'Exchange current density [A.m-2]',
'Sum of volumetric interfacial current densities [A.m-3]',
'Sum of electrolyte reaction source terms [A.m-3]',
'X-averaged open-circuit voltage [V]',
'Measured open-circuit voltage [V]',
'X-averaged reaction overpotential [V]',
'X-averaged solid phase ohmic losses [V]',
'X-averaged battery open-circuit voltage [V]',
'Measured battery open-circuit voltage [V]',
'X-averaged battery reaction overpotential [V]',
'X-averaged battery solid phase ohmic losses [V]',
'X-averaged battery electrolyte ohmic losses [V]',
'X-averaged battery concentration overpotential [V]',
'Battery voltage [V]',
'Change in measured open-circuit voltage [V]',
'Local ECM resistance [Ohm]',
'Terminal power [W]',
'Power [W]',
'Resistance [Ohm]',
'Total lithium in negative electrode [mol]',
'LAM_ne [%]',
'Loss of active material in negative electrode [%]',
'Total lithium in positive electrode [mol]',
'LAM_pe [%]',
'Loss of active material in positive electrode [%]',
'LLI [%]',
'Loss of lithium inventory [%]',
'Loss of lithium inventory, including electrolyte [%]',
'Total lithium [mol]',
'Total lithium in particles [mol]',
'Total lithium capacity [A.h]',
'Total lithium capacity in particles [A.h]',
'Total lithium lost [mol]',
'Total lithium lost from particles [mol]',
'Total lithium lost from electrolyte [mol]',
'Total lithium lost to side reactions [mol]',
'Total capacity lost to side reactions [A.h]']
There are a lot of variables. You can also search the list of variables for a particular string (e.g. “electrolyte”)
[3]:
model_dfn.variables.search("electrolyte")
Electrolyte concentration
Electrolyte concentration [Molar]
Electrolyte concentration [mol.m-3]
Electrolyte concentration concatenation [mol.m-3]
Electrolyte convection flux [mol.m-2.s-1]
Electrolyte current density [A.m-2]
Electrolyte diffusion flux [mol.m-2.s-1]
Electrolyte flux [mol.m-2.s-1]
Electrolyte migration flux [mol.m-2.s-1]
Electrolyte potential [V]
Electrolyte transport efficiency
Gradient of electrolyte potential [V.m-1]
Gradient of negative electrolyte potential [V.m-1]
Gradient of positive electrolyte potential [V.m-1]
Gradient of separator electrolyte potential [V.m-1]
Loss of lithium inventory, including electrolyte [%]
Negative electrolyte concentration
Negative electrolyte concentration [Molar]
Negative electrolyte concentration [mol.m-3]
Negative electrolyte potential [V]
Negative electrolyte transport efficiency
Positive electrolyte concentration
Positive electrolyte concentration [Molar]
Positive electrolyte concentration [mol.m-3]
Positive electrolyte potential [V]
Positive electrolyte transport efficiency
Separator electrolyte concentration
Separator electrolyte concentration [Molar]
Separator electrolyte concentration [mol.m-3]
Separator electrolyte potential [V]
Separator electrolyte transport efficiency
Sum of electrolyte reaction source terms [A.m-3]
Sum of negative electrode electrolyte reaction source terms [A.m-3]
Sum of positive electrode electrolyte reaction source terms [A.m-3]
Sum of x-averaged negative electrode electrolyte reaction source terms [A.m-3]
Sum of x-averaged positive electrode electrolyte reaction source terms [A.m-3]
Total lithium in electrolyte [mol]
Total lithium lost from electrolyte [mol]
X-averaged battery electrolyte ohmic losses [V]
X-averaged electrolyte concentration
X-averaged electrolyte concentration [Molar]
X-averaged electrolyte concentration [mol.m-3]
X-averaged electrolyte ohmic losses [V]
X-averaged electrolyte overpotential [V]
X-averaged electrolyte potential [V]
X-averaged negative electrolyte concentration
X-averaged negative electrolyte concentration [Molar]
X-averaged negative electrolyte concentration [mol.m-3]
X-averaged negative electrolyte potential [V]
X-averaged negative electrolyte transport efficiency
X-averaged positive electrolyte concentration
X-averaged positive electrolyte concentration [Molar]
X-averaged positive electrolyte concentration [mol.m-3]
X-averaged positive electrolyte potential [V]
X-averaged positive electrolyte transport efficiency
X-averaged separator electrolyte concentration
X-averaged separator electrolyte concentration [Molar]
X-averaged separator electrolyte concentration [mol.m-3]
X-averaged separator electrolyte potential [V]
X-averaged separator electrolyte transport efficiency
We have tried to make variables names fairly self explanatory.
As a first example, we choose to plot the voltage. We add this to a list and then pass this list to the plot method of our simulation:
<pybamm.plotting.quick_plot.QuickPlot at 0x13a8fbe80>
You can also plot multiple variables on the same plot by nesting lists
[6]:
sim_dfn.plot([["Electrode current density [A.m-2]","Electrolyte current density [A.m-2]"],"Voltage [V]",])
[6]:
<pybamm.plotting.quick_plot.QuickPlot at 0x13aaadd60>
[7]:
sim_dfn.plot()
[7]:
<pybamm.plotting.quick_plot.QuickPlot at 0x13ab92e20>
For plotting the voltage components you can use the plot_votage_components function
[8]:
pybamm.plot_voltage_components(sim_dfn.solution)
[8]:
(<Figure size 640x480 with 1 Axes>, <AxesSubplot: xlabel='Time [h]'>)
And with a few modifications (by creating subplots and by providing the axes on which the voltage components have to be plotted), it can also be used to compare the voltage components of different simulations
[9]:
# simulating and solving Single Particle Modelmodel_spm=pybamm.lithium_ion.SPM()sim_spm=pybamm.Simulation(model_spm)sim_spm.solve([0,3700])# comparing voltage components for Doyle-Fuller-Newman model and Single Particle Modelfig,axes=plt.subplots(1,2,figsize=(15,6),sharey=True)pybamm.plot_voltage_components(sim_dfn.solution,ax=axes.flat[0])pybamm.plot_voltage_components(sim_spm.solution,ax=axes.flat[1])axes.flat[0].set_title("Doyle-Fuller-Newman Model")axes.flat[1].set_title("Single Particle Model")plt.show()
In this tutorial we have seen how to use the plotting functionality in PyBaMM.
In Tutorial 4 we show how to change parameter values.
[1] Weilong Ai, Ludwig Kraft, Johannes Sturm, Andreas Jossen, and Billy Wu. Electrochemical thermal-mechanical modelling of stress inhomogeneity in lithium-ion pouch cells. Journal of The Electrochemical Society, 167(1):013512, 2019. doi:10.1149/2.0122001JES.
[2] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[3] Rutooj Deshpande, Mark Verbrugge, Yang-Tse Cheng, John Wang, and Ping Liu. Battery cycle life prediction with coupled chemical degradation and fatigue mechanics. Journal of the Electrochemical Society, 159(10):A1730, 2012. doi:10.1149/2.049210jes.
[4] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[5] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[6] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[7] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In Tutorial 1 and Tutorial 2, we saw how to run a PyBaMM model with all the default settings. However, PyBaMM also allows you to tweak these settings for your application. In this tutorial, we will see how to change the parameters in PyBaMM.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportosos.chdir(pybamm.__path__[0]+"/..")
WARNING: You are using pip version 22.0.4; however, version 22.3.1 is available.
You should consider upgrading via the '/home/siegeljb/Documents/PyBaMM_Master/PyBaMM/.tox/dev/bin/python3.9 -m pip install --upgrade pip' command.Note: you may need to restart the kernel to use updated packages.
We often want to quickly change a small number of parameter values to investigate how the behaviour or the battery changes. In such cases, we can change parameter values without having to leave the notebook or script you are working in.
We start initialising the model and the parameter values
In this example we will change the current to 10 A
[11]:
parameter_values["Current function [A]"]=10parameter_values["Open-circuit voltage at 100% SOC [V]"]=3.4parameter_values["Open-circuit voltage at 0% SOC [V]"]=3.0
Now we just need to run the simulation with the new parameter values
You can implement drive cycles importing the dataset and creating an interpolant to pass as the current function.
[ ]:
importpandasaspd# needed to read the csv data file# Import drive cycle from filedrive_cycle=pd.read_csv("pybamm/input/drive_cycles/US06.csv",comment="#",header=None).to_numpy()# Create interpolantcurrent_interpolant=pybamm.Interpolant(drive_cycle[:,0],drive_cycle[:,1],pybamm.t)# Set drive cycleparameter_values["Current function [A]"]=current_interpolant
Note that your drive cycle data can be stored anywhere, you just need to pass the path of the file. Then, again, the model can be solved as usual but notice that now, if t_eval is not specified, the solver will take the time points from the data set.
Alternatively, we can define the current to be an arbitrary function of time
[ ]:
importnumpyasnpdefmy_current(t):returnpybamm.sin(2*np.pi*t/60)parameter_values["Current function [A]"]=my_current
and we can now solve the model again. In this case, we can pass t_eval to the solver to make sure we have enough time points to resolve the function in our output.
[1] Weilong Ai, Ludwig Kraft, Johannes Sturm, Andreas Jossen, and Billy Wu. Electrochemical thermal-mechanical modelling of stress inhomogeneity in lithium-ion pouch cells. Journal of The Electrochemical Society, 167(1):013512, 2019. doi:10.1149/2.0122001JES.
[2] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[3] Chang-Hui Chen, Ferran Brosa Planella, Kieran O'Regan, Dominika Gastol, W. Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.
[4] Rutooj Deshpande, Mark Verbrugge, Yang-Tse Cheng, John Wang, and Ping Liu. Battery cycle life prediction with coupled chemical degradation and fatigue mechanics. Journal of the Electrochemical Society, 159(10):A1730, 2012. doi:10.1149/2.049210jes.
[5] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[6] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[7] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[8] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In Tutorial 4 we saw how to change the parameters, including the applied current. However, in some cases we might want to prescribe a given voltage, a given power or switch between different conditions to simulate experimental setups. We can use the Experiment class for these simulations.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnp
[notice] A new release of pip is available: 23.0.1 -> 23.1.2[notice] To update, run: pip install --upgrade pip
Note: you may need to restart the kernel to use updated packages.
We start defining an experiment, which consists on a set of instructions on how to cycle the battery. For example, we can set the following experiment:
[2]:
experiment=pybamm.Experiment([("Discharge at C/10 for 10 hours or until 3.3 V","Rest for 1 hour","Charge at 1 A until 4.1 V","Hold at 4.1 V until 50 mA","Rest for 1 hour",),]*3)
A cycle is defined by a tuple of operating instructions. In this case, the experiment consists of a cycle of constant current C/10 discharge, a one hour rest, a constant current (1 A) constant voltage (4.1 V) and another one hour rest, all of it repeated three times (notice the * 3).
Then we can choose our model
[3]:
model=pybamm.lithium_ion.DFN()
and create our simulation, passing our experiment using a keyword argument
<pybamm.plotting.quick_plot.QuickPlot at 0x17d7a9490>
As we have seen, experiments allow us to define complex simulations using a very simple syntax. The instructions can be of the form “(Dis)charge at x A/C/W”, “Rest”, or “Hold at x V”. The running time should be a time in seconds, minutes or hours, e.g. “10 seconds”, “3 minutes” or “1 hour”. The stopping conditions should be a circuit state, e.g. “1 A”, “C/50” or “3 V”.
Some examples of experiment instructions are:
"Discharge at 1C for 0.5 hours","Discharge at C/20 for 0.5 hours","Charge at 0.5 C for 45 minutes","Discharge at 1 A for 90 seconds","Charge at 200mA for 45 minutes","Discharge at 1 W for 0.5 hours","Charge at 200 mW for 45 minutes","Rest for 10 minutes","Hold at 1 V for 20 seconds","Charge at 1 C until 4.1V","Hold at 4.1 V until 50 mA","Hold at 3V until C/50",
Additionally, we can use the operators + and * on lists in order to combine and repeat cycles:
[6]:
[("Discharge at 1C for 0.5 hours","Discharge at C/20 for 0.5 hours")]*3+[("Charge at 0.5 C for 45 minutes",)]
[6]:
[('Discharge at 1C for 0.5 hours', 'Discharge at C/20 for 0.5 hours'),
('Discharge at 1C for 0.5 hours', 'Discharge at C/20 for 0.5 hours'),
('Discharge at 1C for 0.5 hours', 'Discharge at C/20 for 0.5 hours'),
('Charge at 0.5 C for 45 minutes',)]
To pass additional arguments such as a period, temperature, or tags, the method pybamm.step.string should be used, for example:
[7]:
pybamm.step.string("Discharge at 1C for 1 hour",period="1 minute",temperature="25oC",tags=["tag1"])
[7]:
_Step(C-rate, 1.0, duration=1 hour, period=1 minute, temperature=25oC, tags=['tag1'], description=Discharge at 1C for 1 hour)
<pybamm.plotting.quick_plot.QuickPlot at 0x2885170d0>
For a drive cycle, the duration is until the final time provided and the period is the smallest time step. For best results, we recommend using a constant time step size.
In this notebook we have seen how to use the Experiment class to run simulations of more complex operating conditions. In Tutorial 6 we will see how to manage the outputs of the simulation.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[5] Peyman Mohtat, Suhak Lee, Jason B Siegel, and Anna G Stefanopoulou. Towards better estimability of electrode-specific state of health: decoding the cell expansion. Journal of Power Sources, 427:101–111, 2019.
[6] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[7] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
[8] Andrew Weng, Jason B Siegel, and Anna Stefanopoulou. Differential voltage analysis for battery manufacturing process control. arXiv preprint arXiv:2303.07088, 2023.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In the previous tutorials we have interacted with the outputs of the simulation via the default dynamic plot. However, usually we need to access the output data to manipulate it or transfer to another software which is the topic of this notebook.
We start by building and solving our model as shown in previous notebooks:
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammmodel=pybamm.lithium_ion.SPMe()sim=pybamm.Simulation(model)sim.solve([0,3600])
Note: you may need to restart the kernel to use updated packages.
[1]:
<pybamm.solvers.solution.Solution at 0x7f996c4a7e90>
In some cases simulations might take a long time to run so it is advisable to save in your computer so it can be analysed later without re-running the simulation. You can save the whole simulation doing:
[7]:
sim.save("SPMe.pkl")
If you now check the root directory of your notebooks you will notice that a new file called "SPMe.pkl" has appeared. We can load the stored simulation doing
[8]:
sim2=pybamm.load("SPMe.pkl")
which allows the same manipulation as the original simulation would allow
[9]:
sim2.plot()
Alternatively, we can just save the solution of the simulation in a similar way
In this notebook we have shown how to extract and store the outputs of PyBaMM’s simulations. Next, in Tutorial 7 we will show how to change the model options.
Before finishing we will remove the data files we saved so that we leave the directory as we found it
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[4] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). ECSarXiv. February, 2020. doi:10.1149/osf.io/67ckj.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In all of the previous tutorials, we have made use of the default forms of the inbuilt models in PyBaMM. However, PyBaMM provides a high-level interface for tweaking these models for your particular application.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybamm
Note: you may need to restart the kernel to use updated packages.
In this tutorial, we add a thermal model to the SPMe. From the documentation, we see that we have a choice of either a ‘x-full’ thermal model or a number of different lumped thermal models. For a deeper look at the thermal models see the thermal models notebook. We choose the full thermal model, which solves the spatially-dependent heat equation on our battery
geometry, and couples the temperature with the electrochemistry. We set the model options by creating a Python dictionary:
[2]:
options={"thermal":"x-full"}
and passing it to the model. Then, the model can be solved as shown in previous notebooks. We also increase the current to amplify the thermal effects:
[3]:
model=pybamm.lithium_ion.SPMe(options=options)# loading in optionssim=pybamm.Simulation(model)sim.solve([0,3600])
[3]:
<pybamm.solvers.solution.Solution at 0x139461520>
We now plot the cell temperature and the total heating by passing these variables to the plot method as we saw in Tutorial 3:
[4]:
sim.plot(["Cell temperature [K]","Total heating [W.m-3]","Current [A]","Voltage [V]"])
[4]:
<pybamm.plotting.quick_plot.QuickPlot at 0x139461fd0>
In this tutorial we have seen how to adjust the model options. To see all of the options currently available in PyBaMM, please take a look at the documentation here.
In the next tutorial we show how to change the solver options.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[4] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[5] Robert Timms, Scott G Marquis, Valentin Sulzer, Colin P. Please, and S Jonathan Chapman. Asymptotic Reduction of a Lithium-ion Pouch Cell Model. SIAM Journal on Applied Mathematics, 81(3):765–788, 2021. doi:10.1137/20M1336898.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In Tutorial 7 we saw how to change the model options. In this tutorial we will show how to pass options to the solver.
All models in PyBaMM have a default solver which is typically different depending on whether the model results in a system of ordinary differential equations (ODEs) or differential algebraic equations (DAEs).
One of the most common options you will want to change is the solver tolerances. By default all tolerances are set to \(10^{-6}\). However, depending on your simulation you may find you want to tighten the tolerances to obtain a more accurate solution, or you may want to loosen the tolerances to reduce the solve time. It is good practice to conduct a tolerance study, where you simulate the same problem with a tighter tolerances and compare the results. We do not show how to do this here, but
we give an example of a mesh resolution study in the next tutorial, which is conducted in a similar way.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybamm
Note: you may need to restart the kernel to use updated packages.
Here we will change the absolute and relative tolerances, as well as the “mode” of the CasadiSolver. For a list of all the solver options please consult the documentation.
The CasadiSolver can operate in a number of modes, including “safe” (default) and “fast”. Safe mode performs step-and-check integration and supports event handling (e.g. you can integrate until you hit a certain voltage), and is the recommended for simulations of a full charge or discharge. Fast mode performs direct integration, ignoring events, and is recommended when simulating a drive cycle or other simulation where no events should be triggered.
We’ll solve the DFN with all the default options in both “safe” and “fast” mode and compare the solutions. For both simulations we’ll use \(10^{-3}\) for both the absolute and relative tolerance. For demonstration purposes we’ll change the cut-off voltage to 3.6V so we can observe the different behaviour of the two solver modes.
[2]:
# load model and parametersmodel=pybamm.lithium_ion.DFN()param=model.default_parameter_valuesparam["Lower voltage cut-off [V]"]=3.6# load solverssafe_solver=pybamm.CasadiSolver(atol=1e-3,rtol=1e-3,mode="safe")fast_solver=pybamm.CasadiSolver(atol=1e-3,rtol=1e-3,mode="fast")# create simulationssafe_sim=pybamm.Simulation(model,parameter_values=param,solver=safe_solver)fast_sim=pybamm.Simulation(model,parameter_values=param,solver=fast_solver)# solvesafe_sim.solve([0,3600])print(f"Safe mode solve time: {safe_sim.solution.solve_time}")fast_sim.solve([0,3600])print(f"Fast mode solve time: {fast_sim.solution.solve_time}")# plot solutionspybamm.dynamic_plot([safe_sim,fast_sim])
Safe mode solve time: 297.861 ms
Fast mode solve time: 100.307 ms
[2]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7f30b0efa3d0>
We see that both solvers give the same solution up to the time at which the cut-off voltage is reached. At this point the solver using “safe” mode stops, but the solver using “fast” mode carries on integrating until the final time. As its name suggests, “fast” mode integrates more quickly that “safe” mode, but is unsuitable if your simulation required events to be handled.
Usually the default solver options provide a good combination of speed and accuracy, but we encourage you to investigate different solvers and options to find the best combination for your problem.
In the next tutorial we show how to change the mesh.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[5] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). ECSarXiv. February, 2020. doi:10.1149/osf.io/67ckj.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In Tutorial 8 we saw how to change the solver options. In this tutorial we will change the mesh used in the simulation, and show how to investigate the influence of the mesh on the solution.
All models in PyBaMM have a default number of mesh points used in a simulation. However, depending on things like the operating conditions you are simulating or the parameters you are using, you may find you need to increase the number points in the mesh to obtain an accurate solution. On the other hand, you may find that you are able to decrease the number of mesh points and still obtain a solution with an acceptable degree of accuracy but in a shorter amount of computational time.
It is always good practice to conduct a mesh refinement study, where you simulate the same problem with a finer mesh and compare the results. Here will show how to do this graphically, but in practice you may wish to do a more detailed calculation of the relative error.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybamm
Note: you may need to restart the kernel to use updated packages.
In order to investigate the influence of the mesh on the solution we must solve the model multiple times, increasing the mesh resolution as we go. We first create a list of the number of points per domain we would like to use
[7]:
npts=[4,8,16,32,64]
and now we can loop over the list, creating and solving simulations as we go. The solutions are stored in the list solutions
[8]:
# choose model and parametersmodel=pybamm.lithium_ion.DFN()parameter_values=pybamm.ParameterValues("Ecker2015")# choose solversolver=pybamm.CasadiSolver(mode="fast")# loop over number of mesh pointssolutions=[]forNinnpts:var_pts={"x_n":N,# negative electrode"x_s":N,# separator"x_p":N,# positive electrode"r_n":N,# negative particle"r_p":N,# positive particle}sim=pybamm.Simulation(model,solver=solver,parameter_values=parameter_values,var_pts=var_pts)sim.solve([0,3600])solutions.append(sim.solution)
We can now pass our list of solutions to the dynamic plot method, allowing use to see the influence of the mesh on the computed voltage. We pass our list of points using the labels keyword so that the plots are labeled with the number of points used in the simulation
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Madeleine Ecker, Stefan Käbitz, Izaro Laresgoiti, and Dirk Uwe Sauer. Parameterization of a Physico-Chemical Model of a Lithium-Ion Battery: II. Model Validation. Journal of The Electrochemical Society, 162(9):A1849–A1857, 2015. doi:10.1149/2.0541509jes.
[4] Madeleine Ecker, Thi Kim Dung Tran, Philipp Dechent, Stefan Käbitz, Alexander Warnecke, and Dirk Uwe Sauer. Parameterization of a Physico-Chemical Model of a Lithium-Ion Battery: I. Determination of Parameters. Journal of the Electrochemical Society, 162(9):A1836–A1848, 2015. doi:10.1149/2.0551509jes.
[5] Alastair Hales, Laura Bravo Diaz, Mohamed Waseem Marzook, Yan Zhao, Yatish Patel, and Gregory Offer. The cell cooling coefficient: a standard to define heat rejection from lithium-ion batteries. Journal of The Electrochemical Society, 166(12):A2383, 2019.
[6] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[7] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[8] Giles Richardson, Ivan Korotkin, Rahifa Ranom, Michael Castle, and Jamie M. Foster. Generalised single particle models for high-rate operation of graded lithium-ion electrodes: systematic derivation and validation. Electrochimica Acta, 339:135862, 2020. doi:10.1016/j.electacta.2020.135862.
[9] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[10] Yan Zhao, Yatish Patel, Teng Zhang, and Gregory J Offer. Modeling the effects of thermal gradients induced by tab and surface cooling on lithium ion cell performance. Journal of The Electrochemical Society, 165(13):A3169, 2018.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In Tutorial 9 we showed how to change the mesh using on of the built-in battery models in PyBaMM. In this tutorial we show how to create a simple model from scratch in PyBaMM.
As simple example, we consider the problem of linear diffusion on a unit sphere with a flux at the boundary that depends on the concentration. We solve
Here \(c_0\) and \(j_0\) are parameters we can control. In this example we will assume that everything is non-dimensional and focus on how to set up and solve the model rather than any specific physical interpretation.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybamm
Note: you may need to restart the kernel to use updated packages.
First we load an empty model. We use the BaseModel class that sets up all the basic framework on which our model will be built.
[2]:
model=pybamm.BaseModel()
We then define our variables and parameters using the Variable and Parameter classes, respectively. Since we are solving a PDE we need to tell PyBaMM the domain each variable belongs to so that it can be discretised in space in the correct way. This is done by passing the keyword argument domain, and in this example we arbitrarily choose the domain “negative particle”.
We then state out governing equations. In PyBaMM we distinguish between Ordinary Differential Equations of the form \(dy/dt = \text{rhs}\) and Algebraic Equations of the form \(f(y) = 0\). The model equations are stored in dictionaries where the key is the variable and the value is the rhs for ODEs and the residual (\(f(y)\)) for algebraic equations.
Sometime it is useful to define intermediate quantities in order to express the governing equations more easily. In this example we define the flux, then define the rhs to be minus the divergence of the flux. The equation is then added to the dictionary model.rhs
[4]:
N=-pybamm.grad(c)# define the fluxdcdt=-pybamm.div(N)# define the rhs equationmodel.rhs={c:dcdt}# add the equation to rhs dictionary with the variable as the key
Next we add the necessary boundary and initial conditions to the model. These are also stored in dictionaries called model.boundary_conditions and model.initial_conditions, respectively.
[5]:
# boundary conditionsc_surf=pybamm.surf(c)# concentration at the surface of the spherej=j0*(1-c_surf)**(1/2)*c_surf**(1/2)# prescribed boundary fluxmodel.boundary_conditions={c:{"left":(0,"Neumann"),"right":(-j,"Neumann")}}# initial conditionsmodel.initial_conditions={c:c0}
We can add any variables of interest to the dictionary model.variables. These can simply be the variables we solve for (in this case \(c\)) or any other user-defined quantities.
In order to solve the model we need to define the geometry and choose how we are going to discretise the equations in space. We first define our radial coordinate using pybamm.SpatialVariable. When we define our spatial variable we pass in a name, the domain on which the variable lives, and the coordinate system we want to use.
We can then define our geometry using a dictionary. The key is the name of the domain, and the value is another dictionary which gives the coordinate to use and the limits. In this case we solve on a unit sphere, so we pass out SpatialVariable, r, and the limit 0 and 1.
Finally we choose how we are going to discretise in space. We choose to use the Finite Volume method on a uniform mesh with 20 volumes.
[9]:
spatial_methods={"negative particle":pybamm.FiniteVolume()}submesh_types={"negative particle":pybamm.Uniform1DSubMesh}var_pts={r:20}# create a mesh of our geometry, using a uniform grid with 20 volumesmesh=pybamm.Mesh(geometry,submesh_types,var_pts)
Now we are ready to solve the model. First we need to provide values for the parameters in our model. We do this by passing a dictionary of parameter names and values to the pybamm.ParameterValues class.
Next we choose a solver. Since this is a system of ODEs we can use the ScipySolver which uses a Runge-Kutta scheme by default.
[11]:
solver=pybamm.ScipySolver()
We can then create a simulation by passing information about the model, geometry, parameters, discretisation and solver to the pybamm.Simulation class.
<pybamm.solvers.solution.Solution at 0x7f41afa86b20>
The easiest way to quickly plot the results is to call sim.plot to create a slider plot.
Note that at present the plot method is set up to plot dimensional results from battery simulations, so the labels include units which can be ignored (the model assumes a default length scale of 1m and default time scale of 1s). Alternatively we could extract the solution data as seen in Tutorial 6 and create the plots manually. You can find out more about customising plots in this notebook.
[14]:
# pass in a list of the variables we want to plotsim.plot(["Concentration","Surface concentration","Flux","Boundary flux"])
2022-07-11 13:21:23.957 - [WARNING] processed_variable.get_spatial_scale(521): No length scale set for negative particle. Using default of 1 [m].
2022-07-11 13:21:23.975 - [WARNING] processed_variable.get_spatial_scale(521): No length scale set for negative particle. Using default of 1 [m].
[14]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7f41afa9a9d0>
Here we have seen how to create a basic model from scratch in PyBaMM. In the next tutorial we will see how to split this model up into separate submodels.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[4] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In Tutorial 10 we showed how to create a simple model from scratch in PyBaMM. In this tutorial we will solve the same problem, but using separate submodels for the linear diffusion problem and the model for the surface flux. In this simple example the surface flux is just some known function of the concentration, so we could just explicitly define it in the model for diffusion. However, we write it as a separate model to show how submodels interact.
We solved the problem of linear diffusion on a unit sphere with a flux at the boundary that depends on the concentration
Here \(c_0\) and \(j_0\) are parameters we can control. Again we will assume that everything is non-dimensional and focus on how to set up and solve the model rather than any specific physical interpretation.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybamm
Next we set up the submodel for diffusion in the particle using a pybamm.BaseSubModel. Each submodel has methods that define the variables, equations, initial and boundary conditions corresponding to the physics the model describes.
First get_fundamental_variables defines any variables that can be defined independently of any other submodels that may be included in our model. Here we can define the concentration, surface concentration and flux, and add them to the dictionary of variables.
Next we can use get_coupled_variables to define any variables that do depend on variables defined in another submodel. In this simple example we don’t have any variables to define here. However, if we had included a temperature dependent diffusivity, for example, then we would have needed to define the flux in get_coupled_variables since it would now depend on the temperature which would be defined in a separate submodel.
Once we have defined all the variables we need we can write down our equations. Any equations that include time derivatives will turn into ordinary differential equations after discretisation. We set the right hand sides of such equations in the set_rhs method. In this example we add the right hand side of the diffusion equation to the rhs dictionary. Equations that don’t contain time derivatives give algebraic constraints in our model. These equations are set in the set_algebraic
method. In this example we don’t have any algebraic equations, so we can skip this method.
Finally we set the boundary and initial conditions using the methods set_boundary_conditions and set_initial_conditions, respectively.
[3]:
classParticle(pybamm.BaseSubModel):def__init__(self,param,domain,options=None):super().__init__(param,domain,options=options)defget_fundamental_variables(self):# create concentration variablec=pybamm.Variable("Concentration",domain="negative particle")# define concentration at the surface of the spherec_surf=pybamm.surf(c)# define fluxN=-pybamm.grad(c)# create dictionary of model variablesvariables={"Concentration":c,"Surface concentration":c_surf,"Flux":N,}returnvariablesdefget_coupled_variables(self,variables):returnvariablesdefset_rhs(self,variables):# extract the variables we needc=variables["Concentration"]N=variables["Flux"]# define the rhs of the PDEdcdt=-pybamm.div(N)# add it to the submodel dictionaryself.rhs={c:dcdt}defset_algebraic(self,variables):passdefset_boundary_conditions(self,variables):# extract the variables we needc=variables["Concentration"]j=variables["Boundary flux"]# add the boundary conditions to the submodel dictionaryself.boundary_conditions={c:{"left":(0,"Neumann"),"right":(-j,"Neumann")}}defset_initial_conditions(self,variables):# extract the variable we needc=variables["Concentration"]# define the initial concentration parameterc0=pybamm.Parameter("Initial concentration")# add the initial conditions to the submodel dictionaryself.initial_conditions={c:c0}
[4]:
classBoundaryFlux(pybamm.BaseSubModel):def__init__(self,param,domain,options=None):super().__init__(param,domain,options=options)defget_coupled_variables(self,variables):# extract the variable we needc_surf=variables["Surface concentration"]# define the flux parameterj0=pybamm.Parameter("Flux parameter")j=j0*(1-c_surf)**(1/2)*c_surf**(1/2)# prescribed boundary flux# update dictionary of model variablesvariables.update({"Boundary flux":j})returnvariables
We can now set the submodels in a model by assigning a dictionary to model.submodels. The dictionary key is the name we want to give to the submodel and the value is an instance of the submodel class we want to use.
When we instantiate a submodel we are required to pass in param, a class of parameter symbols we are going to call, and domain, the domain on which the submodel lives. In this example we will simply set param to None and hard-code the definition of our parameters into the submodel. When writing lots of submodels it is more efficient to define all the parameters in a shared class, and pass this to each submodel. For the domain we will choose “Negative”.
At this stage we have just told the model which submodels it is constructed from, but the variables and equations have not yet been created. For example if we look at the rhs dictionary it is empty.
[6]:
model.rhs
[6]:
{}
To populate the model variables, equations, boundary and initial conditions we need to “build” the model. To do this we call build_model
[7]:
model.build_model()
This loops through all of the submodels, first creating the “fundamental variables”, followed by the “coupled variables” and finally the equations (rhs and algebraic) and the boundary and initial conditions. Now we see that model.rhs contains our diffusion equation.
<pybamm.solvers.solution.Solution at 0x7f9dc1b2e490>
and plot the results
[12]:
# pass in a list of the variables we want to plotsim.plot(["Concentration","Surface concentration","Flux","Boundary flux"])
2022-07-11 13:20:04.194 - [WARNING] processed_variable.get_spatial_scale(521): No length scale set for negative particle. Using default of 1 [m].
2022-07-11 13:20:04.209 - [WARNING] processed_variable.get_spatial_scale(521): No length scale set for negative particle. Using default of 1 [m].
[12]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7f9dc1b2ec10>
In this notebook we saw how to split a model up into submodels. Although this was a simple example it let us understand how to construct submodels and see how they interact via coupled variables.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[4] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this series of notebooks, we will run through the steps involved in creating a new model within pybamm. Before using pybamm we recommend following the Getting Started guides.
In this notebook we create and solve the following simple ODE model:
We first initialise the model using the BaseModel class. This sets up the required structure for our model.
[2]:
model=pybamm.BaseModel()
Next, we define the variables in the model using the Variable class. In more complicated models we can give the variables more informative string names, but here we simply name the variables “x” and “y”
[3]:
x=pybamm.Variable("x")y=pybamm.Variable("y")
We can now use the symbols we have created for our variables to write out our governing equations. Note that the governing equations must be provied in the explicit form d/dt=rhs since pybamm only stores the right hand side (rhs) and assumes that the left hand side is the time derivative.
[4]:
dxdt=4*x-2*ydydt=3*x-y
The governing equations must then be added to the dictionary model.rhs. The dictionary stores key and item pairs, where the key is the variable which is governed by the equation stored in the corresponding item. Note that the keys are the symbols that represent the variables and are not the variable names (e.g. the key is x, not the string “x”).
[5]:
model.rhs={x:dxdt,y:dydt}
The initial conditions are also stored in a dictionary, model.initial_conditions, which again uses the variable as the key
Finally, we can add any variables of interest to our model. Note that these can be things other than the variables that are solved for. For example, we may want to store the variable defined by \(z=x+4y\) as a model output. Variables are added to the model using the model.variables dictionary as follows:
[7]:
model.variables={"x":x,"y":y,"z":x+4*y}
Note that the keys of this dictionary are strings (i.e. the names of the variables). The string names can be different from the variable symbol, and should in general be something informative. The model is now completely defined and is ready to be discretised and solved!
We first discretise the model using the pybamm.Discretisation class. Calling the method process_model turns the model variables into a pybamm.StateVector object that can be passed to a solver. Since the model is a system of ODEs we do not need to provide a mesh or worry about any spatial dependence, so we can use the default discretisation. Details on how to provide a mesh will be covered in the following notebook.
[8]:
disc=pybamm.Discretisation()# use the default discretisationdisc.process_model(model);
Now that the model has been discretised it is ready to be solved. Here we choose the ODE solver pybamm.ScipySolver and solve, returning the solution at 20 time points in the interval \(t \in [0, 1]\)
After solving, we can extract the variables from the solution. These are automatically post-processed so that the solutions can be called at any time \(t\) using interpolation. The times at which the model was solved at are stored in solution.t and the statevectors at those times are stored in solution.y
[10]:
t_sol,y_sol=solution.t,solution.y# get solution times and statesx=solution["x"]# extract and process x from the solutiony=solution["y"]# extract and process y from the solution
We then plot the numerical solution against the exact solution.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). ECSarXiv. February, 2020. doi:10.1149/osf.io/67ckj.
[4] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In the previous notebook we show how to create, discretise and solve an ODE model in pybamm. In this notebook we show how to create and solve a PDE problem, which will require meshing of the spatial domain.
As an example, we consider the problem of linear diffusion on a unit sphere,
As in the previous example, we start with a pybamm.BaseModel object and define our model variables. Since we are now solving a PDE we need to tell pybamm the domain each variable belongs to so that it can be discretised in space in the correct way. This is done by passing the keyword argument domain, and in this example we choose the domain “negative particle”.
Note that we have given our variable the (useful) name “Concentration”, but the symbol representing this variable is simply c.
We then state out governing equations. Sometime it is useful to define intermediate quantities in order to express the governing equations more easily. In this example we define the flux, then define the rhs to be minus the divergence of the flux. The equation is then added to the dictionary model.rhs
[3]:
N=-pybamm.grad(c)# define the fluxdcdt=-pybamm.div(N)# define the rhs equationmodel.rhs={c:dcdt}# add the equation to rhs dictionary
Unlike ODE models, PDE models require both initial and boundary conditions. Similar to initial conditions, boundary conditions can be added using the dictionary model.boundary_conditions. Boundary conditions for each variable are provided as a dictionary of the form {side:(value,type), where, in 1D, side can be “left” or “right”, value is the value of the boundary conditions, and type is the type of boundary condition (at present, this can be “Dirichlet” or “Neumann”).
Now the model is now completely defined all that remains is to discretise and solve. Since this model is a PDE we need to define the geometry on which it will be solved, and choose how to mesh the geometry and discretise in space.
We can define spatial variables in a similar way to how we defined model variables, providing a domain and a coordinate system. The geometry on which we wish to solve the model is defined using a nested dictionary. The first key is the domain name (here “negative particle”) and the entry is a dictionary giving the limits of the domain.
We then create a uniform one-dimensional mesh with 20 points.
[7]:
# mesh and discretisesubmesh_types={"negative particle":pybamm.Uniform1DSubMesh}var_pts={r:20}mesh=pybamm.Mesh(geometry,submesh_types,var_pts)
Example of meshes that do require parameters include the pybamm.Exponential1DSubMesh which clusters points close to one or both boundaries using an exponential rule. It takes a parameter which sets how closely the points are clustered together, and also lets the users select the side on which more points should be clustered. For example, to create a mesh with more nodes clustered to the right (i.e. the surface in the particle problem), using a stretch factor of 2, we pass an instance of the
exponential submesh class and a dictionary of parameters into the MeshGenerator class as follows: pybamm.MeshGenerator(pybamm.Exponential1DSubMesh,submesh_params={"side":"right","stretch":2})
After defining a mesh we choose a spatial method. Here we choose the Finite Volume Method. We then set up a discretisation by passing the mesh and spatial methods to the class pybamm.Discretisation. The model is then processed, turning the variables into (slices of) a statevector, spatial variables into vector and spatial operators into matrix-vector multiplications.
As before, we choose a solver and times at which we want the solution returned. We then solve, extract the variables we are interested in, and plot the result.
[9]:
# solvesolver=pybamm.ScipySolver()t=np.linspace(0,1,100)solution=solver.solve(model,t)# post-process, so that the solution can be called at any time t or space r# (using interpolation)c=solution["Concentration"]# plotfig,(ax1,ax2)=plt.subplots(1,2,figsize=(13,4))ax1.plot(solution.t,c(solution.t,r=1))ax1.set_xlabel("t")ax1.set_ylabel("Surface concentration")r=np.linspace(0,1,100)ax2.plot(r,c(t=0.5,r=r))ax2.set_xlabel("r")ax2.set_ylabel("Concentration at t=0.5")plt.tight_layout()plt.show()
2021-11-19 15:31:50,774 - [WARNING] processed_variable.get_spatial_scale(520): No length scale set for negative particle. Using default of 1 [m].
In the next notebook we build on the example here to to solve the problem of diffusion in the negative electrode particle within the single particle model. In doing so we will also cover how to include parameters in a model.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[4] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In the previous notebook we saw how to solve a PDE model in pybamm. Now it is time to solve a real-life battery problem! We consider the problem of spherical diffusion in the negative electrode particle within the single particle model. That is,
where \(c\) is the concentration, \(r\) the radial coordinate, \(t\) time, \(R\) the particle radius, \(D\) the diffusion coefficient, \(j\) the interfacial current density, \(F\) Faraday’s constant, and \(c_0\) the initial concentration.
As before, we begin by importing the PyBaMM library into this notebook, along with any other packages we require, and start with an empty pybamm.BaseModel
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpimportmatplotlib.pyplotaspltmodel=pybamm.BaseModel()
Note: you may need to restart the kernel to use updated packages.
We then define all of the model variables and parameters. Parameters are created using the pybamm.Parameter class and are given informative names (with units). Later, we will provide parameter values and the Parameter objects will be turned into numerical values. For more information please see the parameter values notebook.
[2]:
R=pybamm.Parameter("Particle radius [m]")D=pybamm.Parameter("Diffusion coefficient [m2.s-1]")j=pybamm.Parameter("Interfacial current density [A.m-2]")F=pybamm.Parameter("Faraday constant [C.mol-1]")c0=pybamm.Parameter("Initial concentration [mol.m-3]")c=pybamm.Variable("Concentration [mol.m-3]",domain="negative particle")
Now we define our model equations, boundary and initial conditions, as in the previous example.
In order to discretise and solve the model we need to provide values for all of the parameters. This is done via the pybamm.ParameterValues class, which accepts a dictionary of parameter names and values
[5]:
param=pybamm.ParameterValues({"Particle radius [m]":10e-6,"Diffusion coefficient [m2.s-1]":3.9e-14,"Interfacial current density [A.m-2]":1.4,"Faraday constant [C.mol-1]":96485,"Initial concentration [mol.m-3]":2.5e4,})
Here all of the parameters are simply scalars, but they can also be functions or read in from data (see parameter values notebook).
As in the previous example, we define the particle geometry. Note that in this example the definition of the geometry contains a parameter, the particle radius \(R\)
As is the previous example, we choose a solver and times at which we want the solution returned.
[9]:
# solvesolver=pybamm.ScipySolver()t=np.linspace(0,3600,600)solution=solver.solve(model,t)# post-process, so that the solution can be called at any time t or space r# (using interpolation)c=solution["Concentration [mol.m-3]"]c_surf=solution["Surface concentration [mol.m-3]"]# plotfig,(ax1,ax2)=plt.subplots(1,2,figsize=(13,4))ax1.plot(solution.t,c_surf(solution.t))ax1.set_xlabel("Time [s]")ax1.set_ylabel("Surface concentration [mol.m-3]")r=mesh["negative particle"].nodes# radial positiontime=1000# time in secondsax2.plot(r*1e6,c(t=time,r=r),label=f"t={time}[s]")ax2.set_xlabel("Particle radius [microns]")ax2.set_ylabel("Concentration [mol.m-3]")ax2.legend()plt.tight_layout()plt.show()
2021-11-19 15:29:22,931 - [WARNING] processed_variable.get_spatial_scale(520): No length scale set for negative particle. Using default of 1 [m].
In the next notebook we consider the limit of fast diffusion in the particle. This leads to a reduced-order model for the particle behaviour, which we compare with the full (Fickian diffusion) model.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[4] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In the previous notebook we saw how to solve the problem of diffusion on a sphere, motivated by the problem in the negative particle in battery modelling. In this notebook we consider a reduced-order ODE model for the particle behaviour, suitable in the limit of fast diffusion. We also show how to compare the results of the full and reduced-order models.
In the limit of fast diffusion in the particles the concentration is uniform in \(r\). This result in the following ODE model for the (uniform) concentration in the particle
where \(c\)$ is the concentration, \(r\) the radial coordinate, \(t\) time, \(R\) the particle radius, \(D\) the diffusion coefficient, \(j\) the interfacial current density, \(F\) Faraday’s constant, and \(c_0\) the initial concentration.
As in the previous example we use the following parameters:
As in the single particle diffusion example, we begin by importing the pybamm library into this notebook, along with any other packages we require. In this notebook we want to compare the results of the full and reduced-order models, so we create two empty pybamm.BaseModel objects. We can pass in a name when we create the model, for easy reference.
[11]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpimportmatplotlib.pyplotaspltfull_model=pybamm.BaseModel(name="full model")reduced_model=pybamm.BaseModel(name="reduced model")
WARNING: You are using pip version 22.0.4; however, version 22.3.1 is available.
You should consider upgrading via the '/home/mrobins/git/PyBaMM/env/bin/python -m pip install --upgrade pip' command.Note: you may need to restart the kernel to use updated packages.
It can be useful to add the models to a list so that we can perform the same operations on each model easily
[12]:
models=[full_model,reduced_model]
We then define our parameters, as seen previously,
[13]:
R=pybamm.Parameter("Particle radius [m]")D=pybamm.Parameter("Diffusion coefficient [m2.s-1]")j=pybamm.Parameter("Interfacial current density [A.m-2]")F=pybamm.Parameter("Faraday constant [C.mol-1]")c0=pybamm.Parameter("Initial concentration [mol.m-3]")
The reduced order model solves and ODE for the (uniform) concentration in the particle. In the parameter regime where this is valid, we expect that the solution of the ODE model should agree with the average concentration in the PDE mode. In anticipation of this, we create two variables: the concentration (which we will use in the PDE model), and the average concentration (which we will use in the ODE model). This will make it straightforward to compare the results in a consistent way. Note that
the average concentration doesn’t have a domain since it is a scalar quantity.
Now we define our model equations, initial and boundary conditions (where appropriate)
[15]:
# governing equations for full modelN=-D*pybamm.grad(c)# fluxdcdt=-pybamm.div(N)full_model.rhs={c:dcdt}# governing equations for reduced modeldc_avdt=-3*j/R/Freduced_model.rhs={c_av:dc_avdt}# initial conditions (these are the same for both models)full_model.initial_conditions={c:c0}reduced_model.initial_conditions={c_av:c0}# boundary conditions (only required for full model)lbc=pybamm.Scalar(0)rbc=-j/F/Dfull_model.boundary_conditions={c:{"left":(lbc,"Neumann"),"right":(rbc,"Neumann")}}
We can now populate the variables dictionary of both models with any variables of interest. We can compute the average concentration in the full model using the operator pybamm.r_average. We may also wish to compare the concentration profile predicted by the full model with the uniform concentration profile predicted by the reduced model. We can use the operator pybamm.PrimaryBroadcast to broadcast the scalar valued uniform concentration across the particle domain so that it can be
visualised as a function of \(r\).
Note: the “Primary” refers to the fact the we are broadcasting in only one dimension. For some models, such as the DFN, variables may depend on a “pseudo-dimension” (e.g. the position in \(x\) across the cell), but spatial operators only act in the “primary dimension” (e.g. the position in \(r\) within the particle). If you are unfamiliar with battery models, do not worry, the details of this are not important for this example. For more information see the broadcasts
notebook.
[16]:
# full modelfull_model.variables={"Concentration [mol.m-3]":c,"Surface concentration [mol.m-3]":pybamm.surf(c),"Average concentration [mol.m-3]":pybamm.r_average(c),}# reduced modelreduced_model.variables={"Concentration [mol.m-3]":pybamm.PrimaryBroadcast(c_av,"negative particle"),"Surface concentration [mol.m-3]":c_av,# in this model the surface concentration is just equal to the scalar average concentration"Average concentration [mol.m-3]":c_av,}
We can now set up our mesh, choose a spatial method, and discretise our models. Note that, even though the reduced-order model is an ODE model, we discretise using the mesh for the particle so that our PrimaryBroadcast operator is discretised in the correct way.
[19]:
# meshsubmesh_types={"negative particle":pybamm.Uniform1DSubMesh}var_pts={r:20}mesh=pybamm.Mesh(geometry,submesh_types,var_pts)# discretisationspatial_methods={"negative particle":pybamm.FiniteVolume()}disc=pybamm.Discretisation(mesh,spatial_methods)# process modelsformodelinmodels:disc.process_model(model)
Both models are now discretised and ready to be solved.
As before, we choose a solver and times at which we want the solution returned. We then solve both models, post-process the results, and create a slider plot to compare the results.
[20]:
# loop over models to solvet=np.linspace(0,3600,600)solutions=[None]*len(models)# create list to hold solutionsfori,modelinenumerate(models):solver=pybamm.ScipySolver()solutions[i]=solver.solve(model,t)# post-process the solution of the full modelc_full=solutions[0]["Concentration [mol.m-3]"]c_av_full=solutions[0]["Average concentration [mol.m-3]"]# post-process the solution of the reduced modelc_reduced=solutions[1]["Concentration [mol.m-3]"]c_av_reduced=solutions[1]["Average concentration [mol.m-3]"]# plotr=mesh["negative particle"].nodes# radial positiondefplot(t):fig,(ax1,ax2)=plt.subplots(1,2,figsize=(13,4))# Plot concetration as a function of rax1.plot(r*1e6,c_full(t=t,r=r),label="Full Model")ax1.plot(r*1e6,c_reduced(t=t,r=r),label="Reduced Model")ax1.set_xlabel("Particle radius [microns]")ax1.set_ylabel("Concentration [mol.m-3]")ax1.legend()# Plot average concentration over timet_hour=np.linspace(0,3600,600)# plot over full hourc_min=c_av_reduced(t=3600)*0.98# minimum axes limitc_max=param["Initial concentration [mol.m-3]"]*1.02# maximum axes limitax2.plot(t_hour,c_av_full(t=t_hour),label="Full Model")ax2.plot(t_hour,c_av_reduced(t=t_hour),label="Reduced Model")ax2.plot([t,t],[c_min,c_max],"k--")# plot line to track timeax2.set_xlabel("Time [s]")ax2.set_ylabel("Average concentration [mol.m-3]")ax2.legend()plt.tight_layout()plt.show()importipywidgetsaswidgetswidgets.interact(plot,t=widgets.FloatSlider(min=0,max=3600,step=1,value=0));
2022-12-12 12:41:59.589 - [WARNING] processed_variable.get_spatial_scale(518): No length scale set for negative particle. Using default of 1 [m].
2022-12-12 12:41:59.609 - [WARNING] processed_variable.get_spatial_scale(518): No length scale set for negative particle. Using default of 1 [m].
From the results we observe that the reduced-order model does a good job of predicting the average concentration, but, since it is only an ODE model, cannot predicted the spatial distribution.
In the next notebook we will show how to set up and solve a model which contains multiple domains.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[4] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In the previous notebook we saw how to compare full and reduced-order models. Both of these models were posed on a single domain: the negative electrode particle. Here we will see how to create a model which contains multiple domains.
We consider a problem posed on a half-cell geometry, which consists of a separator (\(-L_s<x<0\)) and a positive electrode (\(0<x<L_p\)). These two regions are considered “macroscale” domains. At each point in the positive electrode we treat a “microscale” problem to model diffusion of lithium within the positive particles (\(0<r<R_p\)). We will see how to create variables in each of the different domains so that the governing equations are properly coupled.
In the interest of simplicity we assume that the current in both the the solid and electrolyte is given by Ohm’s law, and ignore any concentration gradients in the electrolyte. The governing equations for charge conservation at the macroscale are then
\[\begin{split}i_e = -\kappa \nabla \phi_e, \quad \nabla i_e = a j, \quad -L_s<x<0, \\
i = -\sigma \nabla \phi, \quad \nabla \cdot i = -a j, \quad 0<x<L_p,\end{split}\]
where \(i\) and \(i_e\) are the current densities in the solid and electrolyte, respectively, \(\phi\) and \(\phi_e\) are the electric potentials in the solid and electrolyte, respectively, \(\sigma\) is the solid conductivity, \(\kappa\) is the ionic conductivity, \(a\) is the electrode surface area per unit volume and \(j\) the interfacial current density. The charge conservation equations are subject to the boundary conditions
where \(I_{\text{app}}\) is the applied current and \(A\) is the electrode cross-sectional area. We then have an equation posed at each macroscopic point in the electrode (\(0<x<L_p\)) describing transport of lithium within the active material particles. That is,
where \(c\) is the concentration, \(r\) the radial coordinate, \(t\) time, \(R\) the particle radius, \(D\) the diffusion coefficient, \(F\) Faraday’s constant, and \(c_0\) the initial concentration. For the interfacial current density we assume Butler-Volmer kinetics
As before, we begin by importing the PyBaMM library into this notebook, along with any other packages we require, and start with an empty pybamm.BaseModel
[19]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpmodel=pybamm.BaseModel()
Note: you may need to restart the kernel to use updated packages.
Let’s first define our model variables. We can define the electric potential in the positive electrode in the same way as we defined the concentration variables in the previous notebooks
and then concatenate these two variables together to define a single variable that spans the separator and positive electrode
[22]:
phi_e=pybamm.concatenation(phi_e_s,phi_e_p)
Note that in our formulation the separator will be on the left and the positive electrode will be on the right, so this is the order in which we concatenated the variables for the electrolyte potential in each domain.
The concentration in the electrode particles can vary both in \(r\) and \(x\), but diffusion only occurs in the \(r\) direction. In order to handle this situation we introduce the concept of “auxiliary domains”. These are domains in which quantities can vary, but spatial operators do not act. To set up our concentration variable we create a Variable which has domain “positive particle” and secondary domain “positive electrode”
Now spatial operators acting on c only act in the \(r\) direction (corresponding to the primary domain “positive particle”), but c can still depend on \(x\) (corresponding to the secondary domain “positive electrode”). For more details on the different domains (primary, secondary, etc.) see the broadcasts notebook.
Next we will define our parameters. As seen before, scalar parameters can be defined using the Parameter object
[24]:
F=pybamm.Parameter("Faraday constant [C.mol-1]")R=pybamm.Parameter("Molar gas constant [J.mol-1.K-1]")T=pybamm.Parameter("Temperature [K]")a=pybamm.Parameter("Surface area per unit volume [m-1]")R_p=pybamm.Parameter("Positive particle radius [m]")L_s=pybamm.Parameter("Separator thickness [m]")L_p=pybamm.Parameter("Positive electrode thickness [m]")A=pybamm.Parameter("Electrode cross-sectional area [m2]")sigma=pybamm.Parameter("Positive electrode conductivity [S.m-1]")kappa=pybamm.Parameter("Electrolyte conductivity [S.m-1]")D=pybamm.Parameter("Diffusion coefficient [m2.s-1]")I_app=pybamm.Parameter("Applied current [A]")c0=pybamm.Parameter("Initial concentration [mol.m-3]")
Parameters can also have some functional dependence. We can define such parameters using the FunctionParameter object. We also need to specify the inputs (i.e. the variables on which the function depends). In our example both the exchange current density and the open-circuit potential will depend on the particle surface concentration.
[25]:
c_surf=pybamm.surf(c)# get the surface concentrationinputs={"Positive particle surface concentration [mol.m-3]":c_surf}j0=pybamm.FunctionParameter("Positive electrode exchange-current density [A.m-2]",inputs)U=pybamm.FunctionParameter("Positive electrode OCP [V]",inputs)
We also need to define the interfacial current, which is zero in the separator and given by the Butler-Volmer equation in the positive electrode
Now we can write our governing equations, boundary and initial conditions. Note that we provide initial conditions for the algebraic equations. These are not really initial conditions, but are used as an initial guess for the solver.
As we have seen before, we can provide values for our parameters using the ParameterValues class. As well as providing scalar values, we also need to provide the functional form using by our FunctionParameter objects. Here we will define these functions locally, but we could provide the path to a function defined elsewhere or provide data (see the parameterization notebook).
[29]:
frompybammimporttanh# both functions will depend on the maximum concentrationc_max=pybamm.Parameter("Maximum concentration in positive electrode [mol.m-3]")defexchange_current_density(c_surf):k=6*10**(-7)# reaction rate [(A/m2)(m3/mol)**1.5]c_e=1000# (constant) electrolyte concentration [mol.m-3]returnk*c_e**0.5*c_surf**0.5*(c_max-c_surf)**0.5defopen_circuit_potential(c_surf):stretch=1.062sto=stretch*c_surf/c_maxu_eq=(2.16216+0.07645*tanh(30.834-54.4806*sto)+2.1581*tanh(52.294-50.294*sto)-0.14169*tanh(11.0923-19.8543*sto)+0.2051*tanh(1.4684-5.4888*sto)+0.2531*tanh((-sto+0.56478)/0.1316)-0.02167*tanh((sto-0.525)/0.006))returnu_eq
Now we can pass these functions, along with our scalar parameters, to ParameterValues
[30]:
param=pybamm.ParameterValues({"Surface area per unit volume [m-1]":0.15e6,"Positive particle radius [m]":10e-6,"Separator thickness [m]":25e-6,"Positive electrode thickness [m]":100e-6,"Electrode cross-sectional area [m2]":2.8e-2,"Applied current [A]":0.9,"Positive electrode conductivity [S.m-1]":10,"Electrolyte conductivity [S.m-1]":1,"Diffusion coefficient [m2.s-1]":1e-13,"Faraday constant [C.mol-1]":96485,"Initial concentration [mol.m-3]":25370,"Molar gas constant [J.mol-1.K-1]":8.314,"Temperature [K]":298.15,"Maximum concentration in positive electrode [mol.m-3]":51217,"Positive electrode exchange-current density [A.m-2]":exchange_current_density,"Positive electrode OCP [V]":open_circuit_potential,})
Next we define the geometry. In the same way that our variable for the concentration in the electrode particles had and “auxiliary domain”, our spatial variable \(r\) also has an auxiliary domain. This means that when the model in discretised there will be the correct number of particles included in the geometry - one for each point in \(x\).
and plot the results. To make the plot we will use dynamic_plot which automatically creates a slider plot given a solution and a list of variables to plot. By nesting variables in the list we can plot two variables together on the same axes.
2021-11-19 15:30:23,304 - [WARNING] processed_variable.get_spatial_scale(520): No length scale set for positive electrode. Using default of 1 [m].
2021-11-19 15:30:23,328 - [WARNING] processed_variable.get_spatial_scale(520): No length scale set for separator. Using default of 1 [m].
2021-11-19 15:30:23,367 - [WARNING] processed_variable.get_spatial_scale(520): No length scale set for positive electrode. Using default of 1 [m].
2021-11-19 15:30:23,395 - [WARNING] processed_variable.get_spatial_scale(520): No length scale set for positive electrode. Using default of 1 [m].
[35]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7ff07a82d130>
In the next notebook we consider a simple model for SEI growth, and show how to correctly pose the model in non-dimensional form and then create and solve it using pybamm.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this notebook, we will run through the steps involved in creating a new model within pybamm. We will then solve and plot the outputs of the model. We have chosen to implement a very simple model of SEI growth. We first give a brief derivation of the model and discuss how to nondimensionalise the model so that we can show the full process of model conception to solution within a single notebook.
Note: if you run the entire notebook and then try to evaluate the earlier cells, you will likely receive an error. This is because the state of objects is mutated as it is passed through various stages of processing. In this case, we recommend that you restart the Kernel and then evaluate cells in turn through the notebook.
A Simple Model of Solid Electrolyte Interphase (SEI) Growth#
The SEI is a porous layer that forms on the surfaces of negative electrode particles from the products of electrochemical reactions which consume lithium and electrolyte solvents. In the first few cycles of use, a lithium-ion battery loses a large amount of capacity; this is generally attributed to lithium being consumed to produce SEI. However, after a few cycles, the rate of capacity loss slows at a rate often (but not always) reported to scale with the square root of time. SEI growth is
therefore often considered to be limited in some way by a diffusion process.
In our simple SEI model, we consider a one-dimensional SEI which extends from the surface of a planar negative electrode at \(x=0\) until \(x=L\), where \(L\) is the thickness of the SEI. Since the SEI is porous, there is some electrolyte within the region \(x\in[0, L]\) and therefore some concentration of solvent, \(c\). Within the porous SEI, the solvent is transported via a diffusion process according to:
where \(t\) is the time, \(N\) is the solvent flux, and \(D(c)\) is the effective solvent diffusivity (a function of the solvent concentration).
On the electrode-SEI surface (\(x=0\)) the solvent is consumed by the SEI growth reaction, \(R\). We assume that diffusion of solvent in the bulk electrolyte (\(x>L\)) is fast so that on the SEI-electrolyte surface (\(x=L\)) the concentration of solvent is fixed at the value \(c_{\infty}\). Therefore, the boundary conditions are
\[N|_{x=0} = - R, \quad c|_{x=L} = c_{\infty},\]
We also assume that the concentration of solvent within the SEI is initially uniform and equal to the bulk electrolyte solvent concentration, so that the initial condition is
\[c|_{t=0} = c_{\infty}\]
Since the SEI is growing, we require an additional equation for the SEI thickness. The thickness of the SEI grows at a rate proportional to the SEI growth reaction \(R\), where the constant of proportionality is the partial molar volume of the reaction products, \(\hat{V}\). We also assume that the SEI is initially of thickness \(L_0\). Therefore, we have
Finally, we assume for the sake of simplicity that the SEI growth reaction is irreversible and that the potential difference across the SEI is constant. The reaction is also assumed to be proportional to the concentration of solvent at the electrode-SEI surface (\(x=0\)). Therefore, the reaction flux is given by
\[R = k c|_{x=0}\]
where \(k\) is the reaction rate constant (which is in general dependent upon the potential difference across the SEI).
The model above is a moving boundary problem as it is defined in \(x\in[0, L]\). In order to solve it, we need to fix the boundary by introducing the scaling
As always, we begin by importing PyBaMM and changing our working directory to the root of the pybamm/ folder.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpimportosos.chdir(pybamm.__path__[0]+"/..")
[notice] A new release of pip is available: 23.0.1 -> 23.2[notice] To update, run: pip install --upgrade pip
Note: you may need to restart the kernel to use updated packages.
A model is defined in six steps: 1. Initialise model 2. Define parameters and variables 3. State governing equations 4. State boundary conditions 5. State initial conditions 6. State output variables
We shall proceed through each step to enter our simple SEI growth model.
In our SEI model, we have two dimensionless parameters, \(k\) and \(\hat{V}\), and one dimensionless function \(D(c)\), which are all given in terms of the dimensional parameters, see (5). In pybamm, inputs are dimensional, so we first state all the dimensional parameters. We then define the dimensionless parameters, which are expressed an non-dimensional groupings of dimensional parameters. To define the dimensional parameters, we use the Parameter object to create parameter
symbols. Parameters which are functions are defined using FunctionParameter object and should be defined within a python function as shown.
We now define the dimensionless variables in our model. Since these are the variables we solve for directly, we do not need to write them in terms of the dimensional variables. We simply use SpatialVariable and Variable to create the required symbols:
We can now use the symbols we have created for our parameters and variables to write out our governing equations. Note that before we use the reaction flux and solvent flux, we must derive new symbols for them from the defined parameter and variable symbols. Each governing equation must also be stated in the explicit form d/dt=rhs since pybamm only stores the right hand side (rhs) and assumes that the left hand side is the time derivative. The governing equations are then simply
[5]:
# SEI reaction fluxR=k*pybamm.BoundaryValue(c,"left")# solvent concentration equationN=-1/L*D(c)*pybamm.grad(c)dcdt=(V_hat*R)/L*pybamm.inner(xi,pybamm.grad(c))-1/L*pybamm.div(N)# SEI thickness equationdLdt=V_hat*R
Once we have stated the equations, we can add them to the model.rhs dictionary. This is a dictionary whose keys are the variables being solved for, and whose values correspond right hand sides of the governing equations for each variable.
We only have boundary conditions on the solvent concentration equation. We must state where a condition is Neumann (on the gradient) or Dirichlet (on the variable itself).
The boundary condition on the electrode-SEI (x=0) boundary is:
which is a Neumann condition. To implement this boundary condition in pybamm, we must first rearrange the equation so that the gradient of the concentration, \(\nabla c|_{x=0}\), is the subject. Therefore we have
D_left=pybamm.BoundaryValue(D(c),"left")# pybamm requires BoundaryValue(D(c)) and not D(BoundaryValue(c))grad_c_left=R*L/D_left
On the SEI-electrolyte boundary (\(\xi=1\)), we have the boundary condition
\[c|_{\xi=1} = c_∞\]
which is a Dirichlet condition and is just entered as
[8]:
c_right=c_inf
We now load these boundary conditions into the model.boundary_conditions dictionary in the following way, being careful to state the type of boundary condition:
We already have everything required in model for the model to be used and solved, but we have not yet stated what we actually want to output from the model. PyBaMM allows users to output any combination of symbols as an output variable therefore allowing the user the flexibility to output important quantities without further tedious postprocessing steps.
Some useful outputs for this simple model are: - the SEI thickness - the SEI growth rate - the solvent concentration
These are added to the model by adding entries to the model.variables dictionary
The model is now fully defined and ready to be used. If you plan on reusing the model several times, you can additionally set model defaults which may include: a default geometry to run the model on, a default set of parameter values, a default solver, etc.
The model will now behave in the same way as any of the inbuilt PyBaMM models. However, to demonstrate that the model works we display the steps involved in solving the model but we will not go into details within this notebook.
[13]:
# define geometrygeometry=pybamm.Geometry({"SEI layer":{xi:{"min":pybamm.Scalar(0),"max":pybamm.Scalar(1)}}})defDiffusivity(cc):returncc*10**(-12)# parameter values (not physically based, for example only!)param=pybamm.ParameterValues({"Reaction rate constant [m.s-1]":1e-6,"Initial thickness [m]":1e-6,"Partial molar volume [m3.mol-1]":10,"Bulk electrolyte solvent concentration [mol.m-3]":1,"Diffusivity [m2.s-1]":Diffusivity,})# process model and geometryparam.process_model(model)param.process_geometry(geometry)# mesh and discretisesubmesh_types={"SEI layer":pybamm.Uniform1DSubMesh}var_pts={xi:100}mesh=pybamm.Mesh(geometry,submesh_types,var_pts)spatial_methods={"SEI layer":pybamm.FiniteVolume()}disc=pybamm.Discretisation(mesh,spatial_methods)disc.process_model(model)
[13]:
<pybamm.models.base_model.BaseModel at 0x7f3a8005b490>
Using these outputs, we can now plot the SEI thickness as a function of time and also the solvent concentration profile within the SEI. We use a slider to plot the concentration profile at different times. Note that, even though our model is written in nondimensional form, the processed variables are functions of dimensional space and time (in SI units).
[15]:
importmatplotlib.pyplotasplt# plot SEI thickness in microns as a function of t in microseconds# and concentration in mol/m3 as a function of x in micronsL_0_eval=param.evaluate(L_0)xi=np.linspace(0,1,100)# dimensionless spacedefplot(t):_,(ax1,ax2)=plt.subplots(1,2,figsize=(10,5))ax1.plot(solution.t,L_out(solution.t)*1e6)ax1.plot(t,L_out(t)*1e6,"r.")ax1.set_ylabel(r"SEI thickness [$\mu$m]")ax1.set_xlabel(r"t [s]")ax2.plot(xi*L_out(t)*1e6,c_out(t,xi))ax2.set_ylim(0,1.1)ax2.set_xlim(0,L_out(solution.t[-1])*1e6)ax2.set_ylabel("Solvent concentration [mol.m-3]")ax2.set_xlabel(r"x [$\mu$m]")plt.tight_layout()plt.show()importipywidgetsaswidgetswidgets.interact(plot,t=widgets.FloatSlider(min=0,max=solution.t[-1],step=0.1,value=0));
The purpose of this notebook has been to go through the steps involved in getting a simple model working within PyBaMM. However, if you plan on reusing your model and want greater flexibility then we recommend that you create a new class for your model. We have set out instructions on how to do this in the “Adding a Model” tutorial in the documentation.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[4] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
This reference manual details functions, modules, and objects
included in PyBaMM, describing what they are and what they do.
For a high-level introduction to PyBaMM, see the user guide and the examples.
children (iterable Symbol, optional) – children to attach to this node, default to an empty list
domain (iterable of str, or str) – list of domains over which the node is valid (empty list indicates the symbol
is valid over all domains)
auxiliary_domains (dict of str) – dictionary of auxiliary domains over which the node is valid (empty dictionary
indicates no auxiliary domains). Keys can be “secondary”, “tertiary” or
“quaternary”. The symbol is broadcast over its auxiliary domains.
For example, a symbol might have domain “negative particle”, secondary domain
“separator” and tertiary domain “current collector” (domain=”negative
particle”, auxiliary_domains={“secondary”: “separator”, “tertiary”: “current
collector”}).
domains (dict) – A dictionary equivalent to {‘primary’: domain, auxiliary_domains}. Either
‘domain’ and ‘auxiliary_domains’, or just ‘domains’, should be provided
(not both). In future, the ‘domain’ and ‘auxiliary_domains’ arguments may be
deprecated.
Differentiate a symbol with respect to a variable. For any symbol that can be
differentiated, return 1 if differentiating with respect to yourself,
self._diff(variable) if variable is in the expression tree of the symbol,
and zero otherwise.
Parameters:
variable (pybamm.Symbol) – The variable with respect to which to differentiate
For symbols that cannot be evaluated directly (e.g. Variable or Parameter),
a vector of the appropriate shape is returned instead, using the symbol’s
domain. See pybamm.Symbol.evaluate()
Evaluates the expression. If a node exists in the tree that cannot be evaluated
as a scalar or vector (e.g. Time, Parameter, Variable, StateVector), then None
is returned. If there is an InputParameter in the tree then a 1 is returned.
Otherwise the result of the evaluation is given.
Returns True if a symbol evaluates on an edge, i.e. symbol contains a gradient
operator, but not a divergence operator, and is not an IndefiniteIntegral.
Caches the solution for faster results.
Parameters:
dimension (str) – The dimension (primary, secondary, etc) in which to query evaluation on
edges
Returns:
Whether the symbol evaluates on edges (in the finite volume discretisation
sense)
Returns True if evaluating the expression returns a number.
Returns False otherwise, including if NotImplementedError or TyperError
is raised.
!Not to be confused with isinstance(self, pybamm.Scalar)!
Shape of an object for cases where it cannot be evaluated directly. If a symbol
cannot be evaluated directly (e.g. it is a Variable or Parameter), it is
instead given an arbitrary domain-dependent shape.
A node in the expression tree representing a function parameter.
This node will be replaced by a pybamm.Function node if a callable function
is passed to the parameter values, and otherwise (in some rarer cases, such as
constant current) a pybamm.Scalar node.
inputs (dict) – A dictionary with string keys and pybamm.Symbol values representing
the function inputs. The string keys should provide a reasonable description
of what the input to the function is
(e.g. “Electrolyte concentration [mol.m-3]”)
diff_variable (pybamm.Symbol, optional) – if diff_variable is specified, the FunctionParameter node will be replaced by a
pybamm.Function and then differentiated with respect to diff_variable.
Default is None.
print_name (str, optional) – The name to show when printing. Default is ‘calculate’, in which case the name
is calculated using sys._getframe().
name (str) – name of the node
domain : iterable of str, optional
list of domains that this variable is valid over
auxiliary_domains (dict, optional) – dictionary of auxiliary domains ({‘secondary’: …, ‘tertiary’: …,
‘quaternary’: …}). For example, for the single particle model, the particle
concentration would be a Variable with domain ‘negative particle’ and secondary
auxiliary domain ‘current collector’. For the DFN, the particle concentration
would be a Variable with domain ‘negative particle’, secondary domain
‘negative electrode’ and tertiary domain ‘current collector’
domains (dict) – A dictionary equivalent to {‘primary’: domain, auxiliary_domains}. Either
‘domain’ and ‘auxiliary_domains’, or just ‘domains’, should be provided
(not both). In future, the ‘domain’ and ‘auxiliary_domains’ arguments may be
deprecated.
bounds (tuple, optional) – Physical bounds on the variable
print_name (str, optional) – The name to use for printing. Default is None, in which case self.name is used.
scale (float or pybamm.Symbol, optional) – The scale of the variable, used for scaling the model when solving. The state
vector representing this variable will be multiplied by this scale.
Default is 1.
reference (float or pybamm.Symbol, optional) – The reference value of the variable, used for scaling the model when solving.
This value will be added to the state vector representing this variable.
Default is 0.
Differentiate a symbol with respect to a variable. For any symbol that can be
differentiated, return 1 if differentiating with respect to yourself,
self._diff(variable) if variable is in the expression tree of the symbol,
and zero otherwise.
Parameters:
variable (pybamm.Symbol) – The variable with respect to which to differentiate
domain (iterable of str) – list of domains that this variable is valid over
auxiliary_domains (dict) – dictionary of auxiliary domains ({‘secondary’: …, ‘tertiary’: …,
‘quaternary’: …}). For example, for the single particle model, the particle
concentration would be a Variable with domain ‘negative particle’ and secondary
auxiliary domain ‘current collector’. For the DFN, the particle concentration
would be a Variable with domain ‘negative particle’, secondary domain
‘negative electrode’ and tertiary domain ‘current collector’
domains (dict) – A dictionary equivalent to {‘primary’: domain, auxiliary_domains}. Either
‘domain’ and ‘auxiliary_domains’, or just ‘domains’, should be provided
(not both). In future, the ‘domain’ and ‘auxiliary_domains’ arguments may be
deprecated.
bounds (tuple, optional) – Physical bounds on the variable. Included for compatibility with VariableBase,
but ignored.
print_name (str, optional) – The name to use for printing. Default is None, in which case self.name is used.
scale (float or pybamm.Symbol, optional) – The scale of the variable, used for scaling the model when solving. The state
vector representing this variable will be multiplied by this scale.
Default is 1.
reference (float or pybamm.Symbol, optional) – The reference value of the variable, used for scaling the model when solving.
This value will be added to the state vector representing this variable.
Default is 0.
Differentiate a symbol with respect to a variable. For any symbol that can be
differentiated, return 1 if differentiating with respect to yourself,
self._diff(variable) if variable is in the expression tree of the symbol,
and zero otherwise.
Parameters:
variable (pybamm.Symbol) – The variable with respect to which to differentiate
domain (iterable of str) – list of domains that this variable is valid over
auxiliary_domains (dict, optional) – dictionary of auxiliary domains, defaults to empty dict
domains (dict) – A dictionary equivalent to {‘primary’: domain, auxiliary_domains}. Either
‘domain’ and ‘auxiliary_domains’, or just ‘domains’, should be provided
(not both). In future, the ‘domain’ and ‘auxiliary_domains’ arguments may be
deprecated.
A node in the expression tree representing a spatial variable.
Parameters:
name (str) – name of the node (e.g. “x”, “y”, “z”, “r”, “x_n”, “x_s”, “x_p”, “r_n”, “r_p”)
domain (iterable of str) – list of domains that this variable is valid over (e.g. “cartesian”, “spherical
polar”)
auxiliary_domains (dict, optional) – dictionary of auxiliary domains, defaults to empty dict
domains (dict) – A dictionary equivalent to {‘primary’: domain, auxiliary_domains}. Either
‘domain’ and ‘auxiliary_domains’, or just ‘domains’, should be provided
(not both). In future, the ‘domain’ and ‘auxiliary_domains’ arguments may be
deprecated.
domain (iterable of str, optional) – list of domains the parameter is valid over, defaults to empty list
auxiliary_domains (dict, optional) – dictionary of auxiliary domains, defaults to empty dict
domains (dict) – A dictionary equivalent to {‘primary’: domain, auxiliary_domains}. Either
‘domain’ and ‘auxiliary_domains’, or just ‘domains’, should be provided
(not both). In future, the ‘domain’ and ‘auxiliary_domains’ arguments may be
deprecated.
entries_string (str) – String representing the entries (slow to recalculate when copying)
Return coordinate matrices as from coordinate vectors by calling
numpy.meshgrid with keyword arguments ‘kwargs’. For a list of ‘kwargs’
see the numpy meshgrid documentation
domain (iterable of str, optional) – list of domains the parameter is valid over, defaults to empty list
auxiliary_domains (dict of str, optional) – dictionary of auxiliary domains
domains (dict) – A dictionary equivalent to {‘primary’: domain, auxiliary_domains}. Either
‘domain’ and ‘auxiliary_domains’, or just ‘domains’, should be provided
(not both). In future, the ‘domain’ and ‘auxiliary_domains’ arguments may be
deprecated.
evaluation_array (list, optional) – List of boolean arrays representing slices. Default is None, in which case the
evaluation_array is computed from y_slices.
Differentiate a symbol with respect to a variable. For any symbol that can be
differentiated, return 1 if differentiating with respect to yourself,
self._diff(variable) if variable is in the expression tree of the symbol,
and zero otherwise.
Parameters:
variable (pybamm.Symbol) – The variable with respect to which to differentiate
domain (iterable of str, optional) – list of domains the parameter is valid over, defaults to empty list
auxiliary_domains (dict of str, optional) – dictionary of auxiliary domains
domains (dict) – A dictionary equivalent to {‘primary’: domain, auxiliary_domains}. Either
‘domain’ and ‘auxiliary_domains’, or just ‘domains’, should be provided
(not both). In future, the ‘domain’ and ‘auxiliary_domains’ arguments may be
deprecated.
evaluation_array (list, optional) – List of boolean arrays representing slices. Default is None, in which case the
evaluation_array is computed from y_slices.
Differentiate a symbol with respect to a variable. For any symbol that can be
differentiated, return 1 if differentiating with respect to yourself,
self._diff(variable) if variable is in the expression tree of the symbol,
and zero otherwise.
Parameters:
variable (pybamm.Symbol) – The variable with respect to which to differentiate
A node in the expression tree representing a multiplication operator
(Hadamard product). Overloads cases where the “*” operator would usually return a
matrix multiplication (e.g. scipy.sparse.coo.coo_matrix)
A node in the expression tree which represents the inner (or dot) product. This
operator should be used to take the inner product of two mathematical vectors
(as opposed to the computational vectors arrived at post-discretisation) of the
form v = v_x e_x + v_y e_y + v_z e_z where v_x, v_y, v_z are scalars
and e_x, e_y, e_z are x-y-z-directional unit vectors. For v and w mathematical
vectors, inner product returns v_x * w_x + v_y * w_y + v_z * w_z. In addition,
for some spatial discretisations mathematical vector quantities (such as
i = grad(phi) ) are evaluated on a different part of the grid to mathematical
scalars (e.g. for finite volume mathematical scalars are evaluated on the nodes but
mathematical vectors are evaluated on cell edges). Therefore, inner also transfers
the inner product of the vector onto the scalar part of the grid if required
by a particular discretisation.
A node in the expression tree representing a heaviside step function.
This class is semi-private and should not be called directly, use EqualHeaviside
or NotEqualHeaviside instead, or < or <=.
Adding this operation to the rhs or algebraic equations in a model can often cause a
discontinuity in the solution. For the specific cases listed below, this will be
automatically handled by the solver. In the general case, you can explicitly tell
the solver of discontinuities by adding a Event object with
EventType DISCONTINUITY to the model’s list of events.
In the case where the Heaviside function is of the form pybamm.t < x, pybamm.t <=
x, x < pybamm.t, or x <= pybamm.t, where x is any constant equation, this
DISCONTINUITY event will automatically be added by the solver.
Returns the smaller of two objects, possibly with a smoothing approximation.
Not to be confused with pybamm.min(), which returns min function of child.
Softminus approximation to the minimum function. k is the smoothing parameter,
set by pybamm.settings.min_max_smoothing. The recommended value is k=10.
Sigmoidal approximation to the heaviside function. k is the smoothing parameter,
set by pybamm.settings.heaviside_smoothing. The recommended value is k=10.
Note that the concept of deciding which side to pick when left=right does not apply
for this smooth approximation. When left=right, the value is (left+right)/2.
A convenience function for creating (part of) an expression tree representing
a source term. This is necessary for spatial methods where the mass matrix
is not the identity (e.g. finite element formulation with piecwise linear
basis functions). The left child is the symbol representing the source term
and the right child is the symbol of the equation variable (currently, the
finite element formulation in PyBaMM assumes all functions are constructed
using the same basis, and the matrix here is constructed accoutning for the
boundary conditions of the right child). The method returns the matrix-vector
product of the mass matrix (adjusted to account for any Dirichlet boundary
conditions imposed the the right symbol) and the discretised left symbol.
Parameters:
left (Symbol) – The left child node, which represents the expression for the source term.
right (Symbol) – The right child node. This is the symbol whose boundary conditions are
accounted for in the construction of the mass matrix.
boundary (bool, optional) – If True, then the mass matrix should is assembled over the boundary,
corresponding to a source term which only acts on the boundary of the
domain. If False (default), the matrix is assembled over the entire domain,
corresponding to a source term in the bulk.
check_size (bool, optional) – Whether to check if the slice size exceeds the child size. Default is True.
This should always be True when creating a new symbol so that the appropriate
check is performed, but should be False for creating a new copy to avoid
unnecessarily repeating the check.
A node in the expression tree representing a the inner product of the grad
operator with itself. In particular, this is useful in the finite element
formualtion where we only require the (sclar valued) square of the gradient,
and not the gradient itself.
Returns the mass matrix for a given symbol assembled over the boundary of
the domain, accounting for Dirchlet boundary conditions where necessary
(e.g. in the finite element formualtion)
A node in the expression tree representing an integral operator over the
boundary of a domain
\[I = \int_{\partial a}\!f(u)\,du,\]
where \(\partial a\) is the boundary of the domain, and
\(u\in\text{domain boundary}\).
Parameters:
function (pybamm.Symbol) – The function to be integrated (will become self.children[0])
region (str, optional) – The region of the boundary over which to integrate. If region is entire
(default) the integration is carried out over the entire boundary. If
region is negative tab or positive tab then the integration is only
carried out over the appropriate part of the boundary corresponding to
the tab.
A node in the expression tree which evaluates a symbol at a given position in space
in its primary domain. Currently this is only implemented for 1D primary domains.
A node in the expression tree representing an upwinding or downwinding operator.
Usually to be used for better stability in convection-dominated equations.
Smooth approximation to the absolute value function. k is the smoothing parameter,
set by pybamm.settings.abs_smoothing. The recommended value is k=10.
A node in the expression tree representing a concatenation of equations, when we
don’t care about domains. The class pybamm.DomainConcatenation, which
is careful about domains and uses broadcasting where appropriate, should be used
whenever possible instead.
Upon evaluation, equations are concatenated using numpy concatenation.
Parameters:
children (iterable of pybamm.Symbol) – The equations to concatenate
A node in the expression tree representing a concatenation of symbols, being
careful about domains.
It is assumed that each child has a domain, and the final concatenated vector will
respect the sizes and ordering of domains established in mesh keys
Parameters:
children (iterable of pybamm.Symbol) – The symbols to concatenate
full_mesh (pybamm.BaseMesh) – The underlying mesh for discretisation, used to obtain the number of mesh points
in each domain.
copy_this (pybamm.DomainConcatenation (optional)) – if provided, this class is initialised by copying everything except the children
from copy_this. mesh is not used in this case
A node in the expression tree representing a concatenation of sparse
matrices. As with NumpyConcatenation, we don’t care about domains.
The class pybamm.DomainConcatenation, which is careful about
domains and uses broadcasting where appropriate, should be used whenever
possible instead.
Parameters:
children (iterable of Concatenation) – The equations to concatenate
A node in the expression tree representing a broadcasting operator.
Broadcasts a child to a specified domain. After discretisation, this will evaluate
to an array of the right shape for the specified domain.
A node in the expression tree representing a primary broadcasting operator.
Broadcasts in a primary dimension only. That is, makes explicit copies of the
symbol in the domain specified by broadcast_domain. This should be used for
broadcasting from a “larger” scale to a “smaller” scale, for example broadcasting
temperature T(x) from the electrode to the particles, or broadcasting current
collector current i(y, z) from the current collector to the electrodes.
A node in the expression tree representing a secondary broadcasting operator.
Broadcasts in a secondary dimension only. That is, makes explicit copies of the
symbol in the domain specified by broadcast_domain. This should be used for
broadcasting from a “smaller” scale to a “larger” scale, for example broadcasting
SPM particle concentrations c_s(r) from the particles to the electrodes. Note that
this wouldn’t be used to broadcast particle concentrations in the DFN, since these
already depend on both x and r.
broadcast_domain (iterable of str) – Secondary domain for broadcast. This will become the secondary domain of the
symbol, shifting the child’s secondary and tertiary (if present) over by
one position.
A node in the expression tree representing an arbitrary function.
Parameters:
function (method) – A function can have 0 or many inputs. If no inputs are given, self.evaluate()
simply returns func(). Otherwise, self.evaluate(t, y, u) returns
func(child0.evaluate(t, y, u), child1.evaluate(t, y, u), etc).
children (pybamm.Symbol) – The children nodes to apply the function to
derivative (str, optional) – Which derivative to use when differentiating (“autograd” or “derivative”).
Default is “autograd”.
differentiated_function (method, optional) – The function which was differentiated to obtain this one. Default is None.
classpybamm.Interpolant(x, y, children, name=None, interpolator='linear', extrapolate=True, entries_string=None)[source]#
Interpolate data in 1D, 2D, or 3D. Interpolation in 3D requires the input data to be
on a regular grid (as per scipy.interpolate.RegularGridInterpolator).
Parameters:
x (iterable of numpy.ndarray) – The data point coordinates. If 1-D, then this is an array(s) of real values. If,
2D or 3D interpolation, then this is to ba a tuple of 1D arrays (one for each
dimension) which together define the coordinates of the points.
y (numpy.ndarray) – The values of the function to interpolate at the data points. In 2D and 3D, this
should be a matrix of two and three dimensions respectively.
children (iterable of pybamm.Symbol) – Node(s) to use when evaluating the interpolant. Each child corresponds to an
entry of x
name (str, optional) – Name of the interpolant. Default is None, in which case the name “interpolating
function” is given.
interpolator (str, optional) – Which interpolator to use. Can be “linear”, “cubic”, or “pchip”. Default is
“linear”. For 3D interpolation, only “linear” an “cubic” are currently
supported.
extrapolate (bool, optional) – Whether to extrapolate for points that are outside of the parametrisation
range, or return NaN (following default behaviour from scipy). Default is True.
Generally, it is best to set this to be False for 3D interpolation due to
the higher potential for errors in extrapolation.
This function recurses down the tree, computing the Jacobian using
the Jacobians defined in classes derived from pybamm.Symbol. E.g. the
Jacobian of a ‘pybamm.Multiplication’ is computed via the product rule.
If the Jacobian of a symbol has already been calculated, the stored value
is returned.
Note: The Jacobian is the derivative of a symbol with respect to a (slice of)
a State Vector.
Parameters:
symbol (pybamm.Symbol) – The symbol to calculate the Jacobian of
variable (pybamm.Symbol) – The variable with respect to which to differentiate
Loads a discretised, ready to solve model into PyBaMM.
A new pybamm battery model instance will be created, which can be solved
and the results plotted as usual.
Currently only available for pybamm models which have previously been written
out using the save_model() option.
Warning: This only loads in discretised models. If you wish to make edits to the
model or initial conditions, a new model will need to be constructed seperately.
Parameters:
filename (str) – Path to the JSON file containing the serialised model file
battery_model (pybamm.BaseModel (optional)) – PyBaMM model to be created (e.g. pybamm.lithium_ion.SPM), which will
override any model names within the file. If None, the function will look
for the saved object path, present if the original model came from PyBaMM.
Returns:
A PyBaMM model object, of type specified either in the JSON or in
battery_model.
mesh (pybamm.Mesh (optional)) – The mesh the model has been discretised over. Not neccesary to solve
the model when read in, but required to use pybamm’s plotting tools.
variables (pybamm.FuzzyDict (optional)) – The discretised model varaibles. Not necessary to solve a model, but
required to use pybamm’s plotting tools.
filename (str (optional)) – The desired name of the JSON file. If no name is provided, one will be
created based on the model name, and the current datetime.
Below is an overview of all the battery models included in PyBaMM.
Each of the pre-built models contains a reference to the paper in which it is derived.
The models can be customised using the options dictionary defined in the pybamm.BaseBatteryModel (which also provides information on which options and models are compatible)
Visit our examples page
to see how these models can be solved, and compared, using PyBaMM.
A dictionary that maps expressions (variables) to expressions that represent
the algebraic equations. The algebraic expressions are assumed to equate
to zero. Note that all the variables in the model must exist in the keys of
rhs or algebraic.
A dictionary that maps expressions (variables) to expressions that represent
the initial conditions for the state variables y. The initial conditions for
algebraic variables are provided as initial guesses to a root finding algorithm
that calculates consistent initial conditions.
A list of events. Each event can either cause the solver to terminate
(e.g. concentration goes negative), or be used to inform the solver of the
existance of a discontinuity (e.g. discontinuity in the input current).
Contains the Jacobian for the part of the model which contains time derivatives.
If model.use_jacobian is True, the Jacobian is computed automatically during
solver set up.
Contains the Jacobian for the algebraic part of the model. This may be used
by the solver when calculating consistent initial conditions. If
model.use_jacobian is True, the Jacobian is computed automatically during
solver set up.
Discretise model if it isn’t already discretised
This only works with purely 0D models, as otherwise the mesh and spatial
method should be specified by the user
Check that the model is well-posed by executing the following tests:
- Model is not over- or underdetermined, by comparing keys and equations in rhs
and algebraic. Overdetermined if more equations than variables, underdetermined
if more variables than equations.
- There is an initial condition in self.initial_conditions for each
variable/equation pair in self.rhs
- There are appropriate boundary conditions in self.boundary_conditions for each
variable/equation pair in self.rhs and self.algebraic
Parameters:
post_discretisation (boolean) – A flag indicating tests to be skipped after discretisation
Export the constituent parts of the model (rhs, algebraic, initial conditions,
etc) as casadi objects.
Parameters:
variable_names (list) – Variables to be exported alongside the model structure
input_parameter_order (list, optional) – Order in which the input parameters should be stacked.
If input_parameter_order=None and len(self.input_parameters) > 1, a
ValueError is raised (this helps to avoid accidentally using the wrong
order)
Returns:
casadi_dict – Dictionary of {str: casadi object} pairs representing the model in casadi
format
filename (str) – Name of the file to which to save the code
variable_names (list) – Variables to be exported alongside the model structure
input_parameter_order (list, optional) – Order in which the input parameters should be stacked.
If input_parameter_order=None and len(self.input_parameters) > 1, a
ValueError is raised (this helps to avoid accidentally using the wrong
order)
Extracts the parameter information and returns it as a dictionary.
To get a list of all parameter-like objects without extra information,
use model.parameters.
filename (str (optional)) – Accepted file formats - any image format, pdf and tex
Default is None, When None returns all model equations in latex
If not None, returns all model equations in given file format.
newline (bool (optional)) – Default is True, If True, returns every equation in a new line.
If False, returns the list of all the equations.
model (Load) –
pybamm.lithium_ion.SPM() (>>> model =) –
png (This will returns all model equations in) –
model.latexify("equations.png") (>>>) –
latex (This will return all the model equations in) –
Process parameters and discretise a symbol using supplied parameter values
and discretisation. Note: care should be taken if using spatial operators
on dimensional symbols. Operators in pybamm are written in non-dimensional
form, so may need to be scaled by the appropriate length scale. It is
recommended to use this method on non-dimensional symbols.
Update initial conditions with the final states from a Solution object or from
a dictionary.
This assumes that, for each variable in self.initial_conditions, there is a
corresponding variable in the solution with the same name and size.
Parameters:
solution (pybamm.Solution, or dict) – The solution to use to initialize the model
inplace (bool, optional) – Whether to modify the model inplace or create a new model (default True)
return_type (str, optional) – Whether to return the model (default) or initial conditions (“ics”)
Base model class with some default settings and required variables
Parameters:
options (dict-like, optional) – A dictionary of options to be passed to the model. If this is a dict (and not
a subtype of dict), it will be processed by pybamm.BatteryModelOptions
to ensure that the options are valid. If this is a subtype of dict, it is
assumed that the options have already been processed and are valid. This allows
for the use of custom options classes. The default options are given by
pybamm.BatteryModelOptions.
name (str, optional) – The name of the model. The default is “Unnamed battery model”.
A dictionary of options to be passed to the model. The options that can
be set are listed below. Note that not all of the options are compatible with
each other and with all of the models implemented in PyBaMM. Each option is
optional and takes a default value if not provided.
In general, the option provided must be a string, but there are some cases
where a 2-tuple of strings can be provided instead to indicate a different
option for the negative and positive electrodes.
“calculate discharge energy”: str
Whether to calculate the discharge energy, throughput energy and
throughput capacity in addition to discharge capacity. Must be one of
“true” or “false”. “false” is the default, since calculating discharge
energy can be computationally expensive for simple models like SPM.
“cell geometry”str
Sets the geometry of the cell. Can be “arbitrary” (default) or
“pouch”. The arbitrary geometry option solves a 1D electrochemical
model with prescribed cell volume and cross-sectional area, and
(if thermal effects are included) solves a lumped thermal model
with prescribed surface area for cooling.
“calculate heat source for isothermal models”str
Whether to calculate the heat source terms during isothermal operation.
Can be “true” or “false”. If “false”, the heat source terms are set
to zero. Default is “false” since this option may require additional
parameters not needed by the electrochemical model.
“convection”str
Whether to include the effects of convection in the model. Can be
“none” (default), “uniform transverse” or “full transverse”.
Must be “none” for lithium-ion models.
“current collector”str
Sets the current collector model to use. Can be “uniform” (default),
“potential pair” or “potential pair quite conductive”.
“diffusivity”str
Sets the model for the diffusivity. Can be “single”
(default) or “current sigmoid”. A 2-tuple can be provided for different
behaviour in negative and positive electrodes.
“dimensionality”int
Sets the dimension of the current collector problem. Can be 0
(default), 1 or 2.
“electrolyte conductivity”str
Can be “default” (default), “full”, “leading order”, “composite” or
“integrated”.
“exchange-current density”str
Sets the model for the exchange-current density. Can be “single”
(default) or “current sigmoid”. A 2-tuple can be provided for different
behaviour in negative and positive electrodes.
“hydrolysis”str
Whether to include hydrolysis in the model. Only implemented for
lead-acid models. Can be “false” (default) or “true”. If “true”, then
“surface form” cannot be ‘false’.
“intercalation kinetics”str
Model for intercalation kinetics. Can be “symmetric Butler-Volmer”
(default), “asymmetric Butler-Volmer”, “linear”, “Marcus”,
“Marcus-Hush-Chidsey” (which uses the asymptotic form from Zeng 2014),
or “MSMR” (which uses the form from Baker 2018). A 2-tuple can be
provided for different behaviour in negative and positive electrodes.
“interface utilisation”: str
Can be “full” (default), “constant”, or “current-driven”.
“lithium plating”str
Sets the model for lithium plating. Can be “none” (default),
“reversible”, “partially reversible”, or “irreversible”.
“lithium plating porosity change”str
Whether to include porosity change due to lithium plating, can be
“false” (default) or “true”.
“loss of active material”str
Sets the model for loss of active material. Can be “none” (default),
“stress-driven”, “reaction-driven”, “current-driven”, or
“stress and reaction-driven”.
A 2-tuple can be provided for different behaviour in negative and
positive electrodes.
“number of MSMR reactions”str
Sets the number of reactions to use in the MSMR model in each electrode.
A 2-tuple can be provided to give a different number of reactions in
the negative and positive electrodes. Default is “none”. Can be any
2-tuple of strings of integers. For example, set to (“6”, “4”) for a
negative electrode with 6 reactions and a positive electrode with 4
reactions.
“open-circuit potential”str
Sets the model for the open circuit potential. Can be “single”
(default), “current sigmoid”, or “MSMR”. If “MSMR” then the “particle”
option must also be “MSMR”. A 2-tuple can be provided for different
behaviour in negative and positive electrodes.
“operating mode”str
Sets the operating mode for the model. This determines how the current
is set. Can be:
“current” (default) : the current is explicity supplied
“voltage”/”power”/”resistance” : solve an algebraic equation for current such that voltage/power/resistance is correct
“differential power”/”differential resistance” : solve a differential equation for the power or resistance
“explicit power”/”explicit resistance” : current is defined in terms of the voltage such that power/resistance is correct
“CCCV”: a special implementation of the common constant-current constant-voltage charging protocol, via an ODE for the current
callable : if a callable is given as this option, the function defines the residual of an algebraic equation. The applied current will be solved for such that the algebraic constraint is satisfied.
“particle”str
Sets the submodel to use to describe behaviour within the particle.
Can be “Fickian diffusion” (default), “uniform profile”,
“quadratic profile”, “quartic profile”, or “MSMR”. If “MSMR” then the
“open-circuit potential” option must also be “MSMR”. A 2-tuple can be
provided for different behaviour in negative and positive electrodes.
“particle mechanics”str
Sets the model to account for mechanical effects such as particle
swelling and cracking. Can be “none” (default), “swelling only”,
or “swelling and cracking”.
A 2-tuple can be provided for different behaviour in negative and
positive electrodes.
“particle phases”: str
Number of phases present in the electrode. A 2-tuple can be provided for
different behaviour in negative and positive electrodes.
For example, set to (“2”, “1”) for a negative electrode with 2 phases,
e.g. graphite and silicon.
“particle shape”str
Sets the model shape of the electrode particles. This is used to
calculate the surface area to volume ratio. Can be “spherical”
(default), or “no particles”.
“particle size”str
Sets the model to include a single active particle size or a
distribution of sizes at any macroscale location. Can be “single”
(default) or “distribution”. Option applies to both electrodes.
Set the submodel for additional term in the overpotential due to SEI.
The default value is “none” if the “SEI” option is “none”, and
“distributed” otherwise. This is because the “distributed” model is more
complex than the model with no additional resistance, which adds
unnecessary complexity if there is no SEI in the first place
“none”: no additional resistance
\[\eta_r = \frac{F}{RT} * (\phi_s - \phi_e - U)\]
“distributed”: properly included additional resistance term
“average”: constant additional resistance term (approximation to the true model). This model can give similar results to the “distributed” case without needing to make j an algebraic state
Whether to include SEI growth on particle cracks, can be “false”
(default) or “true”.
“SEI porosity change”str
Whether to include porosity change due to SEI formation, can be “false”
(default) or “true”.
“stress-induced diffusion”str
Whether to include stress-induced diffusion, can be “false” or “true”.
The default is “false” if “particle mechanics” is “none” and “true”
otherwise. A 2-tuple can be provided for different behaviour in negative
and positive electrodes.
“surface form”str
Whether to use the surface formulation of the problem. Can be “false”
(default), “differential” or “algebraic”.
“thermal”str
Sets the thermal model to use. Can be “isothermal” (default), “lumped”,
“x-lumped”, or “x-full”. The ‘cell geometry’ option must be set to
‘pouch’ for ‘x-lumped’ or ‘x-full’ to be valid. Using the ‘x-lumped’
option with ‘dimensionality’ set to 0 is equivalent to using the
‘lumped’ option.
“total interfacial current density as a state”str
Whether to make a state for the total interfacial current density and
solve an algebraic equation for it. Default is “false”, unless “SEI film
resistance” is distributed in which case it is automatically set to
“true”.
“working electrode”str
Can be “both” (default) for a standard battery or “positive” for a
half-cell where the negative electrode is replaced with a lithium metal
counter electrode.
“x-average side reactions”: str
Whether to average the side reactions (SEI growth, lithium plating and
the respective porosity change) over the x-axis in Single Particle
Models, can be “false” or “true”. Default is “false” for SPMe and
“true” for SPM.
TERMINATION indicates an event that will terminate the solver, the expression should
return 0 when the event is triggered
DISCONTINUITY indicates an expected discontinuity in the solution, the expression
should return the time that the discontinuity occurs. The solver will integrate up
to the discontinuity and then restart just after the discontinuity.
INTERPOLANT_EXTRAPOLATION indicates that a pybamm.Interpolant object has been
evaluated outside of the range.
SWITCH indicates an event switch that is used in CasADI “fast with events” model.
Overwrites default parameters from Base Model with default parameters for
lithium-ion models
Parameters:
options (dict-like, optional) – A dictionary of options to be passed to the model. If this is a dict (and not
a subtype of dict), it will be processed by pybamm.BatteryModelOptions
to ensure that the options are valid. If this is a subtype of dict, it is
assumed that the options have already been processed and are valid. This allows
for the use of custom options classes. The default options are given by
pybamm.BatteryModelOptions.
name (str, optional) – The name of the model. The default is “Unnamed battery model”.
build (bool, optional) – Whether to build the model on instantiation. Default is True. Setting this
option to False allows users to change any number of the submodels before
building the complete model (submodels cannot be changed after the model is
built).
Insert a reference electrode to measure the electrolyte potential at a given
position in space. Adds model variables for the electrolyte potential at the
reference electrode and for the potential difference between the electrode
potentials measured at the electrode/current collector interface and the
reference electrode. Only implemented for 1D models (i.e. where the
‘dimensionality’ option is 0).
Parameters:
position (pybamm.Symbol, optional) – The position in space at which to measure the electrolyte potential. If
None, defaults to the mid-point of the separator.
Single Particle Model (SPM) model of a lithium-ion battery, from
Marquis et al.[1].
This class differs from the pybamm.lithium_ion.SPM model class in that it
shows the whole model in a single class. This comes at the cost of flexibility in
combining different physical effects, and in general the main SPM class should be
used instead.
Single Particle Model with Electrolyte (SPMe) of a lithium-ion battery, from
Marquis et al.[1]. Inherits most submodels from SPM, only modifies
potentials and electrolyte. See pybamm.lithium_ion.BaseModel for more
details.
Examples
>>> model=pybamm.lithium_ion.SPMe()>>> model.name'Single Particle Model with electrolyte'
Many-Particle Model (MPM) of a lithium-ion battery with particle-size
distributions for each electrode, from Kirk et al.[1].
See pybamm.lithium_ion.BaseModel for more details.
Doyle-Fuller-Newman (DFN) model of a lithium-ion battery, from
Marquis et al.[1].
This class differs from the pybamm.lithium_ion.DFN model class in that it
shows the whole model in a single class. This comes at the cost of flexibility in
comparing different physical effects, and in general the main DFN class should be
used instead.
Doyle-Fuller-Newman (DFN) model of a lithium-ion battery with composite particles
of graphite and silicon, from Ai et al.[2].
This class differs from the pybamm.lithium_ion.DFN model class in that it
shows the whole model in a single class. This comes at the cost of flexibility in
comparing different physical effects, and in general the main DFN class should be
used instead.
Doyle-Fuller-Newman (DFN) model of a lithium-ion battery with lithium counter
electrode, adapted from Doyle et al.[3].
This class differs from the pybamm.lithium_ion.BasicDFN model class in
that it is for a cell with a lithium counter electrode (half cell). This is a
feature under development (for example, it cannot be used with the Experiment class
for the moment) and in the future it will be incorporated as a standard model with
the full functionality.
The electrode labeled “positive electrode” is the working electrode, and the
electrode labeled “negative electrode” is the counter electrode.
This facilitates compatibility with the full-cell models.
Parameters:
options (dict) – A dictionary of options to be passed to the model. For the half cell it should
include which is the working electrode.
Newman-Tobias model of a lithium-ion battery based on the formulation in
Newman and Tobias[1]. This model assumes a uniform concentration profile in the
electrolyte. Unlike the model posed in Newman and Tobias[1], this model accounts
for nonlinear Butler-Volmer kinetics. It also tracks the average concentration in
the solid phase in each electrode, which is equivalent to including an equation for
the local state of charge as in Chu et al.[2]. The user can pass the
“particle” option to include mass transport in the particles.
Class used to check if the electrode SOH model is feasible, and solve it if it is.
Parameters:
parameter_values (pybamm.ParameterValues.Parameters) – The parameters of the simulation
param (pybamm.LithiumIonParameters, optional) – Specific instance of the symbolic lithium-ion parameter class. If not provided,
the default set of symbolic lithium-ion parameters will be used.
known_value (str, optional) – The known value needed to complete the electrode SOH model.
Can be “cyclable lithium capacity” (default) or “cell capacity”.
options (dict-like, optional) – A dictionary of options to be passed to the model, see
pybamm.BatteryModelOptions.
Calculate initial open-circuit potentials to start off the simulation at a
particular state of charge, given voltage limits, open-circuit potentials, etc
defined by parameter_values
Parameters:
initial_value (float) – Target SOC, must be between 0 and 1.
Returns:
The initial open-circuit potentials at the desired initial state of charge
Calculate initial stoichiometries to start off the simulation at a particular
state of charge, given voltage limits, open-circuit potentials, etc defined by
parameter_values
Parameters:
initial_value (float) – Target initial value.
If integer, interpreted as SOC, must be between 0 and 1.
If string e.g. “4 V”, interpreted as voltage,
must be between V_min and V_max.
Returns:
The initial stoichiometries that give the desired initial state of charge
Calculate initial stoichiometries to start off the simulation at a particular
state of charge, given voltage limits, open-circuit potentials, etc defined by
parameter_values
Parameters:
initial_value (float) – Target initial value.
If integer, interpreted as SOC, must be between 0 and 1.
If string e.g. “4 V”, interpreted as voltage, must be between V_min and V_max.
parameter_values (pybamm.ParameterValues) – The parameter values class that will be used for the simulation. Required for
calculating appropriate initial stoichiometries.
param (pybamm.LithiumIonParameters, optional) – The symbolic parameter set to use for the simulation.
If not provided, the default parameter set will be used.
known_value (str, optional) – The known value needed to complete the electrode SOH model.
Can be “cyclable lithium capacity” (default) or “cell capacity”.
options (dict-like, optional) – A dictionary of options to be passed to the model, see
pybamm.BatteryModelOptions.
Returns:
The initial stoichiometries that give the desired initial state of charge
Calculate min/max stoichiometries
given voltage limits, open-circuit potentials, etc defined by parameter_values
Parameters:
parameter_values (pybamm.ParameterValues) – The parameter values class that will be used for the simulation. Required for
calculating appropriate initial stoichiometries.
param (pybamm.LithiumIonParameters, optional) – The symbolic parameter set to use for the simulation.
If not provided, the default parameter set will be used.
known_value (str, optional) – The known value needed to complete the electrode SOH model.
Can be “cyclable lithium capacity” (default) or “cell capacity”.
options (dict-like, optional) – A dictionary of options to be passed to the model, see
pybamm.BatteryModelOptions.
Calculate initial open-circuit potentials to start off the simulation at a
particular state of charge, given voltage limits, open-circuit potentials, etc
defined by parameter_values
Parameters:
initial_value (float) – Target initial value.
If integer, interpreted as SOC, must be between 0 and 1.
If string e.g. “4 V”, interpreted as voltage, must be between V_min and V_max.
parameter_values (pybamm.ParameterValues) – The parameter values class that will be used for the simulation. Required for
calculating appropriate initial stoichiometries.
param (pybamm.LithiumIonParameters, optional) – The symbolic parameter set to use for the simulation.
If not provided, the default parameter set will be used.
known_value (str, optional) – The known value needed to complete the electrode SOH model.
Can be “cyclable lithium capacity” (default) or “cell capacity”.
options (dict-like, optional) – A dictionary of options to be passed to the model, see
pybamm.BatteryModelOptions.
Returns:
The initial electrode OCPs that give the desired initial state of charge
Calculate min/max open-circuit potentials
given voltage limits, open-circuit potentials, etc defined by parameter_values
Parameters:
parameter_values (pybamm.ParameterValues) – The parameter values class that will be used for the simulation. Required for
calculating appropriate initial open-circuit potentials.
param (pybamm.LithiumIonParameters, optional) – The symbolic parameter set to use for the simulation.
If not provided, the default parameter set will be used.
known_value (str, optional) – The known value needed to complete the electrode SOH model.
Can be “cyclable lithium capacity” (default) or “cell capacity”.
options (dict-like, optional) – A dictionary of options to be passed to the model, see
pybamm.BatteryModelOptions.
Overwrites default parameters from Base Model with default parameters for
lead-acid models
Parameters:
options (dict-like, optional) – A dictionary of options to be passed to the model. If this is a dict (and not
a subtype of dict), it will be processed by pybamm.BatteryModelOptions
to ensure that the options are valid. If this is a subtype of dict, it is
assumed that the options have already been processed and are valid. This allows
for the use of custom options classes. The default options are given by
pybamm.BatteryModelOptions.
name (str, optional) – The name of the model. The default is “Unnamed battery model”.
build (bool, optional) – Whether to build the model on instantiation. Default is True. Setting this
option to False allows users to change any number of the submodels before
building the complete model (submodels cannot be changed after the model is
built).
Porous electrode model for lead-acid, from Sulzer et al.[1].
This class differs from the pybamm.lead_acid.Full model class in that it
shows the whole model in a single class. This comes at the cost of flexibility in
comparing different physical effects, and in general the main DFN class should be
used instead.
The classical Thevenin Equivalent Circuit Model of a battery as
described in, for example, Barletta et al.[1].
This equivalent circuit model consists of an OCV element, a resistor
element, and a number of RC elements (by default 1). The model is
coupled to two lumped thermal models, one for the cell and
one for the surrounding jig. Heat generation terms for each element
follow equation (1) of Nieto et al.[2].
Parameters:
name (str, optional) – The name of the model. The default is
“Thevenin Equivalent Circuit Model”.
A dictionary of options to be passed to the model. The default is None.
Possible options are:
”number of rc elements”str
The number of RC elements to be added to the model. The default is 1.
”calculate discharge energy”: str
Whether to calculate the discharge energy, throughput energy and
throughput capacity in addition to discharge capacity. Must be one of
“true” or “false”. “false” is the default, since calculating discharge
energy can be computationally expensive for simple models like SPM.
”operating mode”str
Sets the operating mode for the model. This determines how the current
is set. Can be:
”current” (default) : the current is explicity supplied
”voltage”/”power”/”resistance” : solve an algebraic equation for current such that voltage/power/resistance is correct
”differential power”/”differential resistance” : solve a differential equation for the power or resistance
”CCCV”: a special implementation of the common constant-current constant-voltage charging protocol, via an ODE for the current
callable : if a callable is given as this option, the function defines the residual of an algebraic equation. The applied current will be solved for such that the algebraic constraint is satisfied.
build (bool, optional) – Whether to build the model on instantiation. Default is True. Setting this
option to False allows users to change any number of the submodels before
building the complete model (submodels cannot be changed after the model is
built).
The base class for all submodels. All submodels inherit from this class and must
only provide public methods which overwrite those in this base class. Any methods
added to a submodel that do not overwrite those in this bass class are made
private with the prefix ‘_’, providing a consistent public interface for all
submodels.
Parameters:
param (parameter class) – The model parameter symbols
domain (str) – The domain of the model either ‘Negative’ or ‘Positive’
name (str) – A string giving the name of the submodel
external (bool, optional) – Whether the variables defined by the submodel will be provided externally
by the users. Default is ‘False’.
A dictionary that maps expressions (variables) to expressions that represent
the algebraic equations. The algebraic expressions are assumed to equate
to zero. Note that all the variables in the model must exist in the keys of
rhs or algebraic.
A dictionary that maps expressions (variables) to expressions that represent
the initial conditions for the state variables y. The initial conditions for
algebraic variables are provided as initial guesses to a root finding algorithm
that calculates consistent initial conditions.
A list of events. Each event can either cause the solver to terminate
(e.g. concentration goes negative), or be used to inform the solver of the
existance of a discontinuity (e.g. discontinuity in the input current)
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the differential equations which do not contain a time
derivative. Note: this method modifies the state of self.algebraic. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set events related to the state of submodel variable. Note: this
method modifies the state of self.events. Unless overwritten by a submodel, the
default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A model which calculates the effective Ohmic resistance of the current
collectors in the limit of large electrical conductivity. For details see
Timms et al.[1].
Note that this formulation assumes uniform potential across the tabs. See
pybamm.AlternativeEffectiveResistance2D for the formulation that
assumes a uniform current density at the tabs (in 1D the two formulations
are equivalent).
Calculates the potentials in the current collector and the terminal
voltage given the average voltage and current.
Note: This takes in the processed V_av and I_av from a 1D simulation
representing the average cell behaviour and returns a dictionary of
processed potentials.
A model which calculates the effective Ohmic resistance of the 2D current
collectors in the limit of large electrical conductivity. This model assumes
a uniform current density at the tabs and the solution is computed by first
solving and auxilliary problem which is the related to the resistances.
Calculates the potentials in the current collector given
the average voltage and current.
Note: This takes in the processed V_av and I_av from a 1D simulation
representing the average cell behaviour and returns a dictionary of
processed potentials.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A submodel for Ohm’s law plus conservation of current in the current collectors.
For details on the potential pair formulation see Timms et al.[1] and
Marquis[2].
Parameters:
param (parameter class) – The parameters to use for this submodel
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the differential equations which do not contain a time
derivative. Note: this method modifies the state of self.algebraic. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
The convection submodels are split up into “through-cell”, which is the x-direction
problem in the electrode domains, and “transverse”, which is the z-direction problem in the
separator domain
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the differential equations which do not contain a time
derivative. Note: this method modifies the state of self.algebraic. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the differential equations which do not contain a time
derivative. Note: this method modifies the state of self.algebraic. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
options (dict, optional) – A dictionary of options to be passed to the model.
set_positive_potential (bool, optional) – If True the battery model sets the positive potential based on the current.
If False, the potential is specified by the user. Default is True.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
options (dict, optional) – A dictionary of options to be passed to the model.
set_positive_potential (bool, optional) – If True the battery model sets the positve potential based on the current.
If False, the potential is specified by the user. Default is True.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
An explicit composite leading and first order solution to solid phase
current conservation with ohm’s law. Note that the returned current density is
only the leading order approximation.
Parameters:
param (parameter class) – The parameters to use for this submodel
domain (str) – Either ‘Negative electrode’ or ‘Positive electrode’
options (dict, optional) – A dictionary of options to be passed to the model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the differential equations which do not contain a time
derivative. Note: this method modifies the state of self.algebraic. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
Leading-order model for conservation of charge in the electrolyte
employing the Stefan-Maxwell constitutive equations. (Leading refers
to leading-order in the asymptotic reduction)
Parameters:
param (parameter class) – The parameters to use for this submodel
domain (str, optional) – The domain in which the model holds
options (dict, optional) – A dictionary of options to be passed to the model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
Integrated model for conservation of charge in the electrolyte derived from
integrating the Stefan-Maxwell constitutive equations, from
Brosa Planella et al.[1].
Parameters:
param (parameter class) – The parameters to use for this submodel
domain (str, optional) – The domain in which the model holds
options (dict, optional) – A dictionary of options to be passed to the model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
Full model for conservation of charge in the electrolyte employing the
Stefan-Maxwell constitutive equations. (Full refers to unreduced by
asymptotic methods)
Parameters:
param (parameter class) – The parameters to use for this submodel
options (dict, optional) – A dictionary of options to be passed to the model.
Discretise model if it isn’t already discretised
This only works with purely 0D models, as otherwise the mesh and spatial
method should be specified by the user
Check that the model is well-posed by executing the following tests:
- Model is not over- or underdetermined, by comparing keys and equations in rhs
and algebraic. Overdetermined if more equations than variables, underdetermined
if more variables than equations.
- There is an initial condition in self.initial_conditions for each
variable/equation pair in self.rhs
- There are appropriate boundary conditions in self.boundary_conditions for each
variable/equation pair in self.rhs and self.algebraic
Parameters:
post_discretisation (boolean) – A flag indicating tests to be skipped after discretisation
Export the constituent parts of the model (rhs, algebraic, initial conditions,
etc) as casadi objects.
Parameters:
variable_names (list) – Variables to be exported alongside the model structure
input_parameter_order (list, optional) – Order in which the input parameters should be stacked.
If input_parameter_order=None and len(self.input_parameters) > 1, a
ValueError is raised (this helps to avoid accidentally using the wrong
order)
Returns:
casadi_dict – Dictionary of {str: casadi object} pairs representing the model in casadi
format
filename (str) – Name of the file to which to save the code
variable_names (list) – Variables to be exported alongside the model structure
input_parameter_order (list, optional) – Order in which the input parameters should be stacked.
If input_parameter_order=None and len(self.input_parameters) > 1, a
ValueError is raised (this helps to avoid accidentally using the wrong
order)
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
Extracts the parameter information and returns it as a dictionary.
To get a list of all parameter-like objects without extra information,
use model.parameters.
filename (str (optional)) – Accepted file formats - any image format, pdf and tex
Default is None, When None returns all model equations in latex
If not None, returns all model equations in given file format.
newline (bool (optional)) – Default is True, If True, returns every equation in a new line.
If False, returns the list of all the equations.
model (Load) –
pybamm.lithium_ion.SPM() (>>> model =) –
png (This will returns all model equations in) –
model.latexify("equations.png") (>>>) –
latex (This will return all the model equations in) –
Process parameters and discretise a symbol using supplied parameter values
and discretisation. Note: care should be taken if using spatial operators
on dimensional symbols. Operators in pybamm are written in non-dimensional
form, so may need to be scaled by the appropriate length scale. It is
recommended to use this method on non-dimensional symbols.
A method to set the differential equations which do not contain a time
derivative. Note: this method modifies the state of self.algebraic. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set events related to the state of submodel variable. Note: this
method modifies the state of self.events. Unless overwritten by a submodel, the
default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
Update initial conditions with the final states from a Solution object or from
a dictionary.
This assumes that, for each variable in self.initial_conditions, there is a
corresponding variable in the solution with the same name and size.
Parameters:
solution (pybamm.Solution, or dict) – The solution to use to initialize the model
inplace (bool, optional) – Whether to modify the model inplace or create a new model (default True)
return_type (str, optional) – Whether to return the model (default) or initial conditions (“ics”)
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
Full model for conservation of charge in the electrolyte employing the
Stefan-Maxwell constitutive equations and where capacitance is present.
(Full refers to unreduced by asymptotic methods)
Parameters:
param (parameter class) – The parameters to use for this submodel
options (dict, optional) – A dictionary of options to be passed to the model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
Full model for conservation of charge in the electrolyte employing the
Stefan-Maxwell constitutive equations. (Full refers to unreduced by
asymptotic methods)
Parameters:
param (parameter class) – The parameters to use for this submodel
options (dict, optional) – A dictionary of options to be passed to the model.
A method to set the differential equations which do not contain a time
derivative. Note: this method modifies the state of self.algebraic. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
Leading-order model for conservation of charge in the electrolyte employing the
Stefan-Maxwell constitutive equations employing the surface potential difference
formulation and where capacitance is present.
Parameters:
param (parameter class) – The parameters to use for this submodel
options (dict, optional) – A dictionary of options to be passed to the model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
Leading-order model for conservation of charge in the electrolyte employing the
Stefan-Maxwell constitutive equations employing the surface potential difference
formulation.
Parameters:
param (parameter class) – The parameters to use for this submodel
options (dict, optional) – A dictionary of options to be passed to the model.
A method to set the differential equations which do not contain a time
derivative. Note: this method modifies the state of self.algebraic. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
We provide boundary conditions even though the concentration is constant
so that the gradient of the concentration has the correct shape after
discretisation.
A method to set events related to the state of submodel variable. Note: this
method modifies the state of self.events. Unless overwritten by a submodel, the
default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
Class for conservation of mass in the electrolyte employing the
Stefan-Maxwell constitutive equations. (Leading refers to leading order
of asymptotic reduction)
Parameters:
param (parameter class) – The parameters to use for this submodel
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
Class for conservation of mass in the electrolyte employing the
Stefan-Maxwell constitutive equations. (Full refers to unreduced by
asymptotic methods)
Parameters:
param (parameter class) – The parameters to use for this submodel
options (dict, optional) – A dictionary of options to be passed to the model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
Models to enforce different boundary conditions (as imposed by an imaginary external
circuit) such as constant current, constant voltage, constant power, or any other
relationship between the current and voltage. “Current control” enforces these directly
through boundary conditions, while “Function control”
submodels add an algebraic equation (for the current) and hence can be used to set any
variable to be constant.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the differential equations which do not contain a time
derivative. Note: this method modifies the state of self.algebraic. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
Get variables associated with interfacial current over the whole cell domain
This function also creates the “total source term” variables by summing all
the reactions.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set events related to the state of submodel variable. Note: this
method modifies the state of self.events. Unless overwritten by a submodel, the
default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the differential equations which do not contain a time
derivative. Note: this method modifies the state of self.algebraic. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
Submodel which implements the forward Butler-Volmer equation in the MSMR
formulation in which the interfacial current density is summed over all
reactions.
Parameters:
param (parameter class) – model parameters
domain (str) – The domain to implement the model, either: ‘Negative’ or ‘Positive’.
reaction (str) – The name of the reaction being implemented
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A submodel that implements the inverted form of the Butler-Volmer relation to
solve for the reaction overpotential.
Parameters:
param – Model parameters
domain (iter of str, optional) – The domain(s) in which to compute the interfacial current.
reaction (str) – The name of the reaction being implemented
options (dict) – A dictionary of options to be passed to the model. In this case “SEI film
resistance” is the important option. See pybamm.BaseBatteryModel
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
Class for open-circuit potential within the Multi-Species Multi-Reaction
framework Baker and Verbrugge[1]. The thermodynamic model is presented in
Verbrugge et al.[2], along with parameter values for a number of
substitutional materials.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
Class for conservation of mass of oxygen. (Full refers to unreduced by
asymptotic methods)
In this model, extremely fast oxygen kinetics in the negative electrode imposes
zero oxygen concentration there, and so the oxygen variable only lives in the
separator and positive electrode. The boundary condition at the negative electrode/
separator interface is homogeneous Dirichlet.
Parameters:
param (parameter class) – The parameters to use for this submodel
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
Class for molar conservation in particles employing Fick’s
law, assuming a polynomial concentration profile in r, and allowing variation
in the electrode domain. Model equations from Subramanian et al.[1].
Parameters:
param (parameter class) – The parameters to use for this submodel
domain (str) – The domain of the model either ‘Negative’ or ‘Positive’
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the differential equations which do not contain a time
derivative. Note: this method modifies the state of self.algebraic. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
Class for molar conservation in a single x-averaged particle employing Fick’s law,
with an assumed polynomial concentration profile in r. Model equations from
Subramanian et al.[1].
Parameters:
param (parameter class) – The parameters to use for this submodel
domain (str) – The domain of the model either ‘Negative’ or ‘Positive’
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the differential equations which do not contain a time
derivative. Note: this method modifies the state of self.algebraic. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
Class for molar conservation in particles within the Multi-Species Multi-Reaction
framework Baker and Verbrugge[1]. The thermodynamic model is presented in
Verbrugge et al.[2], along with parameter values for a number of
substitutional materials.
Parameters:
param (parameter class) – The parameters to use for this submodel
domain (str) – The domain of the model either ‘Negative’ or ‘Positive’
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set events related to the state of submodel variable. Note: this
method modifies the state of self.events. Unless overwritten by a submodel, the
default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set events related to the state of submodel variable. Note: this
method modifies the state of self.events. Unless overwritten by a submodel, the
default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A method to set events related to the state of submodel variable. Note: this
method modifies the state of self.events. Unless overwritten by a submodel, the
default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set events related to the state of submodel variable. Note: this
method modifies the state of self.events. Unless overwritten by a submodel, the
default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
Class for one-dimensional (x-direction) thermal submodel.
Note: this model assumes infinitely large electrical and thermal conductivity
in the current collectors, so that the contribution to the Ohmic heating
from the current collectors is zero and the boundary conditions are applied
at the edges of the electrodes (at x=0 and x=1, in non-dimensional coordinates).
For more information see Timms et al.[1] and Marquis[2].
Parameters:
param (parameter class) – The parameters to use for this submodel
options (dict, optional) – A dictionary of options to be passed to the model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
Class for one-dimensional thermal submodel for use in the “1+1D” pouch cell
model. The thermal model is averaged in the x-direction and is therefore referred
to as ‘x-lumped’. For more information see Timms et al.[1] and
Marquis[2].
Parameters:
param (parameter class) – The parameters to use for this submodel
options (dict, optional) – A dictionary of options to be passed to the model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
Class for two-dimensional thermal submodel for use in the “2+1D” pouch cell
model. The thermal model is averaged in the x-direction and is therefore referred
to as ‘x-lumped’. For more information see Timms et al.[1] and
Marquis[2].
Parameters:
param (parameter class) – The parameters to use for this submodel
options (dict, optional) – A dictionary of options to be passed to the model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the boundary conditions for the submodel. Note: this method
modifies the state of self.boundary_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set events related to the state of submodel variable. Note: this
method modifies the state of self.events. Unless overwritten by a submodel, the
default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A public method that creates and returns the variables in a submodel which can
be created independent of other submodels. For example, the electrolyte
concentration variables can be created independent of whether any other
variables have been defined in the model. As a rule, if a variable can be
created without variables from other submodels, then it should be placed in
this method.
Returns:
The variables created by the submodel which are independent of variables in
other submodels.
A method to set the initial conditions for the submodel. Note: this method
modifies the state of self.initial_conditions. Unless overwritten by a
submodel, the default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
A method to set the right hand side of the differential equations which contain
a time derivative. Note: this method modifies the state of self.rhs. Unless
overwritten by a submodel, the default behaviour of ‘pass’ is used as
implemented in pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
Voltage model for use with equivalent
circuits. This model is used to calculate
the voltage and total overpotentials
from the other elements in the circuit.
Parameters:
param (parameter class) – The parameters to use for this submodel
options (dict, optional) – A dictionary of options to be passed to the model.
A public method that creates and returns the variables in a submodel which
require variables in other submodels to be set first. For example, the
exchange current density requires the concentration in the electrolyte to
be created before it can be created. If a variable can be created independent
of other submodels then it should be created in ‘get_fundamental_variables’
instead of this method.
Parameters:
variables (dict) – The variables in the whole model.
Returns:
The variables created in this submodel which depend on variables in
other submodels.
A method to set events related to the state of submodel variable. Note: this
method modifies the state of self.events. Unless overwritten by a submodel, the
default behaviour of ‘pass’ is used as implemented in
pybamm.BaseSubModel.
Parameters:
variables (dict) – The variables in the whole model.
Note that this class does not inherit directly from the python dictionary class as
this causes issues with saving and loading simulations.
Parameters:
values (dict or string) – Explicit set of parameters, or reference to an inbuilt parameter set
If string and matches one of the inbuilt parameter sets, returns that parameter
set.
Print a dictionary of evaluated parameters to an output file
Parameters:
evaluated_parameters (defaultdict) – The evaluated parameters, for further processing if needed
output_file (string, optional) – The file to print parameters to. If None, the parameters are not printed,
and this function simply acts as a test that all the parameters can be
evaluated
Return dictionary of evaluated parameters, and optionally print these evaluated
parameters to an output file.
Parameters:
parameters (class or dict containing pybamm.Parameter objects) – Class or dictionary containing all the parameters to be evaluated
output_file (string, optional) – The file to print parameters to. If None, the parameters are not printed,
and this function simply acts as a test that all the parameters can be
evaluated, and returns the dictionary of evaluated parameters.
Returns:
evaluated_parameters – The evaluated parameters, for further processing if needed
Return type:
defaultdict
Notes
A C-rate of 1 C is the current required to fully discharge the battery in 1
hour, 2 C is current to discharge the battery in 0.5 hours, etc
Process boundary conditions for a model
Boundary conditions are dictionaries {“left”: left bc, “right”: right bc}
in general, but may be imposed on the tabs (or not on the tab) for a
small number of variables, e.g. {“negative tab”: neg. tab bc,
“positive tab”: pos. tab bc “no tab”: no tab bc}.
Assign parameter values to a model.
Currently inplace, could be changed to return a new model.
Parameters:
unprocessed_model (pybamm.BaseModel) – Model to assign parameter values for
inplace (bool, optional) – If True, replace the parameters in the model in place. Otherwise, return a
new model with parameter values set. Default is True.
Raises:
pybamm.ModelError – If an empty model is passed (model.rhs = {} and model.algebraic = {} and
model.variables = {})
Update parameter dictionary, while also performing some basic checks.
Parameters:
values (dict) – Dictionary of parameter values to update parameter dictionary with
check_conflict (bool, optional) – Whether to check that a parameter in values has not already been defined
in the parameter class when updating it, and if so that its value does not
change. This is set to True during initialisation, when parameters are
combined from different sources, and is False by default otherwise
check_already_exists (bool, optional) – Whether to check that a parameter in values already exists when trying to
update it. This is to avoid cases where an intended change in the parameters
is ignored due a typo in the parameter name, and is True by default but can
be manually overridden.
path (string, optional) – Path from which to load functions
>>> print(pybamm.parameter_sets.get_docstring("Ai2020"))Parameters for the Enertech cell (Ai2020), from the papers :footcite:t:`Ai2019`,:footcite:t:`rieger2016new` and references therein....
Parameter sets can be added to PyBaMM by creating a python package, and
registering a entry point to pybamm_parameter_sets. At a minimum, the
package (cell_parameters) should consist of the following:
PyBaMM provides pre-defined parameter sets for several common chemistries,
listed below. See Adding Parameter Sets for information on registering new
parameter sets with PyBaMM.
Parameters for the Enertech cell (Ai2020), from the papers Ai et al.[2],
Rieger et al.[3] and references therein.
SEI parameters are example parameters for SEI growth from the papers
Ramadass et al.[4], Ploehn et al.[5],
Single et al.[6], Safari et al.[7], and
Yang et al.[8]
Note
This parameter set does not claim to be representative of the true parameter
values. Instead these are parameter values that were used to fit SEI models to
observed experimental data in the referenced papers.
Parameters for an LG M50 cell, from the paper Chen et al.[9] and references
therein.
SEI parameters are example parameters for SEI growth from the papers
Ramadass et al.[4], Ploehn et al.[5],
Single et al.[6], Safari et al.[7], and
Yang et al.[8]
Note
This parameter set does not claim to be representative of the true parameter
values. Instead these are parameter values that were used to fit SEI models to
observed experimental data in the referenced papers.
Parameters for a composite graphite/silicon negative electrode, from the paper
Ai et al.[10], based on the paper Chen et al.[9], and references
therein.
SEI parameters are example parameters for composite SEI on silicon/graphite. Both
phases use the same values, from the paper Yang et al.[8]
The fits to data for the electrode and electrolyte properties are those provided
by Dr. Simon O’Kane in the paper Richardson et al.[15]
SEI parameters are example parameters for SEI growth from the papers
Ramadass et al.[4], Ploehn et al.[5],
Single et al.[6], Safari et al.[7], and
Yang et al.[8]
Note
This parameter set does not claim to be representative of the true parameter
values. Instead these are parameter values that were used to fit SEI models to
observed experimental data in the referenced papers.
Parameters for a graphite half-cell based on a Kokam SLPB 75106100 cell, from papers
Ecker, Madeleine, et al. “Parameterization of a physico-chemical model of a
lithium-ion battery I. determination of parameters.” Journal of the
Electrochemical Society 162.9 (2015): A1836-A1848.
Ecker, Madeleine, et al. “Parameterization of a physico-chemical model of a
lithium-ion battery II. Model validation.” Journal of The Electrochemical
Society 162.9 (2015): A1849-A1857.
The tab placement parameters are taken from measurements in
Hales, Alastair, et al. “The cell cooling coefficient: a standard to define heat
rejection from lithium-ion batteries.” Journal of The Electrochemical Society
166.12 (2019): A2383.
The thermal material properties are for a 5 Ah power pouch cell by Kokam. The data
are extracted from
Zhao, Y., et al. “Modeling the effects of thermal gradients induced by tab and
surface cooling on lithium ion cell performance.”” Journal of The
Electrochemical Society, 165.13 (2018): A3169-A3178.
The fits to data for the electrode and electrolyte properties are those provided
by Dr. Simon O’Kane in the paper:
Richardson, Giles, et. al. “Generalised single particle models for high-rate
operation of graded lithium-ion electrodes: Systematic derivation and
validation.” Electrochemica Acta 339 (2020): 135862
SEI parameters are example parameters for SEI growth from the papers:
Ramadass, P., Haran, B., Gomadam, P. M., White, R., & Popov, B. N. (2004).
Development of first principles capacity fade model for Li-ion cells. Journal of
the Electrochemical Society, 151(2), A196-A203.
Ploehn, H. J., Ramadass, P., & White, R. E. (2004). Solvent diffusion model for
aging of lithium-ion battery cells. Journal of The Electrochemical Society,
151(3), A456-A462.
Single, F., Latz, A., & Horstmann, B. (2018). Identifying the mechanism of
continued growth of the solid-electrolyte interphase. ChemSusChem, 11(12),
1950-1955.
Safari, M., Morcrette, M., Teyssot, A., & Delacour, C. (2009). Multimodal
Physics- Based Aging Model for Life Prediction of Li-Ion Batteries. Journal of
The Electrochemical Society, 156(3),
Yang, X., Leng, Y., Zhang, G., Ge, S., Wang, C. (2017). Modeling of lithium
plating induced aging of lithium-ion batteries: Transition from linear to
nonlinear aging. Journal of Power Sources, 360, 28-40.
Note: this parameter set does not claim to be representative of the true parameter
values. Instead these are parameter values that were used to fit SEI models to
observed experimental data in the referenced papers.
Example parameter values for use with MSMR models. The thermodynamic parameters
are for Graphite and NMC622, and are taken from Table 1 of the paper
Mark Verbrugge, Daniel Baker, Brian Koch, Xingcheng Xiao and Wentian Gu.
Thermodynamic Model for Substitutional Materials: Application to Lithiated
Graphite, Spinel Manganese Oxide, Iron Phosphate, and Layered
Nickel-Manganese-Cobalt Oxide. Journal of The Electrochemical Society,
164(11):3243-3253, 2017. doi:10.1149/2.0341708jes.
The remaining value are based on a parameterization of the LG M50 cell, from the
paper
Chang-Hui Chen, Ferran Brosa Planella, Kieran O’Regan, Dominika Gastol, W.
Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for
Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The
Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.
and references therein. Verbrugge et al. (2017) does not provide kinetic parameters
so we set the reference exchange current density to 5 A.m-2 for the positive
electrode reactions and 2.7 A.m-2 for the negative electrode reactions, which are
the values used in the Chen et al. (2020) paper. We also assume that the
exchange-current density is symmetric. Note: the 4th reaction in the positive
electrode gave unphysical results so we set the reference exchange current density
and symmetry factor to 1e6 and 1, respectively. The parameter values are intended
to serve as an example set to use with the MSMR model and do not claim to match any
experimental cycling data.
Parameters for a Kokam SLPB78205130H cell, from the paper Marquis et al.[16]
and references therein.
SEI parameters are example parameters for SEI growth from the papers
Ramadass et al.[4], Ploehn et al.[5],
Single et al.[6], Safari et al.[7], and
Yang et al.[8]
Note
This parameter set does not claim to be representative of the true parameter
values. Instead these are parameter values that were used to fit SEI models to
observed experimental data in the referenced papers.
Parameters for a graphite/NMC532 pouch cell from the paper Mohtat et al.[17]
and references therein.
SEI parameters are example parameters for SEI growth from the papers
Ramadass et al.[4], Ploehn et al.[5],
Single et al.[6], Safari et al.[7], and
Yang et al.[8]
Parameters for lithium plating are from the paper Yang et al.[8]
Note
This parameter set does not claim to be representative of the true parameter
values. Instead these are parameter values that were used to fit SEI models to
observed experimental data in the referenced papers.
Parameters for a “Nominal Design” graphite/NCA pouch cell, from the paper
Kim et al.[18]
Note
Only an effective cell volumetric heat capacity is provided in the paper. We
therefore used the values for the density and specific heat capacity reported in
the Marquis2019 parameter set in each region and multiplied each density by the
ratio of the volumetric heat capacity provided in smith to the calculated value.
This ensures that the values produce the same effective cell volumetric heat
capacity. This works fine for thermal models that are averaged over the
x-direction but not for full (PDE in x direction) thermal models. We do the same
for the planar effective thermal conductivity.
SEI parameters are example parameters for SEI growth from the papers
Ramadass et al.[4], Ploehn et al.[5],
Single et al.[6], Safari et al.[7], and
Yang et al.[8]
Note
This parameter set does not claim to be representative of the true parameter
values. Instead these are parameter values that were used to fit SEI models to
observed experimental data in the referenced papers.
Parameters for an LG M50 cell, from the paper O’Kane et al.[19], based on the
paper Chen et al.[9] and references therein.
Note
This parameter set does not claim to be representative of the true parameter
values. Instead these are parameter values that were used to fit SEI models to
observed experimental data in the referenced papers.
Parameters for the graphite+SiOx negative electrode of a LG M50 cell, from the paper
Simon E. J. O’Kane, Weilong Ai, Ganesh Madabattula, Diego Alonso-Alvarez, Robert
Timms, Valentin Sulzer, Jacqueline Sophie Edge, Billy Wu, Gregory J. Offer, and
Monica Marinescu. Lithium-ion battery degradation: how to model it. Phys. Chem.
Chem. Phys., 24:7909-7922, 2022. URL: http://dx.doi.org/10.1039/D2CP00417H,
doi:10.1039/D2CP00417H.
based on the paper
Chang-Hui Chen, Ferran Brosa Planella, Kieran O’Regan, Dominika Gastol, W.
Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for
Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The
Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.
and references therein.
Note: the SEI, plating and mechanical parameters do not claim to be representative
of the true parameter values. These are merely the parameter values that were used
in the referenced papers.
Ramadass2004 parameter set. This is a bit of a Frankenstein parameter set and should
be used with caution.
Parameters for a graphite negative electrode, Lithium Cobalt Oxide positive
electrode, and LiPF6 electrolyte are from the papers Marquis et al.[16],
Ramadass et al.[4], and references therein.
Parameters for the separator are from the papers Ecker et al.[11]
The thermal material properties are for a 5 Ah power pouch cell by Kokam. The data
are extracted from Lithium Cobalt Oxide positive electrode parameters in
Zhao et al.[14]
Parameters for SEI growth are from the papers Ramadass et al.[4] and
Safari et al.[7]
Note
Ramadass 2004 has mistakes in units and values of SEI parameters, corrected by
Safari2009.
Parameters for a Kokam SLPB78205130H half-cell, from the paper Xu et al.[23]
and references therein. Anode is graphite MCMB 2528. Separator is Celgard 2325.
Cathode is lithium Cobalt Oxide. Electrolyte is LiPF6.
Parameters for a LiPF6 electrolyte are from the paper Valøen and Reimers[24]
SEI parameters are example parameters for SEI growth from the papers
Ramadass et al.[4], Ploehn et al.[5],
Single et al.[6], Safari et al.[7], and
Yang et al.[8].
Note
This parameter set does not claim to be representative of the true parameter
values. Instead these are parameter values that were used to fit SEI models to
observed experimental data in the referenced papers.
Process 2D data from a csv file. Assumes
data is in the form of a three columns
and that all data points lie on a regular
grid. The first column is assumed to
be the ‘slowest’ changing variable and
the second column the ‘fastest’ changing
variable, which is the C convention for
indexing multidimensional arrays (as opposed
to the Fortran convention where the ‘fastest’
changing variable comes first).
Process 3D data from a csv file. Assumes
data is in the form of four columns and
that all data points lie on a
regular grid. The first column is assumed to
be the ‘slowest’ changing variable and
the third column the ‘fastest’ changing
variable, which is the C convention for
indexing multidimensional arrays (as opposed
to the Fortran convention where the ‘fastest’
changing variable comes first).
A geometry class to store the details features of the cell geometry.
The values assigned to each domain are dictionaries containing the spatial variables
in that domain, along with expression trees giving their min and maximum extents.
For example, the following dictionary structure would represent a Geometry with a
single domain “negative electrode”, defined using the variable x_n which has a
range from 0 to the pre-defined parameter l_n.
Create meshes for potential ghost nodes on either side of each submesh, using
self.submeshclass
This will be useful for calculating the gradient with Dirichlet BCs.
Combine submeshes into a new submesh, using self.submeshclass
Raises pybamm.DomainError if submeshes to be combined do not match up (edges are
not aligned).
Parameters:
submeshnames (list of str) – The names of the submeshes to be combined
Returns:
submesh – A new submesh with the class defined by self.submeshclass
A class to generate a uniform submesh on a 1D domain
Parameters:
lims (dict) – A dictionary that contains the limits of the spatial variables
npts (dict) – A dictionary that contains the number of points to be used on each
spatial variable. Note: the number of nodes (located at the cell centres)
is npts, and the number of edges is npts+1.
A class to generate a submesh on a 1D domain in which the points are clustered
close to one or both of boundaries using an exponential formula on the interval
[a,b].
for k = 1, …, N. The grid spacing is then reflected to contruct the grid
on the full interval [a,b].
In the above, alpha is a stretching factor. As the number of gridpoints tends
to infinity, the ratio of the largest and smallest grid cells tends to exp(alpha).
Parameters:
lims (dict) – A dictionary that contains the limits of the spatial variables
npts (dict) – A dictionary that contains the number of points to be used on each
spatial variable. Note: the number of nodes (located at the cell centres)
is npts, and the number of edges is npts+1.
side (str, optional) – Whether the points are clustered near to the left or right boundary,
or both boundaries. Can be “left”, “right” or “symmetric”. Default is
“symmetric”
stretch (float, optional) – The factor (alpha) which appears in the exponential. If side is “symmetric”
then the default stretch is 1.15. If side is “left” or “right” then the
default stretch is 2.3.
for k = 1, …, N, where N is the number of nodes. Note: this mesh then
appends the boundary edges, so that the mesh edges are given by
\[a < x_{1} < ... < x_{N} < b.\]
Parameters:
lims (dict) – A dictionary that contains the limits of the spatial variables
npts (dict) – A dictionary that contains the number of points to be used on each
spatial variable. Note: the number of nodes (located at the cell centres)
is npts, and the number of edges is npts+1.
tabs (dict, optional) – A dictionary that contains information about the size and location of
the tabs
A class to generate a submesh on a 1D domain from a user supplied array of
edges.
Parameters:
lims (dict) – A dictionary that contains the limits of the spatial variables
npts (dict) – A dictionary that contains the number of points to be used on each
spatial variable. Note: the number of nodes (located at the cell centres)
is npts, and the number of edges is npts+1.
edges (array_like) – The array of points which correspond to the edges of the mesh.
2D submesh class.
Contains information about the 2D finite element mesh.
Note: This class only allows for the use of piecewise-linear triangular
finite elements.
Parameters:
edges (array_like) – An array containing the points corresponding to the edges of the submesh
coord_sys (string) – The coordinate system of the submesh
tabs (dict, optional) – A dictionary that contains information about the size and location of
the tabs
Contains information about the 2D finite element mesh with uniform grid
spacing (can be different spacing in y and z).
Note: This class only allows for the use of piecewise-linear triangular
finite elements.
Parameters:
lims (dict) – A dictionary that contains the limits of each
spatial variable
npts (dict) – A dictionary that contains the number of points to be used on each
spatial variable
Contains information about the 2D finite element mesh generated by taking the
tensor product of a uniformly spaced grid in the y direction, and a unequally
spaced grid in the z direction in which the points are clustered
close to the top boundary using an exponential formula on the interval [a,b].
The gridpoints in the z direction are given by
for k = 1, …, N, where N is the number of nodes. Here alpha is
a stretching factor. As the number of gridpoints tends to infinity, the ratio
of the largest and smallest grid cells tends to exp(alpha).
Note: in the future this will be extended to allow points to be clustered near
any of the boundaries.
Parameters:
lims (dict) – A dictionary that contains the limits of each
spatial variable
npts (dict) – A dictionary that contains the number of points to be used on each
spatial variable
side (str, optional) – Whether the points are clustered near to a particular boundary. At present,
can only be “top”. Default is “top”.
stretch (float, optional) – The factor (alpha) which appears in the exponential. Default is 2.3.
Contains information about the 2D finite element mesh generated by taking the
tensor product of two 1D meshes which use Chebyshev nodes on the
interval (a, b), given by
A class to generate a tensor product submesh on a 2D domain by using two user
supplied vectors of edges: one for the y-direction and one for the z-direction.
Note: this mesh should be created using UserSupplied2DSubMeshGenerator.
Parameters:
lims (dict) – A dictionary that contains the limits of the spatial variables
npts (dict) – A dictionary that contains the number of points to be used on each
spatial variable. Note: the number of nodes (located at the cell centres)
is npts, and the number of edges is npts+1.
y_edges (array_like) – The array of points which correspond to the edges in the y direction
of the mesh.
z_edges (array_like) – The array of points which correspond to the edges in the z direction
of the mesh.
The discretisation class, with methods to process a model and replace
Spatial Operators with Matrices and Variables with StateVectors
Parameters:
mesh (pybamm.Mesh) – contains all submeshes to be used on each domain
spatial_methods (dict) – a dictionary of the spatial methods to be used on each
domain. The keys correspond to the model domains and the
values to the spatial method.
Check any boundary conditions applied on “negative tab”, “positive tab”
and “no tab”. For 1D current collector meshes, these conditions are
converted into boundary conditions on “left” (tab at z=0) or “right”
(tab at z=l_z) depending on the tab location stored in the mesh. For 2D
current collector meshes, the boundary conditions can be applied on the
tabs directly.
Check variables in variable list against rhs.
Be lenient with size check if the variable in model.variables is broadcasted, or
a concatenation
(if broadcasted, variable is a multiplication with a vector of ones)
Creates mass matrix of the discretised model.
Note that the model is assumed to be of the form M*y_dot = f(t,y), where
M is the (possibly singular) mass matrix.
Parameters:
model (pybamm.BaseModel) – Discretised model. Must have attributes rhs, initial_conditions and
boundary_conditions (all dicts of {variable: equation})
Discretise model boundary_conditions, also converting keys to ids
Parameters:
model (pybamm.BaseModel) – Model to dicretise. Must have attributes rhs, initial_conditions and
boundary_conditions (all dicts of {variable: equation})
Discretise a dictionary of {variable: equation}, broadcasting if necessary
(can be model.rhs, model.algebraic, model.initial_conditions or
model.variables).
Parameters:
var_eqn_dict (dict) – Equations ({variable: equation} dict) to dicretise
(can be model.rhs, model.algebraic, model.initial_conditions or
model.variables)
ics (bool, optional) – Whether the equations are initial conditions. If True, the equations are
scaled by the reference value of the variable, if given
model (pybamm.BaseModel) – Model to dicretise. Must have attributes rhs, initial_conditions and
boundary_conditions (all dicts of {variable: equation})
Returns:
Tuple of processed_initial_conditions (dict of initial conditions) and
concatenated_initial_conditions (numpy array of concatenated initial
conditions)
Discretise a model. Currently inplace, could be changed to return a new model.
Parameters:
model (pybamm.BaseModel) – Model to dicretise. Must have attributes rhs, initial_conditions and
boundary_conditions (all dicts of {variable: equation})
inplace (bool, optional) – If True, discretise the model in place. Otherwise, return a new
discretised model. Default is True.
check_model (bool, optional) – If True, model checks are performed after discretisation. For large
systems these checks can be slow, so can be skipped by setting this
option to False. When developing, testing or debugging it is recommended
to leave this option as True as it may help to identify any errors.
Default is True.
remove_independent_variables_from_rhs (bool, optional) – If True, model checks to see whether any variables from the RHS are used
in any other equation. If a variable meets all of the following criteria
(not used anywhere in the model, len(rhs)>1), then the variable
is moved to be explicitly integrated when called by the solution object.
Default is True.
Returns:
model_disc – The discretised model. Note that if inplace is True, model will
have also been discretised in place so model == model_disc. If
inplace is False, model != model_disc
Discretise model equations - differential (‘rhs’) and algebraic.
Parameters:
model (pybamm.BaseModel) – Model to dicretise. Must have attributes rhs, initial_conditions and
boundary_conditions (all dicts of {variable: equation})
Returns:
Tuple of processed_rhs (dict of processed differential equations),
processed_concatenated_rhs, processed_algebraic (dict of processed algebraic
equations) and processed_concatenated_algebraic
A method to set the internal boundary conditions for the submodel.
These are required to properly calculate the gradient.
Note: this method modifies the state of self.boundary_conditions.
A general spatial methods class, with default (trivial) behaviour for some spatial
operations.
All spatial methods will follow the general form of SpatialMethod in
that they contain a method for broadcasting variables onto a mesh,
a gradient operator, and a divergence operator.
Parameters:
mesh – Contains all the submeshes for discretisation
Implements the boundary integral for a spatial method.
Parameters:
child (pybamm.Symbol) – The symbol to which is being integrated
discretised_child (pybamm.Symbol) – The discretised symbol of the correct size
region (str) – The region of the boundary over which to integrate. If region is None
(default) the integration is carried out over the entire boundary. If
region is negative tab or positive tab then the integration is only
carried out over the appropriate part of the boundary corresponding to
the tab.
Returns:
Contains the result of acting the discretised boundary integral on
the child discretised_symbol
Returns the boundary value or flux using the appropriate expression for the
spatial method. To do this, we create a sparse vector ‘bv_vector’ that extracts
either the first (for side=”left”) or last (for side=”right”) point from
‘discretised_child’.
Parameters:
symbol (pybamm.Symbol) – The boundary value or flux symbol
discretised_child (pybamm.StateVector) – The discretised variable from which to calculate the boundary value
bcs (dict (optional)) – The boundary conditions. If these are supplied and “use bcs” is True in
the options, then these will be used to improve the accuracy of the
extrapolation.
symbol (pybamm.Symbol) – The symbol to be broadcasted
domains (dict of strings) – The domains for broadcasting
broadcast_type (str) – The type of broadcast: ‘primary to node’, ‘primary to edges’, ‘secondary to
nodes’, ‘secondary to edges’, ‘tertiary to nodes’, ‘tertiary to edges’,
‘full to nodes’ or ‘full to edges’
Returns:
broadcasted_symbol – The discretised symbol of the correct size for the spatial method
For Dirichlet bcs, for a boundary condition “y = a at the left-hand boundary”,
we concatenate a ghost node to the start of the vector y with value “2*a - y1”
where y1 is the value of the first node.
Similarly for the right-hand boundary condition.
For Neumann bcs no ghost nodes are added. Instead, the exact value provided
by the boundary condition is used at the cell edge when calculating the
gradient (see pybamm.FiniteVolume.add_neumann_values()).
discretised_symbol (pybamm.Vector) – Contains the discretised variable
bcs (dict of tuples (pybamm.Scalar, str)) – Dictionary (with keys “left” and “right”) of boundary conditions. Each
boundary condition consists of a value and a flag indicating its type
(e.g. “Dirichlet”)
Returns:
Matrix @ discretised_symbol + bcs_vector. When evaluated, this gives the
discretised_symbol, with appropriate ghost nodes concatenated at each end.
discretised_gradient (pybamm.Vector) – Contains the discretised gradient of symbol
bcs (dict of tuples (pybamm.Scalar, str)) – Dictionary (with keys “left” and “right”) of boundary conditions. Each
boundary condition consists of a value and a flag indicating its type
(e.g. “Dirichlet”)
domain (list of strings) – The domain of the gradient of the symbol (may include ghost nodes)
Returns:
Matrix @ discretised_gradient + bcs_vector. When evaluated, this gives the
discretised_gradient, with the values of the Neumann boundary conditions
concatenated at each end (if given).
Delta function. Implemented as a vector whose only non-zero element is the
first (if symbol.side = “left”) or last (if symbol.side = “right”), with
appropriate value so that the integral of the delta function across the whole
domain is the same as the integral of the discretised symbol across the whole
domain.
Matrix for finite-volume implementation of the indefinite integral where the
integrand is evaluated on mesh edges (shape (n+1, 1)).
The integral will then be evaluated on mesh nodes (shape (n, 1)).
Parameters:
domains (dict) – The domain(s) and auxiliary domains of integration
direction (str) – The direction of integration (forward or backward). See notes.
The indefinite integral must satisfy the following conditions:
\(F(0) = 0\)
\(f(x) = \frac{dF}{dx}\)
or, in discrete form,
BoundaryValue(F, “left”) = 0, i.e. \(3*F_0 - F_1 = 0\)
\(f_{i+1/2} = (F_{i+1} - F_i) / dx_{i+1/2}\)
Hence we must have
\(F_0 = du_{1/2} * f_{1/2} / 2\)
\(F_{i+1} = F_i + du_{i+1/2} * f_{i+1/2}\)
Note that \(f_{-1/2}\) and \(f_{end+1/2}\) are included in the discrete
integrand vector f, so we add a column of zeros at each end of the
indefinite integral matrix to ignore these.
Backward integral
\[F(x) = \int_x^{end}\!f(u)\,du\]
The indefinite integral must satisfy the following conditions:
\(F(end) = 0\)
\(f(x) = -\frac{dF}{dx}\)
or, in discrete form,
BoundaryValue(F, “right”) = 0, i.e. \(3*F_{end} - F_{end-1} = 0\)
\(f_{i+1/2} = -(F_{i+1} - F_i) / dx_{i+1/2}\)
Hence we must have
\(F_{end} = du_{end+1/2} * f_{end-1/2} / 2\)
\(F_{i-1} = F_i + du_{i-1/2} * f_{i-1/2}\)
Note that \(f_{-1/2}\) and \(f_{end+1/2}\) are included in the discrete
integrand vector f, so we add a column of zeros at each end of the
indefinite integral matrix to ignore these.
Matrix for finite-volume implementation of the (backward) indefinite integral
where the integrand is evaluated on mesh nodes (shape (n, 1)).
The integral will then be evaluated on mesh edges (shape (n+1, 1)).
This is just a straightforward (backward) cumulative sum of the integrand
Parameters:
domains (dict) – The domain(s) and auxiliary domains of integration
direction (str) – The direction of integration (forward or backward)
Discretise binary operators in model equations. Performs appropriate
averaging of diffusivities if one of the children is a gradient operator, so
that discretised sizes match up. For this averaging we use the harmonic
mean [1].
[1] Recktenwald, Gerald. “The control-volume finite-difference approximation to
the diffusion equation.” (2012).
Convert a discretised symbol evaluated at edges/nodes, to a discretised symbol
evaluated at nodes/edges. Can be the arithmetic mean or the harmonic mean.
Note: when computing fluxes at cell edges it is better to take the
harmonic mean based on [1].
[1] Recktenwald, Gerald. “The control-volume finite-difference approximation to
the diffusion equation.” (2012).
Parameters:
discretised_symbol (pybamm.Symbol) – Symbol to be averaged. When evaluated, this symbol returns either a scalar
or an array of shape (n,) or (n+1,), where n is the number of points in the
mesh for the symbol’s domain (n = self.mesh[symbol.domain].npts)
shift_key (str) – Whether to shift from nodes to edges (“node to edge”), or from edges to
nodes (“edge to node”)
method (str) – Whether to use the “arithmetic” or “harmonic” mean
Returns:
Averaged symbol. When evaluated, this returns either a scalar or an array of
shape (n+1,) (if shift_key = “node to edge”) or (n,) (if
shift_key = “edge to node”)
Implement an upwinding operator. Currently, this requires the symbol to have
a Dirichlet boundary condition on the left side (for upwinding) or right side
(for downwinding).
discretised_gradient (pybamm.Vector) – Contains the discretised gradient of symbol
bcs (dict of tuples (pybamm.Scalar, str)) – Dictionary (with keys “left” and “right”) of boundary conditions. Each
boundary condition consists of a value and a flag indicating its type
(e.g. “Dirichlet”)
direction (str) – Direction in which to apply the operator (upwind or downwind)
A class which implements the steps specific to the Spectral Volume
discretisation. It is implemented in such a way that it is very
similar to FiniteVolume; that comes at the cost that it is only
compatible with the SpectralVolume1DSubMesh (which is a certain
subdivision of any 1D mesh, so it shouldn’t be a problem).
For broadcast and mass_matrix, we follow the default behaviour from
SpatialMethod. For spatial_variable,
divergence, divergence_matrix, laplacian, integral,
definite_integral_matrix, indefinite_integral,
indefinite_integral_matrix, indefinite_integral_matrix_nodes,
indefinite_integral_matrix_edges, delta_function
we follow the behaviour from FiniteVolume. This is possible since
the node values are integral averages with Spectral Volume, just
as with Finite Volume. delta_function assigns the integral value
to a CV instead of a SV this way, but that doesn’t matter too much.
Additional methods that are inherited by FiniteVolume which
technically are not suitable for Spectral Volume are
boundary_value_or_flux, process_binary_operators, concatenation,
node_to_edge, edge_to_node and shift. While node_to_edge (as well as
boundary_value_or_flux and process_binary_operators)
could utilize the reconstruction approach of Spectral Volume, the
inverse edge_to_node would still have to fall back to the Finite
Volume behaviour. So these are simply inherited for consistency.
boundary_value_or_flux might not benefit from the reconstruction
approach at all, as it seems to only preprocess symbols.
Parameters:
mesh (pybamm.Mesh) – Contains all the submeshes for discretisation
Chebyshev differentiation matrices, from
Baltensperger and Trummer[1].
Parameters:
noe (integer) – The number of the collocation points. “number of edges”
dod (integer) – The maximum order of differentiation for which a
differentiation matrix shall be calculated. Note that it has
to be smaller than ‘noe’. “degrees of differentiation”
Returns:
The differentiation matrices in ascending order of
differentiation order. With exact arithmetic, the diff.
matrix of order p would just be the pth matrix power of
the diff. matrix of order 1. This method computes the higher
orders in a more numerically stable way.
“Broadcasts” the basic edge value reconstruction matrix to the
actual shape of the discretised symbols. Note that the product
of this and a discretised symbol is a vector which represents
duplicate values for all inner SV edges. These are the
reconstructed values from both sides.
Parameters:
domains (dict) – The domains in which to compute the gradient matrix
Returns:
The (sparse) CV reconstruction matrix for the domain
Gradient matrix for Spectral Volume in the appropriate domain.
Note that it contains the averaging of the duplicate SV edge
gradient values, such that the product of it and a reconstructed
discretised symbol simply represents CV edge values.
On its own, it only works on non-concatenated domains, since
only then the boundary conditions ensure correct behaviour.
More generally, it only works if gradients are a result of
boundary conditions rather than continuity conditions.
For example, two adjacent SVs with gradient zero in each of them
but with different variable values will have zero gradient
between them. This is fixed with “penalty_matrix”.
Parameters:
domains (dict) – The domains in which to compute the gradient matrix
Returns:
The (sparse) Spectral Volume gradient matrix for the domain
Penalty matrix for Spectral Volume in the appropriate domain.
This works the same as the “gradient_matrix” of FiniteVolume
does, just between SVs and not between CVs. Think of it as a
continuity penalty.
Parameters:
domains (dict) – The domains in which to compute the gradient matrix
Returns:
The (sparse) Spectral Volume penalty matrix for the domain
discretised_symbol (pybamm.Vector) – Contains the discretised variable
bcs (dict of tuples (pybamm.Scalar, str)) – Dictionary (with keys “left” and “right”) of boundary
conditions. Each boundary condition consists of a value and
a flag indicating its type (e.g. “Dirichlet”)
Returns:
Matrix @ discretised_symbol + bcs_vector. When evaluated,
this gives the discretised_symbol, with its boundary values
replaced by the Dirichlet boundary conditions.
discretised_gradient (pybamm.Vector) – Contains the discretised gradient of symbol
bcs (dict of tuples (pybamm.Scalar, str)) – Dictionary (with keys “left” and “right”) of boundary
conditions. Each boundary condition consists of a value and
a flag indicating its type (e.g. “Dirichlet”)
Returns:
Matrix @ discretised_gradient + bcs_vector. When
evaluated, this gives the discretised_gradient, with its
boundary values replaced by the Neumann boundary conditions.
A class which implements the steps specific to the finite element method during
discretisation. The class uses scikit-fem to discretise the problem to obtain
the mass and stiffness matrices. At present, this class is only used for
solving the Poisson problem -grad^2 u = f in the y-z plane (i.e. not the
through-cell direction).
For broadcast we follow the default behaviour from SpatialMethod.
Parameters:
mesh (pybamm.Mesh) – Contains all the submeshes for discretisation
boundary (numpy.array) – Array of the indicies which correspond to the boundary.
zero (bool, optional) – If True, the rows of M given by the indicies in boundary are set to zero.
If False, the diagonal element is set to one. default is False.
A node in the expression tree representing an integral operator over the
boundary of a domain
\[I = \int_{\partial a}\!f(u)\,du,\]
where \(\partial a\) is the boundary of the domain, and
\(u\in\text{domain boundary}\).
Parameters:
domain (list) – The domain(s) of the variable in the integrand
region (str) – The region of the boundary over which to integrate. If region is
entire the integration is carried out over the entire boundary. If
region is negative tab or positive tab then the integration is only
carried out over the appropriate part of the boundary corresponding to
the tab.
Matrix-vector multiplication to implement the gradient operator. The
gradient w of the function u is approximated by the finite element method
using the same function space as u, i.e. we solve w = grad(u), which
corresponds to the weak form w*v*dx = grad(u)*v*dx, where v is a suitable
test function.
Parameters:
symbol (pybamm.Symbol) – The symbol that we will take the Laplacian of.
discretised_symbol (pybamm.Symbol) – The discretised symbol of the correct size
boundary_conditions (dict) – The boundary conditions of the model
({symbol: {“negative tab”: neg. tab bc, “positive tab”: pos. tab bc}})
Returns:
A concatenation that contains the result of acting the discretised
gradient on the child discretised_symbol. The first column corresponds
to the y-component of the gradient and the second column corresponds
to the z component of the gradient.
Implementation of the indefinite integral operator. The
input discretised child must be defined on the internal mesh edges.
See pybamm.SpatialMethod.indefinite_integral()
Calculates the zero-dimensional indefinite integral.
If ‘direction’ is forward, this is the identity operator.
If ‘direction’ is backward, this is the negation operator.
method (str, optional) – The method to use for integration, specific to each solver
rtol (float, optional) – The relative tolerance for the solver (default is 1e-6).
atol (float, optional) – The absolute tolerance for the solver (default is 1e-6).
root_method (str or pybamm algebraic solver class, optional) – The method to use to find initial conditions (for DAE solvers).
If a solver class, must be an algebraic solver class.
If “casadi”,
the solver uses casadi’s Newton rootfinding algorithm to find initial
conditions. Otherwise, the solver uses ‘scipy.optimize.root’ with method
specified by ‘root_method’ (e.g. “lm”, “hybr”, …)
root_tol (float, optional) – The tolerance for the initial-condition solver (default is 1e-6).
extrap_tol (float, optional) – The tolerance to assert whether extrapolation occurs or not. Default is 0.
output_variables (list[str], optional) – List of variables to calculate and return. If none are specified then
the complete state vector is returned (can be very large) (default is [])
Calculate consistent state for the algebraic equations through
root-finding. model.y0 is used as the initial guess for rootfinding
Parameters:
model (pybamm.BaseModel) – The model for which to calculate initial conditions.
time (float) – The time at which to calculate the states
inputs (dict, optional) – Any input parameters to pass to the model when solving
Returns:
y0_consistent – Initial conditions that are consistent with the algebraic equations (roots
of the algebraic equations). If self.root_method == None then returns
model.y0.
Check if extrapolation occurred for any of the interpolants. Note that with the
current approach (evaluating all the events at the solution times) some
extrapolations might not be found if they only occurred for a small period of
time.
Identify the cause for termination. In particular, if the solver terminated
due to an event, (try to) pinpoint which event was responsible. If an event
occurs the event time and state are added to the solution object.
Note that the current approach (evaluating all the events and then finding which
one is smallest at the final timestep) is pretty crude, but is the easiest one
that works for all the different solvers.
Execute the solver setup and calculate the solution of the model at
specified times.
Parameters:
model (pybamm.BaseModel) – The model whose solution to calculate. Must have attributes rhs and
initial_conditions. All calls to solve must pass in the same model or
an error is raised
t_eval (numeric type) – The times (in seconds) at which to compute the solution
inputs (dict or list, optional) – A dictionary or list of dictionaries describing any input parameters to
pass to the model when solving
nproc (int, optional) – Number of processes to use when solving for more than one set of input
parameters. Defaults to value returned by “os.cpu_count()”.
calculate_sensitivities (list of str or bool) – If true, solver calculates sensitivities of all input parameters.
If only a subset of sensitivities are required, can also pass a
list of input parameter names
Returns:
If type of inputs is list, return a list of corresponding
pybamm.Solution objects.
Step the solution of the model forward by a given time increment. The
first time this method is called it executes the necessary setup by
calling self.set_up(model).
Parameters:
old_solution (pybamm.Solution or None) – The previous solution to be added to. If None, a new solution is created.
model (pybamm.BaseModel) – The model whose solution to calculate. Must have attributes rhs and
initial_conditions
dt (numeric type) – The timestep (in seconds) over which to step the solution
npts (int, optional) – The number of points at which the solution will be returned during
the step dt. default is 2 (returns the solution at t0 and t0 + dt).
inputs (dict, optional) – Any input parameters to pass to the model when solving
save (bool) – Turn on to store the solution of all previous timesteps
Raises:
pybamm.ModelError – If an empty model is passed (model.rhs = {} and model.algebraic = {} and
model.variables = {})
Solve a discretised model using a JAX compiled solver.
Note: this solver will not work with models that have
termination events or are not converted to jax format
Raises:
RuntimeError – if model has any termination events
RuntimeError – if model.convert_to_format != ‘jax’
Parameters:
method (str) – ‘RK45’ (default) uses jax.experimental.odeint
‘BDF’ uses custom jax_bdf_integrate (see jax_bdf_integrate.py for details)
root_method (str, optional) – Method to use to calculate consistent initial conditions. By default this uses
the newton chord method internal to the jax bdf solver, otherwise choose from
the set of default options defined in docs for pybamm.BaseSolver
rtol (float, optional) – The relative tolerance for the solver (default is 1e-6).
atol (float, optional) – The absolute tolerance for the solver (default is 1e-6).
extrap_tol (float, optional) – The tolerance to assert whether extrapolation occurs or not (default is 0).
extra_options (dict, optional) – Any options to pass to the solver.
Please consult JAX documentation
for details.
Backward Difference formula (BDF) implicit multistep integrator. The basic algorithm
is derived in Byrne and Hindmarsh[1]. This particular implementation
follows that implemented in the Matlab routine ode15s described in
Shampine and Reichelt[2] and the SciPy implementation
Virtanen et al.[3] which features the NDF formulas for improved stability,
with associated differences in the error constants, and calculates the jacobian at
J(t_{n+1}, y^0_{n+1}). This implementation was based on that implemented in the
SciPy library Virtanen et al.[3], which also mainly follows
Shampine and Reichelt[2] but uses the more standard jacobian update.
Parameters:
func (callable) – function to evaluate the time derivative of the solution y at time
t as func(y, t, *args), producing the same shape/structure as y0.
y0 (ndarray) – initial state vector
t_eval (ndarray) – time points to evaluate the solution, has shape (m,)
args ((optional)) – tuple of additional arguments for fun, which must be arrays
scalars, or (nested) standard Python containers (tuples, lists, dicts,
namedtuples, i.e. pytrees) of those types.
rtol ((optional) float) – relative tolerance for the solver
atol ((optional) float) – absolute tolerance for the solver
mass ((optional) ndarray) – diagonal of the mass matrix with shape (n,)
Returns:
y – calculated state vector at each of the m time points
Solve a discretised model, using sundials with the KLU sparse linear solver.
Parameters:
rtol (float, optional) – The relative tolerance for the solver (default is 1e-6).
atol (float, optional) – The absolute tolerance for the solver (default is 1e-6).
root_method (str or pybamm algebraic solver class, optional) – The method to use to find initial conditions (for DAE solvers).
If a solver class, must be an algebraic solver class.
If “casadi”,
the solver uses casadi’s Newton rootfinding algorithm to find initial
conditions. Otherwise, the solver uses ‘scipy.optimize.root’ with method
specified by ‘root_method’ (e.g. “lm”, “hybr”, …)
root_tol (float, optional) – The tolerance for the initial-condition solver (default is 1e-6).
extrap_tol (float, optional) – The tolerance to assert whether extrapolation occurs or not (default is 0).
output_variables (list[str], optional) – List of variables to calculate and return. If none are specified then
the complete state vector is returned (can be very large) (default is [])
Addititional options to pass to the solver, by default:
options={# print statistics of the solver after every solve"print_stats":False,# jacobian form, can be "none", "dense",# "banded", "sparse", "matrix-free""jacobian":"sparse",# name of sundials linear solver to use options are: "SUNLinSol_KLU",# "SUNLinSol_Dense", "SUNLinSol_Band", "SUNLinSol_SPBCGS",# "SUNLinSol_SPFGMR", "SUNLinSol_SPGMR", "SUNLinSol_SPTFQMR","linear_solver":"SUNLinSol_KLU",# preconditioner for iterative solvers, can be "none", "BBDP""preconditioner":"BBDP",# for iterative linear solvers, max number of iterations"linsol_max_iterations":5,# for iterative linear solver preconditioner, bandwidth of# approximate jacobian"precon_half_bandwidth":5,# for iterative linear solver preconditioner, bandwidth of# approximate jacobian that is kept"precon_half_bandwidth_keep":5,# Number of threads available for OpenMP"num_threads":1,}
Note: These options only have an effect if model.convert_to_format == ‘casadi’
method (str, optional) – The method to use in solve_ivp (default is “BDF”)
rtol (float, optional) – The relative tolerance for the solver (default is 1e-6).
atol (float, optional) – The absolute tolerance for the solver (default is 1e-6).
root_method (str or pybamm algebraic solver class, optional) – The method to use to find initial conditions (for DAE solvers).
If a solver class, must be an algebraic solver class.
If “casadi”,
the solver uses casadi’s Newton rootfinding algorithm to find initial
conditions. Otherwise, the solver uses ‘scipy.optimize.root’ with method
specified by ‘root_method’ (e.g. “lm”, “hybr”, …)
root_tol (float, optional) – The tolerance for the initial-condition solver (default is 1e-6).
extrap_tol (float, optional) – The tolerance to assert whether extrapolation occurs or not (default is 0).
”fast”: perform direct integration, without accounting for events. Recommended when simulating a drive cycle or other simulation where no events should be triggered.
”fast with events”: perform direct integration of the whole timespan, then go back and check where events were crossed. Experimental only.
”safe”: perform step-and-check integration in global steps of size dt_max, checking whether events have been triggered. Recommended for simulations of a full charge or discharge.
”safe without grid”: perform step-and-check integration step-by-step. Takes more steps than “safe” mode, but doesn’t require creating the grid each time, so may be faster. Experimental only.
rtol (float, optional) – The relative tolerance for the solver (default is 1e-6).
atol (float, optional) – The absolute tolerance for the solver (default is 1e-6).
root_method (str or pybamm algebraic solver class, optional) – The method to use to find initial conditions (for DAE solvers).
If a solver class, must be an algebraic solver class.
If “casadi”,
the solver uses casadi’s Newton rootfinding algorithm to find initial
conditions. Otherwise, the solver uses ‘scipy.optimize.root’ with method
specified by ‘root_method’ (e.g. “lm”, “hybr”, …)
root_tol (float, optional) – The tolerance for root-finding. Default is 1e-6.
max_step_decrease_count (float, optional) – The maximum number of times step size can be decreased before an error is
raised. Default is 5.
dt_max (float, optional) – The maximum global step size (in seconds) used in “safe” mode. If None
the default value is 600 seconds.
extrap_tol (float, optional) – The tolerance to assert whether extrapolation occurs or not. Default is 0.
Any options to pass to the CasADi integrator when calling the integrator.
Please consult CasADi documentation for
details.
return_solution_if_failed_early (bool, optional) – Whether to return a Solution object if the solver fails to reach the end of
the simulation, but managed to take some successful steps. Default is False.
perturb_algebraic_initial_conditions (bool, optional) – Whether to perturb algebraic initial conditions to avoid a singularity. This
can sometimes slow down the solver, but is kept True as default for “safe” mode
as it seems to be more robust (False by default for other modes).
integrators_maxcount (int, optional) – The maximum number of integrators that the solver will retain before
ejecting past integrators using an LRU methodology. A value of 0 or
None leaves the number of integrators unbound. Default is 100.
Method to create a casadi integrator object.
If t_eval is provided, the integrator uses t_eval to make the grid.
Otherwise, the integrator has grid [0,1].
Solve a discretised model which contains only (time independent) algebraic
equations using a root finding algorithm.
Uses scipy.optimize.root.
Note: this solver could be extended for quasi-static models, or models in
which the time derivative is manually discretised and results in a (possibly
nonlinear) algebaric system at each time level.
Parameters:
method (str, optional) – The method to use to solve the system (default is “lm”). If it starts with
“lsq”, least-squares minimization is used. The method for least-squares can be
specified in the form “lsq_methodname”
tol (float, optional) – The tolerance for the solver (default is 1e-6).
extra_options (dict, optional) – Any options to pass to the rootfinder. Vary depending on which method is chosen.
Please consult SciPy documentation for
details.
Solve a discretised model which contains only (time independent) algebraic
equations using CasADi’s root finding algorithm.
Note: this solver could be extended for quasi-static models, or models in
which the time derivative is manually discretised and results in a (possibly
nonlinear) algebaric system at each time level.
Parameters:
tol (float, optional) – The tolerance for the solver (default is 1e-6).
extra_options (dict, optional) – Any options to pass to the CasADi rootfinder.
Please consult CasADi documentation for
details.
Class containing the solution of, and various attributes associated with, a PyBaMM
model.
Parameters:
all_ts (numpy.array, size (n,) (or list of these)) – A one-dimensional array containing the times at which the solution is evaluated.
A list of times can be provided instead to initialize a solution with
sub-solutions.
all_ys (numpy.array, size (m, n) (or list of these)) – A two-dimensional array containing the values of the solution. y[i, :] is the
vector of solutions at time t[i].
A list of ys can be provided instead to initialize a solution with
sub-solutions.
all_models (pybamm.BaseModel) – The model that was used to calculate the solution.
A list of models can be provided instead to initialize a solution with
sub-solutions that have been calculated using those models.
all_inputs (dict (or list of these)) – The inputs that were used to calculate the solution
A list of inputs can be provided instead to initialize a solution with
sub-solutions.
t_event (numpy.array, size (1,)) – A zero-dimensional array containing the time at which the event happens.
y_event (numpy.array, size (m,)) – A one-dimensional array containing the value of the solution at the time when
the event happens.
termination (str) – String to indicate why the solution terminated
sensitivities (bool or dict) – True if sensitivities included as the solution of the explicit forwards
equations. False if no sensitivities included/wanted. Dict if sensitivities are
provided as a dict of {parameter: sensitivities} pairs.
A Solution object that only contains the first state. This is faster to evaluate
than the full solution when only the first state is needed (e.g. to initialize
a model with the solution)
Construct a (standard python) dictionary of the solution data containing the
variables in variables. If variables is None then all variables are
returned. Any variable names in short_names are replaced with the corresponding
short name.
If the solution has cycles, then the cycle numbers and step numbers are also
returned in the dictionary.
Parameters:
variables (list, optional) – List of variables to return. If None, returns all variables in solution.data
short_names (dict, optional) – Dictionary of shortened names to use when saving.
cycles_and_steps (bool, optional) – Whether to include the cycle numbers and step numbers in the dictionary
A Solution object that only contains the final state. This is faster to evaluate
than the full solution when only the final state is needed (e.g. to initialize
a model with the solution)
’pickle’ (default): creates a pickle file with the data dictionary
’matlab’: creates a .mat file, for loading in matlab
’csv’: creates a csv file (0D variables only)
’json’: creates a json file
short_names (dict, optional) – Dictionary of shortened names to use when saving. This may be necessary when
saving to MATLAB, since no spaces or special characters are allowed in
MATLAB variable names. Note that not all the variables need to be given
a short name.
Returns:
data – str if ‘csv’ or ‘json’ is chosen and filename is None, otherwise None
An object that can be evaluated at arbitrary (scalars or vectors) t and x, and
returns the (interpolated) value of the base variable at that t and x.
Parameters:
base_variables (list of pybamm.Symbol) – A list of base variables with a method evaluate(t,y), each entry of which
returns the value of that variable for that particular sub-solution.
A Solution can be comprised of sub-solutions which are the solutions of
different models.
Note that this can be any kind of node in the expression tree, not
just a pybamm.Variable.
When evaluated, returns an array of size (m,n)
base_variable_casadis (list of casadi.Function) – A list of casadi functions. When evaluated, returns the same thing as
base_Variable.evaluate (but more efficiently).
solution (pybamm.Solution) – The solution object to be used to create the processed variables
warn (bool, optional) – Whether to raise warnings when trying to evaluate time and length scales.
Default is True.
Returns a dictionary of sensitivities for each input parameter.
The keys are the input parameters, and the value is a matrix of size
(n_x * n_t, n_p), where n_x is the number of states, n_t is the number of time
points, and n_p is the size of the input parameter
Base class for experimental conditions under which to run the model. In general, a
list of operating conditions should be passed in. Each operating condition should
be either a pybamm.step._Step class, created using one of the methods
pybamm.step.current, pybamm.step.c_rate, pybamm.step.voltage
, pybamm.step.power, pybamm.step.resistance, or
pybamm.step.string, or a string, in which case the string is passed to
pybamm.step.string.
Parameters:
operating_conditions (list[str]) – List of strings representing the operating conditions.
period (string, optional) – Period (1/frequency) at which to record outputs. Default is 1 minute. Can be
overwritten by individual operating conditions.
temperature (float, optional) – The ambient air temperature in degrees Celsius at which to run the experiment.
Default is None whereby the ambient temperature is taken from the parameter set.
This value is overwritten if the temperature is specified in a step.
termination (list[str], optional) – List of strings representing the conditions to terminate the experiment. Default is None.
This is different from the termination for individual steps. Termination for
individual steps is specified in the step itself, and the simulation moves to
the next step when the termination condition is met
(e.g. 2.5V discharge cut-off). Termination for the
experiment as a whole is specified here, and the simulation stops when the
termination condition is met (e.g. 80% capacity).
string (str) – The string to parse. Each operating condition should
be of the form “Do this for this long” or “Do this until this happens”. For
example, “Charge at 1 C for 1 hour”, or “Charge at 1 C until 4.2 V”, or “Charge
at 1 C for 1 hour or until 4.2 V”. The instructions can be of the form
“(Dis)charge at x A/C/W”, “Rest”, or “Hold at x V until y A”. The running
time should be a time in seconds, minutes or hours, e.g. “10 seconds”,
“3 minutes” or “1 hour”. The stopping conditions should be
a circuit state, e.g. “1 A”, “C/50” or “3 V”.
**kwargs – Any other keyword arguments are passed to the pybamm.step._Step
class.
Class representing one step in an experiment.
All experiment steps are functions that return an instance of this class.
This class is not intended to be used directly.
Parameters:
typ (str) – The type of step, can be “current”, “voltage”, “c_rate”, “power”,
or “resistance”.
value (float) – The value of the step, corresponding to the type of step. Can be a number, a
2-tuple (for cccv_ode), or a 2-column array (for drive cycles)
duration (float, optional) – The duration of the step in seconds.
termination (str or list, optional) – A string or list of strings indicating the condition(s) that will terminate the
step. If a list, the step will terminate when any of the conditions are met.
period (float or string, optional) – The period of the step. If a float, the value is in seconds. If a string, the
value should be a valid time string, e.g. “1 hour”.
temperature (float or string, optional) – The temperature of the step. If a float, the value is in Kelvin. If a string,
the value should be a valid temperature string, e.g. “25 oC”.
tags (str or list, optional) – A string or list of strings indicating the tags associated with the step.
datetime (str or datetime, optional) – A string or list of strings indicating the tags associated with the step.
description (str, optional) – A description of the step.
Return a basic representation of the step, only with type, value, termination
and temperature, which are the variables involved in processing the model. Also
used for hashing.
Standard step termination events are implemented by the following classes, which are
called when the termination is specified by a specific string. These classes can be
either be called directly or via the string format specified in the class docstring
Base class for a termination event for an experiment step. To create a custom
termination, a class must implement get_event to return a pybamm.Event
corresponding to the desired termination. In most cases the class
pybamm.step.CustomTermination can be used to assist with this.
Parameters:
value (float) – The value at which the event is triggered
Return a pybamm.Event object corresponding to the termination event
Parameters:
variables (dict) – Dictionary of model variables, to be used for selecting the variable(s) that
determine the event
step_value (float or pybamm.Symbol) – Value of the step for which this is a termination event, to be used in some
cases to determine the sign of the event.
event_function (callable) – A function that takes in a dictionary of variables and evaluates the event
value. Must be positive before the event is triggered and zero when the
event is triggered.
Example
Add a cut-off based on negative electrode stoichiometry. The event will trigger
when the negative electrode stoichiometry reaches 10%.
experiment (pybamm.Experiment or string or list (optional)) – The experimental conditions under which to solve the model. If a string is
passed, the experiment is constructed as pybamm.Experiment([experiment]). If
a list is passed, the experiment is constructed as
pybamm.Experiment(experiment).
geometry (pybamm.Geometry (optional)) – The geometry upon which to solve the model
parameter_values (pybamm.ParameterValues (optional)) – Parameters and their corresponding numerical values.
submesh_types (dict (optional)) – A dictionary of the types of submesh to use on each subdomain
var_pts (dict (optional)) – A dictionary of the number of points used by each spatial variable
spatial_methods (dict (optional)) – A dictionary of the types of spatial method to use on each
domain (e.g. pybamm.FiniteVolume)
solver (pybamm.BaseSolver (optional)) – The solver to use to solve the model.
output_variables (list (optional)) – A list of variables to plot automatically
C_rate (float (optional)) – The C-rate at which you would like to run a constant current (dis)charge.
A method to build the model into a system of matrices and vectors suitable for
performing numerical computations. If the model has already been built or
solved then this function will have no effect.
This method will automatically set the parameters
if they have not already been set.
initial_soc (float, optional) – Initial State of Charge (SOC) for the simulation. Must be between 0 and 1.
If given, overwrites the initial concentrations provided in the parameter
set.
mesh (bool) – The mesh used to discretise the model. If false, plotting tools will not
be available when the model is read back in and solved.
variables (bool) – The discretised variables. Not required to solve a model, but if false
tools will not be availble. Will automatically save meshes as well, required
for plotting tools.
filename (str, optional) – The desired name of the JSON file. If no name is provided, one will be
created based on the model name, and the current datetime.
Set up a simulation to run with an experiment. This creates a dictionary of
inputs (current/voltage/power, running time, stopping condition) for each
operating condition in the experiment. The model will then be solved by
integrating the model successively with each group of inputs, one group at a
time.
This needs to be done here and not in the Experiment class because the nominal
cell capacity (from the parameters) is used to convert C-rate to current.
A method to solve the model. This method will automatically build
and set the model parameters if not already done so.
Parameters:
t_eval (numeric type, optional) –
The times (in seconds) at which to compute the solution. Can be
provided as an array of times at which to return the solution, or as a
list [t0, tf] where t0 is the initial time and tf is the final time.
If provided as a list the solution is returned at 100 points within the
interval [t0, tf].
If not using an experiment or running a drive cycle simulation (current
provided as data) t_eval must be provided.
If running an experiment the values in t_eval are ignored, and the
solution times are specified by the experiment.
If None and the parameter “Current function [A]” is read from data
(i.e. drive cycle simulation) the model will be solved at the times
provided in the data.
solver (pybamm.BaseSolver, optional) – The solver to use to solve the model. If None, Simulation.solver is used
save_at_cycles (int or list of ints, optional) – Which cycles to save the full sub-solutions for. If None, all cycles are
saved. If int, every multiple of save_at_cycles is saved. If list, every
cycle in the list is saved. The first cycle (cycle 1) is always saved.
calc_esoh (bool, optional) – Whether to include eSOH variables in the summary variables. If False
then only summary variables that do not require the eSOH calculation
are calculated. Default is True.
starting_solution (pybamm.Solution) – The solution to start stepping from. If None (default), then self._solution
is used. Must be None if not using an experiment.
initial_soc (float, optional) – Initial State of Charge (SOC) for the simulation. Must be between 0 and 1.
If given, overwrites the initial concentrations provided in the parameter
set.
callbacks (list of callbacks, optional) – A list of callbacks to be called at each time step. Each callback must
implement all the methods defined in pybamm.callbacks.BaseCallback.
showprogress (bool, optional) – Whether to show a progress bar for cycling. If true, shows a progress bar
for cycles. Has no effect when not used with an experiment.
Default is False.
npts (int, optional) – The number of points at which the solution will be returned during
the step dt. Default is 2 (returns the solution at t0 and t0 + dt).
save (bool) – Turn on to store the solution of all previous timesteps
starting_solution (pybamm.Solution) – The solution to start stepping from. If None (default), then self._solution
is used
Generates a quick plot of a subset of key outputs of the model so that the model
outputs can be easily assessed.
Parameters:
solutions ((iter of) pybamm.Solution or pybamm.Simulation) – The numerical solution(s) for the model(s), or the simulation object(s)
containing the solution(s).
output_variables (list of str, optional) – List of variables to plot
labels (list of str, optional) – Labels for the different models. Defaults to model names
colors (list of str, optional) – The colors to loop over when plotting. Defaults to None, in which case the
default color loop defined by matplotlib style sheet or rcParams is used.
linestyles (list of str, optional) – The linestyles to loop over when plotting. Defaults to [“-”, “:”, “–”, “-.”]
shading (str, optional) – The shading to use for 2D plots. Defaults to “auto”.
figsize (tuple of floats, optional) – The size of the figure to make
n_rows (int, optional) – The number of rows to use. If None (default), floor(n // sqrt(n)) is used where
n = len(output_variables) so that the plot is as square as possible
time_unit (str, optional) – Format for the time output (“hours”, “minutes”, or “seconds”)
spatial_unit (str, optional) – Format for the spatial axes (“m”, “mm”, or “um”)
Creates a pybamm.QuickPlot object (with arguments ‘args’ and keyword
arguments ‘kwargs’) and then calls pybamm.QuickPlot.dynamic_plot().
The key-word argument ‘testing’ is passed to the ‘dynamic_plot’ method, not the
QuickPlot class.
pybamm.plot2D(x, y, z, ax=None, testing=False, **kwargs)[source]#
Generate a simple 2D plot. Calls matplotlib.pyplot.contourf with keyword
arguments ‘kwargs’. For a list of ‘kwargs’ see the
matplotlib contourf documentation
Parameters:
x (pybamm.Array) – The array to plot on the x axis. Can be of shape (M, N) or (N, 1)
y (pybamm.Array) – The array to plot on the y axis. Can be of shape (M, N) or (M, 1)
z (pybamm.Array) – The array to plot on the z axis. Is of shape (M, N)
ax (matplotlib Axis, optional) – The axis on which to put the plot. If None, a new figure and axis is created.
testing (bool, optional) – Whether to actually make the plot (turned off for unit tests)
Search dictionary for keys containing ‘key’. If print_values is True, then
both the keys and values will be printed. Otherwise just the values will
be printed. If no results are found, the best matches are printed.
Base class for callbacks, for documenting callback methods.
Callbacks are used to perform actions (e.g. logging, saving) at certain points in
the simulation. Each callback method is named on_<event>, where <event>
describes the point at which the callback is called. For example, the callback
on_experiment_start is called at the start of an experiment simulation. In
general, callbacks take a single argument, logs, which is a dictionary of
information about the simulation. Each callback method should return None
(the output of the method is ignored).
EXPERIMENTAL - this class is experimental and the callback interface may
change in future releases.
Entry point to citations management.
This object may be used to record BibTeX citation information and then register that
a particular citation is relevant for a particular simulation.
Citations listed in pybamm/CITATIONS.bib can be registered with their citation
key. For all other works provide a BibTeX Citation to register().
Print all citations that were used for running simulations. The verbose
option is provided to print tags for citations in the output such that it can
can be seen where the citations were registered due to the use of PyBaMM models
and solvers in the code.
Note
If a citation is registered manually, it will not be tagged.
Warning
This function will notify the user if a citation that has been previously
registered is invalid or cannot be parsed.
Parameters:
filename (str, optional) – Filename to which to print citations. If None, citations are printed
to the terminal.
verbose (bool, optional) – If True, prints the citation tags for the citations that have been
registered. An example of the output is shown below.
Register a paper to be cited, one at a time. The intended use is that
register() should be called only when the referenced functionality is
actually being used.
Warning
Registering a BibTeX citation, with the same key as an existing citation,
will overwrite the current citation.
A BatchStudy class for comparison of different PyBaMM simulations.
Parameters:
models (dict) – A dictionary of models to be simulated
experiments (dict (optional)) – A dictionary of experimental conditions under which to solve the model.
Default is None
geometries (dict (optional)) – A dictionary of geometries upon which to solve the model
parameter_values (dict (optional)) – A dictionary of parameters and their corresponding numerical values.
Default is None
submesh_types (dict (optional)) – A dictionary of the types of submesh to use on each subdomain.
Default is None
var_pts (dict (optional)) – A dictionary of the number of points used by each spatial variable.
Default is None
spatial_methods (dict (optional)) – A dictionary of the types of spatial method to use on each domain.
Default is None
solvers (dict (optional)) – A dictionary of solvers to use to solve the model. Default is None
output_variables (dict (optional)) – A dictionary of variables to plot automatically. Default is None
C_rates (dict (optional)) – A dictionary of C-rates at which you would like to run a constant current
(dis)charge. Default is None
repeats (int (optional)) – The number of times solve should be called. Default is 1
permutations (bool (optional)) – If False runs first model with first solver, first experiment
and second model with second solver, second experiment etc.
If True runs a cartesian product of models, solvers and experiments.
Default is False
This folder contains a collection of Jupyter notebooks that demonstrate how to use PyBaMM and reveal some of its functionalities and inner workings.
The notebooks are organised into subfolders, and can be viewed in the galleries below.
Tip
An interactive online version of this notebook is available, which can be
accessed via
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpy=pybamm.StateVector(slice(0,1))t=pybamm.tequation=2*y*(1-y)+tequation.visualise("expression_tree1.png")
Note: you may need to restart the kernel to use updated packages.
Once the equation is constructed, we can evaluate it at a given \(t=1\) and \(\mathbf{y}=\begin{pmatrix} 2 \end{pmatrix}\).
[2]:
equation.evaluate(1,np.array([2]))
[2]:
array([[-3.]])
We can also calculate the expression tree representing the gradient of the equation with respect to \(t\),
Proposing, parameter setting and discretising a model in PyBaMM is a pipeline process, consisting of the following steps:
The model is proposed, consisting of equations representing the right-hand-side of an ordinary differential equation (ODE), and/or algebraic equations for a differential algebraic equation (DAE), and also associated boundary condition equations
The equations in the model are discretised onto a mesh, any spatial gradients are replaced with linear algebra expressions and the variables of the model are replaced with state vector slices. This is done using the `pybamm.Discretisation <https://docs.pybamm.org/en/latest/source/api/spatial_methods/discretisation.html>`__ class.
In the second stage, the pybamm.ParameterValues class is used to replace all the parameter nodes with scalar values, according to an input parameter file. For example, we’ll use a this class to set \(D = 2\)
The third and final stage uses the pybamm.Discretisation class to discretise the spatial gradients and variables over a given mesh. After this stage the expression tree will encode a linear algebra expression that can be evaluated given the state vector \(\mathbf{y}\) and \(t\).
Note: for demonstration purposes, we use a dummy discretisation below. For a more complete description of the pybamm.Discretisation class, see the example notebook here.
[7]:
# Here, we import a dummy discretisation from the PyBaMM tests directory.importsyssys.path.insert(0,pybamm.root_dir())fromtestsimportget_discretisation_for_testingdisc=get_discretisation_for_testing()disc.y_slices={c:[slice(0,40)]}dcdt=disc.process_symbol(dcdt)dcdt.visualise("expression_tree5.png")
After the third stage, our expression tree is now able to be evaluated by one of the solver classes. Note that we have used a single equation above to illustrate the different types of expression trees in PyBaMM, but any given models will consist of many RHS or algebraic equations, along with boundary conditions. See here for more details of PyBaMM models.
[1] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[2] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
This notebook explains the different types of broadcast available in PyBaMM. Understanding of the expression_tree and discretisation notebooks is assumed.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnp
Note: you may need to restart the kernel to use updated packages.
We also explicitly set up the discretisation that is used for this notebook. We use a small number of points in each domain, in order to easily visualise the results.
Primary broadcasts are used to broadcast from a “larger” scale to a “smaller” scale, for example broadcasting temperature T(x) from the electrode to the particles, or broadcasting current collector current i(y, z) from the current collector to the electrodes. To demonstrate this, we first create a variable T on the negative electrode domain, discretise it, and evaluate it with a simple linear vector
We then broadcast T onto the “negative particle” domain (using primary broadcast as we are going from the larger electrode scale to the smaller particle scale), and discretise and evaluate the resulting object.
The broadcasted object makes 3 (since the r-grid has 3 points) copies of each element of T and stacks them all up to give an object with size 3x5=15. In the resulting vector, the first 3 entries correspond to the 3 points in the r-domain at the first x-grid point (where T=0 uniformly in r), the next 3 entries correspond to the next 3 points in the r-domain at the second x-grid point (where T=0.25 uniformly in r), etc
Secondary broadcasts are used to broadcast from a “smaller” scale to a “larger” scale, for example broadcasting SPM particle concentrations c_s(r) from the particles to the electrodes. Note that this wouldn’t be used to broadcast particle concentrations in the DFN, since these already depend on both x and r. To demonstrate this, we first create a variable c_s on the negative particle domain, discretise it, and evaluate it with a simple linear vector
We then broadcast c_s onto the “negative electrode” domain (using secondary broadcast as we are going from the smaller particle scale to the large electrode scale), and discretise and evaluate the resulting object.
The broadcasted object makes 5 (since the x-grid has 5 points) identical copies of the whole variable c_s to give an object with size 5x3=15. In the resulting vector, the first 3 entries correspond to the 3 points in the r-domain at the first x-grid point (where c_s varies in r), the next 3 entries correspond to the next 3 points in the r-domain at the second x-grid point (where c_s varies in r), etc
[1] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[2] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this notebook we compare the discharge curves obtained by solving the DFN model both in PyBaMM and COMSOL. Results are presented for a range of C-rates, and we see an excellent agreement between the two implementations. If you would like to compare internal variables please see the script compare_comsol_DFN which creates a slider plot comparing potentials and concentrations as
functions of time and space for a given C-rate. For more information on the DFN model, see the DFN notebook.
First we need to import pybamm, and then change our working directory to the root of the pybamm folder.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpimportosimportpickleimportmatplotlib.pyplotaspltos.chdir(pybamm.__path__[0]+"/..")
We then create a dictionary of the C-rates we would like to solve for and compare. Note that the repository currently only contains COMSOL results for the C-rates listed below.
[2]:
C_rates={"01":0.1,"05":0.5,"1":1,"2":2,"3":3}
We get the DFN model equations, geometry, and default parameters. Before processing the model, we adjust the electrode height and depth to be 1 m, and also adjust the electrode conductivities, to match the one-dimensional model we solved in COMSOL. The model is then processed using the default geometry and updated parameters. Finally, we create a mesh and discretise the model.
[3]:
# load model and geometrymodel=pybamm.lithium_ion.DFN()geometry=model.default_geometry# load parameters and process model and geometryparam=model.default_parameter_valuesparam.update({"Electrode width [m]":1,"Electrode height [m]":1,"Negative electrode conductivity [S.m-1]":126,"Positive electrode conductivity [S.m-1]":16.6,"Current function [A]":"[input]",})param.process_model(model)param.process_geometry(geometry)# create meshvar_pts={"x_n":31,"x_s":11,"x_p":31,"r_n":11,"r_p":11}mesh=pybamm.Mesh(geometry,model.default_submesh_types,var_pts)# discretise modeldisc=pybamm.Discretisation(mesh,model.default_spatial_methods)disc.process_model(model);
We create the figure by looping over the dictionary of C-rates. In each step of the loop we load the COMSOL results from a .csv file and solve the DFN model in pybamm. The output variables are then processed, allowing us to plot the discharges curve computed using pybamm and COMSOL, and their absolute difference.
[4]:
# create figurefig,ax=plt.subplots(figsize=(15,8))plt.tight_layout()plt.subplots_adjust(left=-0.1)discharge_curve=plt.subplot(211)plt.xlim([0,26])plt.ylim([3.2,3.9])plt.xlabel(r"Discharge Capacity (Ah)")plt.ylabel("Voltage (V)")plt.title(r"Comsol $\cdots$ PyBaMM $-$")voltage_difference_plot=plt.subplot(212)plt.xlim([0,26])plt.yscale("log")plt.grid(True)plt.xlabel(r"Discharge Capacity (Ah)")plt.ylabel(r"$\vert V - V_{comsol} \vert$")colors=iter(plt.cycler(color="bgrcmyk"))# loop over C_rates dict to create plotforkey,C_rateinC_rates.items():# load the comsol resultscomsol_results_path=pybamm.get_parameters_filepath(f"input/comsol_results/comsol_{key}C.pickle",)comsol_variables=pickle.load(open(comsol_results_path,"rb"))comsol_time=comsol_variables["time"]comsol_voltage=comsol_variables["voltage"]# update current densitycurrent=24*C_rate# solve model at comsol timessolver=pybamm.CasadiSolver(mode="fast")solution=solver.solve(model,comsol_time,inputs={"Current function [A]":current})time_in_seconds=solution["Time [s]"].entries# discharge capacitydischarge_capacity=solution["Discharge capacity [A.h]"]discharge_capacity_sol=discharge_capacity(time_in_seconds)comsol_discharge_capacity=comsol_time*current/3600# extract the voltagevoltage=solution["Voltage [V]"]voltage_sol=voltage(time_in_seconds)# calculate the difference between the two solution methodsend_index=min(len(time_in_seconds),len(comsol_time))voltage_difference=np.abs(voltage_sol[0:end_index]-comsol_voltage[0:end_index])# plot discharge curves and absolute voltage_differencecolor=next(colors)["color"]discharge_curve.plot(comsol_discharge_capacity,comsol_voltage,color=color,linestyle=":")discharge_curve.plot(discharge_capacity_sol,voltage_sol,color=color,linestyle="-",label=f"{C_rate} C",)voltage_difference_plot.plot(discharge_capacity_sol[0:end_index],voltage_difference,color=color)discharge_curve.legend(loc="best")plt.subplots_adjust(top=0.92,bottom=0.08,left=0.10,right=0.95,hspace=0.25,wspace=0.35)plt.show()
/var/folders/z4/5lmf5d5d23sc2gkhs__zfnfc0000gn/T/ipykernel_2839/4153409347.py:5: MatplotlibDeprecationWarning: Auto-removal of overlapping axes is deprecated since 3.6 and will be removed two minor releases later; explicitly call ax.remove() as needed.
discharge_curve = plt.subplot(211)
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[5] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[ ]:
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this notebook we show how to compare results generated in PyBaMM with experimental data. We compare the results of the DFN model (see the DFN notebook) with the experimental data from Ecker et. al. [3]. Results are compared for a constant current discharge at 1C and at 5C.
First we import pybamm and any other packages required by this example, and then change our working directory to the root of the pybamm folder.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportosimportpandasaspdimportnumpyasnpimportmatplotlib.pyplotaspltos.chdir(pybamm.__path__[0]+"/..")
Note: you may need to restart the kernel to use updated packages.
We then load the Ecker data in from the .csv files using pandas
We load the DFN model and select the parameter set from the Ecker paper [1]. We update the C-rate an InputParameter so that we can re-run the same model at different C-rates without the need to rebuild the model. This is done by passing the flag [input].
[3]:
# choose DFNmodel=pybamm.lithium_ion.DFN()# pick parameters, keeping C-rate as an input to be changed for each solveparameter_values=pybamm.ParameterValues("Ecker2015")parameter_values.update({"Current function [A]":"[input]"})
For this comparison we choose a fine mesh of 1 finite volume per micron in the electrodes and separator and 1 finite volume per 0.1 micron in the particles
We can then solve the model for a 1C and 5C discharge
[6]:
C_rates=[1,5]# C-rates to solve forcapacity=parameter_values["Nominal cell capacity [A.h]"]t_evals=[np.linspace(0,3800,100),np.linspace(0,720,100),]# times to return the solution atsolutions=[None]*len(C_rates)# empty list that will hold solutions# loop over C-ratesfori,C_rateinenumerate(C_rates):current=C_rate*capacitysim.solve(t_eval=t_evals[i],solver=pybamm.CasadiSolver(mode="fast"),inputs={"Current function [A]":current},)solutions[i]=sim.solution
Finally we plot the numerical solution against the experimental data
[7]:
fig,(ax1,ax2)=plt.subplots(1,2,figsize=(13,4))# plot the 1C resultst_sol=solutions[0]["Time [s]"].entriesax1.plot(t_sol,solutions[0]["Voltage [V]"](t_sol))ax1.plot(voltage_data_1C[:,0],voltage_data_1C[:,1],"o")ax1.set_xlabel("Time [s]")ax1.set_ylabel("Voltage [V]")ax1.set_title("1C")ax1.legend(["DFN","Experiment"],loc="best")# plot the 5C resultst_sol=solutions[1]["Time [s]"].entriesax2.plot(t_sol,solutions[1]["Voltage [V]"](t_sol))ax2.plot(voltage_data_5C[:,0],voltage_data_5C[:,1],"o")ax2.set_xlabel("Time [s]")ax2.set_ylabel("Voltage [V]")ax2.set_title("5C")ax2.legend(["DFN","Experiment"],loc="best")plt.tight_layout()plt.show()
For a 1C discharge we observe an excellent agreement between the model and experiment, both in terms of the overall shape of the curve and the capacity. The agreement between model and experiment is less good at 5C, but in line with other implementations of the DFN (e.g. [6]).
[1] Weilong Ai, Ludwig Kraft, Johannes Sturm, Andreas Jossen, and Billy Wu. Electrochemical thermal-mechanical modelling of stress inhomogeneity in lithium-ion pouch cells. Journal of The Electrochemical Society, 167(1):013512, 2019. doi:10.1149/2.0122001JES.
[2] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[3] Rutooj Deshpande, Mark Verbrugge, Yang-Tse Cheng, John Wang, and Ping Liu. Battery cycle life prediction with coupled chemical degradation and fatigue mechanics. Journal of the Electrochemical Society, 159(10):A1730, 2012. doi:10.1149/2.049210jes.
[4] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[5] Madeleine Ecker, Stefan Käbitz, Izaro Laresgoiti, and Dirk Uwe Sauer. Parameterization of a Physico-Chemical Model of a Lithium-Ion Battery: II. Model Validation. Journal of The Electrochemical Society, 162(9):A1849–A1857, 2015. doi:10.1149/2.0541509jes.
[6] Madeleine Ecker, Thi Kim Dung Tran, Philipp Dechent, Stefan Käbitz, Alexander Warnecke, and Dirk Uwe Sauer. Parameterization of a Physico-Chemical Model of a Lithium-Ion Battery: I. Determination of Parameters. Journal of the Electrochemical Society, 162(9):A1836–A1848, 2015. doi:10.1149/2.0551509jes.
[7] Alastair Hales, Laura Bravo Diaz, Mohamed Waseem Marzook, Yan Zhao, Yatish Patel, and Gregory Offer. The cell cooling coefficient: a standard to define heat rejection from lithium-ion batteries. Journal of The Electrochemical Society, 166(12):A2383, 2019.
[8] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[9] Giles Richardson, Ivan Korotkin, Rahifa Ranom, Michael Castle, and Jamie M. Foster. Generalised single particle models for high-rate operation of graded lithium-ion electrodes: systematic derivation and validation. Electrochimica Acta, 339:135862, 2020. doi:10.1016/j.electacta.2020.135862.
[10] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[11] Yan Zhao, Yatish Patel, Teng Zhang, and Gregory J Offer. Modeling the effects of thermal gradients induced by tab and surface cooling on lithium ion cell performance. Journal of The Electrochemical Society, 165(13):A3169, 2018.
Tip
An interactive online version of this notebook is available, which can be
accessed via
But, as always we first import pybamm and other required modules
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportosos.chdir(pybamm.__path__[0]+"/..")importnumpyasnpimportmatplotlib.pyplotasplt
WARNING: You are using pip version 22.0.4; however, version 22.3.1 is available.
You should consider upgrading via the '/home/mrobins/git/PyBaMM/env/bin/python -m pip install --upgrade pip' command.Note: you may need to restart the kernel to use updated packages.
To allow us to perform the same operations on each model easily, we create a dictionary of these three models:
[3]:
models={"DFN":dfn,"SPM":spm,"SPMe":spme}
Each model can then be accessed using:
[4]:
models["DFN"]
[4]:
<pybamm.models.full_battery_models.lithium_ion.dfn.DFN at 0x7f917c709f40>
For each model, we must also provide a cell geometry. The geometry is different for different models; for example, the SPM has solves for a single particle in each electrode whereas the DFN solves for many particles. For simplicity, we use the default geometry associated with each model but note that this can be easily changed.
For simplicity, we use the default parameters values associated with the DFN model, but change the current function to be an input so that we can quickly solve the model with different currents
[6]:
param=dfn.default_parameter_valuesparam["Current function [A]"]="[input]"
It is simple to change this to a different parameter set if desired.
We then process the parameters in each of the models and geometries using this parameter set:
We use the defaults mesh properties (the types of meshes and number of points to be used) for simplicity to generate a mesh of each model geometry. We store these meshes in a dictionary of similar structure to the geometry and models dictionaries:
We now solve each model using the default solver associated with each model:
[10]:
timer=pybamm.Timer()solutions={}t_eval=np.linspace(0,3600,300)# time in secondsformodel_name,modelinmodels.items():solver=pybamm.CasadiSolver()timer.reset()solution=solver.solve(model,t_eval,inputs={"Current function [A]":1})print(f"Solved the {model.name} in {timer.time()}")solutions[model_name]=solution
Solved the Doyle-Fuller-Newman model in 266.815 ms
Solved the Single Particle Model in 20.571 ms
Solved the Single Particle Model with electrolyte in 38.459 ms
To plot results, we extract the variables from the solutions dictionary. Matplotlib can then be used to plot the voltage predictions of each models as follows:
Alternatively the inbuilt QuickPlot functionality can be employed to compare a set of variables over the discharge. We must first create a list of the solutions
Since we have made current an input, it is easy to change it and then perform the calculations again:
[14]:
# update parameter values and solve again# simulate for shorter timet_eval=np.linspace(0,800,300)formodel_name,modelinmodels.items():solutions[model_name]=model.default_solver.solve(model,t_eval,inputs={"Current function [A]":3})# Plotlist_of_solutions=list(solutions.values())quick_plot=pybamm.QuickPlot(list_of_solutions)quick_plot.dynamic_plot();
By increasing the current we observe less agreement between the models, as expected.
[1] Weilong Ai, Ludwig Kraft, Johannes Sturm, Andreas Jossen, and Billy Wu. Electrochemical thermal-mechanical modelling of stress inhomogeneity in lithium-ion pouch cells. Journal of The Electrochemical Society, 167(1):013512, 2019. doi:10.1149/2.0122001JES.
[2] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[3] Rutooj Deshpande, Mark Verbrugge, Yang-Tse Cheng, John Wang, and Ping Liu. Battery cycle life prediction with coupled chemical degradation and fatigue mechanics. Journal of the Electrochemical Society, 159(10):A1730, 2012. doi:10.1149/2.049210jes.
[4] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[5] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[6] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[7] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this notebook we compare the different models for mass transport within the electrode particles. For a full list of all the particle models, see the documentation.
With the “Fickian diffusion” option a diffusion equation is solved within the particle domain, with the boundary flux prescribed at the surface related to the local current density. Alternatively, one can assume a particular (polynomial) concentration profile within the particle (at present, this can be uniform, quadratic, or quartic). The “uniform profile” model assumes that the concentration inside the particle is uniform in space (and therefore equal to the surface concentration through the
entire particle - in effect ignoring transport resistance within the particle), and solves an ODE for the average particle concentration. The “quadratic profile” model additionally solves an algebraic equation for the surface concentration, taking into account the effect of diffusion within the particle. Finally, the “quartic profile” model also solves for the average concentration gradient (the integral of \(\partial c/ \partial r\)) in the particle, giving a higher-order approximation to
the concentration profile within the particle.
As the exchange current density is a function of surface concentration, we can see the effect the choice of particle model has on the voltage profile arising from different overpotentials.
First we import the packages we’re going to use
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportosimportnumpyasnpimportmatplotlib.pyplotaspltos.chdir(pybamm.__path__[0]+"/..")
Note: you may need to restart the kernel to use updated packages.
We then create a list of models with the different particle diffusion submodels using the options functionality
Next we set up simulations for each model with the current set as an input function. We will change this later to observe the effect on the different models.
[3]:
simulations=[]formodelinmodels:param=model.default_parameter_valuesparam["Current function [A]"]="[input]"simulations.append(pybamm.Simulation(model,parameter_values=param))
[4]:
t_eval=np.linspace(0,3600,72)
First we run the simulations for each model with a current of 0.68A - this corresponds to a C-rate of about 1 with the standard cell size configured in the default parameters.
[5]:
solutions_1C=[]forsiminsimulations:sim.solve(t_eval,inputs={"Current function [A]":0.68})solutions_1C.append(sim.solution)print(f"Particle model: {sim.model.name}")print(f"Solve time: {sim.solution.solve_time}s")
We can see that the Fickian, quadratic and quartic profiles agree very well and that the uniform profile over-predicts the cell voltage and capacity by ignoring this transport resistance. The only significant difference between the Fickian and quadratic models is on the first datapoint when transient effects after the initial state may differ. Observe what happens next when we increase the current.
[7]:
t_eval=np.linspace(0,1800,72)solutions_2C=[]forsiminsimulations:sim.solve(t_eval,inputs={"Current function [A]":2*0.68})solutions_2C.append(sim.solution)
The quadratic model is still much better at approximating Fickian diffusion and the relative error in the uniform model has increased. However, the initial error in the quadratic model has grown slightly. Increasing current even more will highlight the problem further. The quartic model is still providing an excellent match to the Fickian diffusion profile.
[9]:
t_eval=np.linspace(0,360,72)solutions_6C=[]forsiminsimulations:sim.solve(t_eval,inputs={"Current function [A]":6*0.68})solutions_6C.append(sim.solution)
Now the quadratic profile assumption begins to breakdown and that initial error propagates much further into the discharge. Happily the quartic model is higher-order and the match to the Fickian profile is still very good.
Finally we can take a look at some of the internal states using PyBaMM’s dynamic_plot
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[5] Venkat R. Subramanian, Vinten D. Diwakar, and Deepak Tapriyal. Efficient macro-micro scale coupled modeling of batteries. Journal of The Electrochemical Society, 152(10):A2002, 2005. doi:10.1149/1.2032427.
[6] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
A composite electrode particle model is developed for (negative) electrodes with two phases, e.g. graphite/silicon in LG M50 battery cells. The current version is demonstrated for negative composite electrodes only but is easily extended to positive composite electrodes. The reference is at the end of this notebook.
# %pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installedimportosimportmatplotlib.pyplotaspltimportnumpyasnpimportpybammimporttimeitfrommatplotlibimportstylestyle.use("ggplot")os.chdir(pybamm.__path__[0]+"/..")pybamm.set_logging_level("INFO")
Choose the option {"particlephases":("2","1")} to load the composite electrode particle model by specifying that there are two particle phases (graphite and silicon) in the negative electrode. The parameter set “Chen2020_composite” includes parameters for silicon as a secondary particle
[2]:
start=timeit.default_timer()model=pybamm.lithium_ion.DFN({"particle phases":("2","1"),"open-circuit potential":(("single","current sigmoid"),"single"),})param=pybamm.ParameterValues("Chen2020_composite")param.update({"Upper voltage cut-off [V]":4.5})param.update({"Lower voltage cut-off [V]":2.5})param.update({"Primary: Maximum concentration in negative electrode [mol.m-3]":28700,"Primary: Initial concentration in negative electrode [mol.m-3]":23000,"Primary: Negative electrode diffusivity [m2.s-1]":5.5e-14,"Secondary: Negative electrode diffusivity [m2.s-1]":1.67e-14,"Secondary: Initial concentration in negative electrode [mol.m-3]":277000,"Secondary: Maximum concentration in negative electrode [mol.m-3]":278000,})
2023-02-21 09:08:46.318 - [INFO] base_model._build_model(550): Start building Doyle-Fuller-Newman model
2023-02-21 09:08:46.452 - [INFO] base_battery_model.build_model(970): Finish building Doyle-Fuller-Newman model
C_rate=0.5capacity=param["Nominal cell capacity [A.h]"]I_load=C_rate*capacityt_eval=np.linspace(0,10000,1000)param["Current function [A]"]=I_load
It is very easy to vary the relative volume fraction of each phase. The following example shows how to compare the results of batteries with three relative volume fractions (0.001, 0.04, 0.1) of silicon.
[4]:
v_si=[0.001,0.04,0.1]total_am_volume_fraction=0.75solution=[]forvinv_si:param.update({"Primary: Negative electrode active material volume fraction":(1-v)*total_am_volume_fraction,# primary"Secondary: Negative electrode active material volume fraction":v*total_am_volume_fraction,})print(v)sim=pybamm.Simulation(model,parameter_values=param,solver=pybamm.CasadiSolver(dt_max=5),)solution.append(sim.solve(t_eval=t_eval))stop=timeit.default_timer()print("running time: "+str(stop-start)+"s")
2023-02-21 09:08:46.528 - [INFO] parameter_values.process_model(425): Start setting parameters for Doyle-Fuller-Newman model
0.001
2023-02-21 09:08:46.741 - [INFO] parameter_values.process_model(527): Finish setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:08:46.743 - [INFO] discretisation.process_model(147): Start discretising Doyle-Fuller-Newman model
2023-02-21 09:08:46.750 - [INFO] discretisation.remove_independent_variables_from_rhs(1120): removing variable Discharge capacity [A.h] from rhs
2023-02-21 09:08:47.292 - [INFO] discretisation.process_model(259): Finish discretising Doyle-Fuller-Newman model
2023-02-21 09:08:47.293 - [INFO] base_solver.solve(703): Start solving Doyle-Fuller-Newman model with CasADi solver with 'safe' mode
2023-02-21 09:08:47.297 - [INFO] base_solver.set_up(111): Start solver set-up
2023-02-21 09:08:47.433 - [INFO] base_solver.set_up(236): Finish solver set-up
2023-02-21 09:08:55.129 - [INFO] base_solver.solve(937): Finish solving Doyle-Fuller-Newman model (event: Minimum voltage)
2023-02-21 09:08:55.130 - [INFO] base_solver.solve(938): Set-up time: 136.675 ms, Solve time: 7.685 s (of which integration time: 6.012 s), Total time: 7.821 s
2023-02-21 09:08:55.137 - [INFO] parameter_values.process_model(425): Start setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:08:55.250 - [INFO] parameter_values.process_model(527): Finish setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:08:55.252 - [INFO] discretisation.process_model(147): Start discretising Doyle-Fuller-Newman model
2023-02-21 09:08:55.260 - [INFO] discretisation.remove_independent_variables_from_rhs(1120): removing variable Discharge capacity [A.h] from rhs
0.04
2023-02-21 09:08:55.808 - [INFO] discretisation.process_model(259): Finish discretising Doyle-Fuller-Newman model
2023-02-21 09:08:55.809 - [INFO] base_solver.solve(703): Start solving Doyle-Fuller-Newman model with CasADi solver with 'safe' mode
2023-02-21 09:08:55.814 - [INFO] base_solver.set_up(111): Start solver set-up
2023-02-21 09:08:55.947 - [INFO] base_solver.set_up(236): Finish solver set-up
2023-02-21 09:09:06.233 - [INFO] base_solver.solve(937): Finish solving Doyle-Fuller-Newman model (event: Minimum voltage)
2023-02-21 09:09:06.234 - [INFO] base_solver.solve(938): Set-up time: 134.145 ms, Solve time: 10.272 s (of which integration time: 8.058 s), Total time: 10.407 s
2023-02-21 09:09:06.242 - [INFO] parameter_values.process_model(425): Start setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:09:06.444 - [INFO] parameter_values.process_model(527): Finish setting parameters for Doyle-Fuller-Newman model
0.1
2023-02-21 09:09:06.447 - [INFO] discretisation.process_model(147): Start discretising Doyle-Fuller-Newman model
2023-02-21 09:09:06.453 - [INFO] discretisation.remove_independent_variables_from_rhs(1120): removing variable Discharge capacity [A.h] from rhs
2023-02-21 09:09:07.047 - [INFO] discretisation.process_model(259): Finish discretising Doyle-Fuller-Newman model
2023-02-21 09:09:07.048 - [INFO] base_solver.solve(703): Start solving Doyle-Fuller-Newman model with CasADi solver with 'safe' mode
2023-02-21 09:09:07.052 - [INFO] base_solver.set_up(111): Start solver set-up
2023-02-21 09:09:07.200 - [INFO] base_solver.set_up(236): Finish solver set-up
At t = 0.00076482 and h = 3.55373e-22, the corrector convergence failed repeatedly or with |h| = hmin.
At t = 0.000250492 and h = 1.20507e-16, the corrector convergence failed repeatedly or with |h| = hmin.
psetup failed: .../casadi/interfaces/sundials/idas_interface.cpp:849: Calculating Jacobian failed
At t = 0.000250493 and h = 2.0588e-18, the corrector convergence failed repeatedly or with |h| = hmin.
psetup failed: .../casadi/interfaces/sundials/idas_interface.cpp:852: Linear solve failed
At t = 0.000121913 and h = 6.48118e-25, the corrector convergence failed repeatedly or with |h| = hmin.
psetup failed: .../casadi/interfaces/sundials/idas_interface.cpp:852: Linear solve failed
At t = 5.76225e-05 and h = 3.13053e-22, the corrector convergence failed repeatedly or with |h| = hmin.
psetup failed: .../casadi/interfaces/sundials/idas_interface.cpp:852: Linear solve failed
psetup failed: .../casadi/interfaces/sundials/idas_interface.cpp:852: Linear solve failed
At t = 2.54755e-05 and h = 4.28889e-31, the corrector convergence failed repeatedly or with |h| = hmin.
psetup failed: .../casadi/interfaces/sundials/idas_interface.cpp:852: Linear solve failed
psetup failed: .../casadi/interfaces/sundials/idas_interface.cpp:852: Linear solve failed
psetup failed: .../casadi/interfaces/sundials/idas_interface.cpp:852: Linear solve failed
psetup failed: .../casadi/interfaces/sundials/idas_interface.cpp:852: Linear solve failed
At t = 9.40462e-06 and h = 4.82262e-22, the corrector convergence failed repeatedly or with |h| = hmin.
2023-02-21 09:09:21.630 - [INFO] base_solver.solve(937): Finish solving Doyle-Fuller-Newman model (event: Minimum voltage)
2023-02-21 09:09:21.631 - [INFO] base_solver.solve(938): Set-up time: 148.688 ms, Solve time: 14.417 s (of which integration time: 9.176 s), Total time: 14.566 s
plt.figure()foriinrange(0,len(v_si)):t_i=solution[i]["Time [s]"].entries/3600j_n_p1_av=solution[i]["X-averaged negative electrode primary interfacial current density [A.m-2]"].entriesplt.plot(t_i,j_n_p1_av,ltype[i],label="$V_\mathrm{si}=$"+str(v_si[i]))plt.xlabel("Time [h]")plt.ylabel("Averaged interfacial current density [A/m$^{2}$]")plt.legend()plt.title("Graphite")plt.figure()foriinrange(0,len(v_si)):t_i=solution[i]["Time [s]"].entries/3600j_n_p2_av=solution[i]["X-averaged negative electrode secondary interfacial current density [A.m-2]"].entriesplt.plot(t_i,j_n_p2_av,ltype[i],label="$V_\mathrm{si}=$"+str(v_si[i]))plt.xlabel("Time [h]")plt.ylabel("Averaged interfacial current density [A/m$^{2}$]")plt.legend()plt.title("Silicon")
[6]:
Text(0.5, 1.0, 'Silicon')
Results of interfacial current density in graphite
[7]:
plt.figure()foriinrange(0,len(v_si)):t_i=solution[i]["Time [s]"].entries/3600j_n_p1_Vav=solution[i]["X-averaged negative electrode primary volumetric interfacial current density [A.m-3]"].entriesplt.plot(t_i,j_n_p1_Vav,ltype[i],label="$V_\mathrm{si}=$"+str(v_si[i]))plt.xlabel("Time [h]")plt.ylabel("Averaged volumetric interfacial current density [A/m$^{3}$]")plt.legend()plt.title("Graphite")plt.figure()foriinrange(0,len(v_si)):t_i=solution[i]["Time [s]"].entries/3600j_n_p2_Vav=solution[i]["X-averaged negative electrode secondary volumetric interfacial current density [A.m-3]"].entriesplt.plot(t_i,j_n_p2_Vav,ltype[i],label="$V_\mathrm{si}=$"+str(v_si[i]))plt.xlabel("Time [h]")plt.ylabel("Averaged volumetric interfacial current density [A/m$^{3}$]")plt.legend()plt.title("Silicon")
For multi-cycling, an experiment definition for static C/2 discharge and charge cycling is presented.
[10]:
experiment=pybamm.Experiment([("Discharge at C/2 until 3.0 V","Rest for 1 hour","Charge at C/2 until 4.2 V","Rest for 1 hour",),]*2)
2023-02-21 09:09:26.957 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Discharge at C/2 until 3.0 V', using temperature from parameter values.
2023-02-21 09:09:26.959 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Rest for 1 hour', using temperature from parameter values.
2023-02-21 09:09:26.961 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Charge at C/2 until 4.2 V', using temperature from parameter values.
2023-02-21 09:09:26.963 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Rest for 1 hour', using temperature from parameter values.
2023-02-21 09:09:26.964 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Discharge at C/2 until 3.0 V', using temperature from parameter values.
2023-02-21 09:09:26.965 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Rest for 1 hour', using temperature from parameter values.
2023-02-21 09:09:26.966 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Charge at C/2 until 4.2 V', using temperature from parameter values.
2023-02-21 09:09:26.967 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Rest for 1 hour', using temperature from parameter values.
The solution is reintroduced, with calc_esoh=False passed into the solve function. Currently, composite electrode state of health predictions are not included in this model.
[11]:
solution=[]forvinv_si:param.update({"Primary: Negative electrode active material volume fraction":(1-v)*total_am_volume_fraction,# primary"Secondary: Negative electrode active material volume fraction":v*total_am_volume_fraction,})print(v)sim=pybamm.Simulation(model,experiment=experiment,parameter_values=param,solver=pybamm.CasadiSolver(dt_max=5),)solution.append(sim.solve(calc_esoh=False))stop=timeit.default_timer()print("running time: "+str(stop-start)+"s")
2023-02-21 09:09:27.094 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Discharge at C/2 until 3.0 V', using temperature from parameter values.
2023-02-21 09:09:27.096 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Rest for 1 hour', using temperature from parameter values.
2023-02-21 09:09:27.097 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Charge at C/2 until 4.2 V', using temperature from parameter values.
2023-02-21 09:09:27.098 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Rest for 1 hour', using temperature from parameter values.
2023-02-21 09:09:27.099 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Discharge at C/2 until 3.0 V', using temperature from parameter values.
2023-02-21 09:09:27.099 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Rest for 1 hour', using temperature from parameter values.
2023-02-21 09:09:27.100 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Charge at C/2 until 4.2 V', using temperature from parameter values.
2023-02-21 09:09:27.101 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Rest for 1 hour', using temperature from parameter values.
2023-02-21 09:09:27.115 - [INFO] callbacks.on_experiment_start(166): Start running experiment
2023-02-21 09:09:27.118 - [INFO] parameter_values.process_model(425): Start setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:09:27.244 - [INFO] parameter_values.process_model(527): Finish setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:09:27.246 - [INFO] parameter_values.process_model(425): Start setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:09:27.362 - [INFO] parameter_values.process_model(527): Finish setting parameters for Doyle-Fuller-Newman model
0.001
2023-02-21 09:09:27.364 - [INFO] parameter_values.process_model(425): Start setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:09:27.482 - [INFO] parameter_values.process_model(527): Finish setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:09:27.486 - [INFO] discretisation.process_model(147): Start discretising Doyle-Fuller-Newman model
2023-02-21 09:09:27.492 - [INFO] discretisation.remove_independent_variables_from_rhs(1120): removing variable Discharge capacity [A.h] from rhs
2023-02-21 09:09:28.059 - [INFO] discretisation.process_model(259): Finish discretising Doyle-Fuller-Newman model
2023-02-21 09:09:28.060 - [INFO] discretisation.process_model(147): Start discretising Doyle-Fuller-Newman model
2023-02-21 09:09:28.066 - [INFO] discretisation.remove_independent_variables_from_rhs(1120): removing variable Discharge capacity [A.h] from rhs
2023-02-21 09:09:28.601 - [INFO] discretisation.process_model(259): Finish discretising Doyle-Fuller-Newman model
2023-02-21 09:09:28.602 - [INFO] discretisation.process_model(147): Start discretising Doyle-Fuller-Newman model
2023-02-21 09:09:28.610 - [INFO] discretisation.remove_independent_variables_from_rhs(1120): removing variable Discharge capacity [A.h] from rhs
2023-02-21 09:09:29.139 - [INFO] discretisation.process_model(259): Finish discretising Doyle-Fuller-Newman model
2023-02-21 09:09:29.140 - [NOTICE] callbacks.on_cycle_start(174): Cycle 1/2 (20.167 us elapsed) --------------------
2023-02-21 09:09:29.141 - [NOTICE] callbacks.on_step_start(182): Cycle 1/2, step 1/4: Discharge at C/2 until 3.0 V
2023-02-21 09:09:29.145 - [INFO] base_solver.set_up(111): Start solver set-up
2023-02-21 09:09:29.290 - [INFO] base_solver.set_up(236): Finish solver set-up
2023-02-21 09:09:30.657 - [NOTICE] callbacks.on_step_start(182): Cycle 1/2, step 2/4: Rest for 1 hour
2023-02-21 09:09:30.661 - [INFO] base_solver.set_up(111): Start solver set-up
2023-02-21 09:09:30.800 - [INFO] base_solver.set_up(236): Finish solver set-up
2023-02-21 09:09:31.643 - [NOTICE] callbacks.on_step_start(182): Cycle 1/2, step 3/4: Charge at C/2 until 4.2 V
2023-02-21 09:09:31.647 - [INFO] base_solver.set_up(111): Start solver set-up
2023-02-21 09:09:31.796 - [INFO] base_solver.set_up(236): Finish solver set-up
2023-02-21 09:09:33.027 - [NOTICE] callbacks.on_step_start(182): Cycle 1/2, step 4/4: Rest for 1 hour
2023-02-21 09:09:33.910 - [NOTICE] callbacks.on_cycle_start(174): Cycle 2/2 (4.771 s elapsed) --------------------
2023-02-21 09:09:33.911 - [NOTICE] callbacks.on_step_start(182): Cycle 2/2, step 1/4: Discharge at C/2 until 3.0 V
2023-02-21 09:09:35.119 - [NOTICE] callbacks.on_step_start(182): Cycle 2/2, step 2/4: Rest for 1 hour
2023-02-21 09:09:35.867 - [NOTICE] callbacks.on_step_start(182): Cycle 2/2, step 3/4: Charge at C/2 until 4.2 V
2023-02-21 09:09:37.032 - [NOTICE] callbacks.on_step_start(182): Cycle 2/2, step 4/4: Rest for 1 hour
2023-02-21 09:09:37.783 - [NOTICE] callbacks.on_experiment_end(222): Finish experiment simulation, took 8.643 s
2023-02-21 09:09:37.784 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Discharge at C/2 until 3.0 V', using temperature from parameter values.
2023-02-21 09:09:37.785 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Rest for 1 hour', using temperature from parameter values.
2023-02-21 09:09:37.785 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Charge at C/2 until 4.2 V', using temperature from parameter values.
2023-02-21 09:09:37.786 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Rest for 1 hour', using temperature from parameter values.
2023-02-21 09:09:37.788 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Discharge at C/2 until 3.0 V', using temperature from parameter values.
2023-02-21 09:09:37.789 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Rest for 1 hour', using temperature from parameter values.
2023-02-21 09:09:37.790 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Charge at C/2 until 4.2 V', using temperature from parameter values.
2023-02-21 09:09:37.791 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Rest for 1 hour', using temperature from parameter values.
2023-02-21 09:09:37.792 - [INFO] callbacks.on_experiment_start(166): Start running experiment
2023-02-21 09:09:37.794 - [INFO] parameter_values.process_model(425): Start setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:09:37.912 - [INFO] parameter_values.process_model(527): Finish setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:09:37.914 - [INFO] parameter_values.process_model(425): Start setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:09:38.035 - [INFO] parameter_values.process_model(527): Finish setting parameters for Doyle-Fuller-Newman model
0.04
2023-02-21 09:09:38.037 - [INFO] parameter_values.process_model(425): Start setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:09:38.341 - [INFO] parameter_values.process_model(527): Finish setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:09:38.344 - [INFO] discretisation.process_model(147): Start discretising Doyle-Fuller-Newman model
2023-02-21 09:09:38.349 - [INFO] discretisation.remove_independent_variables_from_rhs(1120): removing variable Discharge capacity [A.h] from rhs
2023-02-21 09:09:38.914 - [INFO] discretisation.process_model(259): Finish discretising Doyle-Fuller-Newman model
2023-02-21 09:09:38.915 - [INFO] discretisation.process_model(147): Start discretising Doyle-Fuller-Newman model
2023-02-21 09:09:38.922 - [INFO] discretisation.remove_independent_variables_from_rhs(1120): removing variable Discharge capacity [A.h] from rhs
2023-02-21 09:09:39.441 - [INFO] discretisation.process_model(259): Finish discretising Doyle-Fuller-Newman model
2023-02-21 09:09:39.442 - [INFO] discretisation.process_model(147): Start discretising Doyle-Fuller-Newman model
2023-02-21 09:09:39.448 - [INFO] discretisation.remove_independent_variables_from_rhs(1120): removing variable Discharge capacity [A.h] from rhs
2023-02-21 09:09:39.986 - [INFO] discretisation.process_model(259): Finish discretising Doyle-Fuller-Newman model
2023-02-21 09:09:39.987 - [NOTICE] callbacks.on_cycle_start(174): Cycle 1/2 (16.366 us elapsed) --------------------
2023-02-21 09:09:39.988 - [NOTICE] callbacks.on_step_start(182): Cycle 1/2, step 1/4: Discharge at C/2 until 3.0 V
2023-02-21 09:09:39.993 - [INFO] base_solver.set_up(111): Start solver set-up
2023-02-21 09:09:40.141 - [INFO] base_solver.set_up(236): Finish solver set-up
2023-02-21 09:09:41.853 - [NOTICE] callbacks.on_step_start(182): Cycle 1/2, step 2/4: Rest for 1 hour
2023-02-21 09:09:41.858 - [INFO] base_solver.set_up(111): Start solver set-up
2023-02-21 09:09:41.990 - [INFO] base_solver.set_up(236): Finish solver set-up
2023-02-21 09:09:42.685 - [NOTICE] callbacks.on_step_start(182): Cycle 1/2, step 3/4: Charge at C/2 until 4.2 V
2023-02-21 09:09:42.690 - [INFO] base_solver.set_up(111): Start solver set-up
2023-02-21 09:09:42.834 - [INFO] base_solver.set_up(236): Finish solver set-up
2023-02-21 09:09:44.378 - [NOTICE] callbacks.on_step_start(182): Cycle 1/2, step 4/4: Rest for 1 hour
2023-02-21 09:09:45.649 - [NOTICE] callbacks.on_cycle_start(174): Cycle 2/2 (5.662 s elapsed) --------------------
2023-02-21 09:09:45.650 - [NOTICE] callbacks.on_step_start(182): Cycle 2/2, step 1/4: Discharge at C/2 until 3.0 V
2023-02-21 09:09:47.096 - [NOTICE] callbacks.on_step_start(182): Cycle 2/2, step 2/4: Rest for 1 hour
2023-02-21 09:09:47.757 - [NOTICE] callbacks.on_step_start(182): Cycle 2/2, step 3/4: Charge at C/2 until 4.2 V
2023-02-21 09:09:49.184 - [NOTICE] callbacks.on_step_start(182): Cycle 2/2, step 4/4: Rest for 1 hour
2023-02-21 09:09:49.955 - [NOTICE] callbacks.on_experiment_end(222): Finish experiment simulation, took 9.968 s
2023-02-21 09:09:49.956 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Discharge at C/2 until 3.0 V', using temperature from parameter values.
2023-02-21 09:09:49.957 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Rest for 1 hour', using temperature from parameter values.
2023-02-21 09:09:49.958 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Charge at C/2 until 4.2 V', using temperature from parameter values.
2023-02-21 09:09:49.959 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Rest for 1 hour', using temperature from parameter values.
2023-02-21 09:09:49.960 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Discharge at C/2 until 3.0 V', using temperature from parameter values.
2023-02-21 09:09:49.961 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Rest for 1 hour', using temperature from parameter values.
2023-02-21 09:09:49.962 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Charge at C/2 until 4.2 V', using temperature from parameter values.
2023-02-21 09:09:49.963 - [WARNING] experiment._read_and_drop_temperature(431): Temperature not found on step: 'Rest for 1 hour', using temperature from parameter values.
2023-02-21 09:09:49.964 - [INFO] callbacks.on_experiment_start(166): Start running experiment
2023-02-21 09:09:49.966 - [INFO] parameter_values.process_model(425): Start setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:09:50.085 - [INFO] parameter_values.process_model(527): Finish setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:09:50.088 - [INFO] parameter_values.process_model(425): Start setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:09:50.206 - [INFO] parameter_values.process_model(527): Finish setting parameters for Doyle-Fuller-Newman model
0.1
2023-02-21 09:09:50.209 - [INFO] parameter_values.process_model(425): Start setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:09:50.322 - [INFO] parameter_values.process_model(527): Finish setting parameters for Doyle-Fuller-Newman model
2023-02-21 09:09:50.326 - [INFO] discretisation.process_model(147): Start discretising Doyle-Fuller-Newman model
2023-02-21 09:09:50.332 - [INFO] discretisation.remove_independent_variables_from_rhs(1120): removing variable Discharge capacity [A.h] from rhs
2023-02-21 09:09:50.868 - [INFO] discretisation.process_model(259): Finish discretising Doyle-Fuller-Newman model
2023-02-21 09:09:50.869 - [INFO] discretisation.process_model(147): Start discretising Doyle-Fuller-Newman model
2023-02-21 09:09:50.877 - [INFO] discretisation.remove_independent_variables_from_rhs(1120): removing variable Discharge capacity [A.h] from rhs
2023-02-21 09:09:51.557 - [INFO] discretisation.process_model(259): Finish discretising Doyle-Fuller-Newman model
2023-02-21 09:09:51.557 - [INFO] discretisation.process_model(147): Start discretising Doyle-Fuller-Newman model
2023-02-21 09:09:51.563 - [INFO] discretisation.remove_independent_variables_from_rhs(1120): removing variable Discharge capacity [A.h] from rhs
2023-02-21 09:09:52.101 - [INFO] discretisation.process_model(259): Finish discretising Doyle-Fuller-Newman model
2023-02-21 09:09:52.101 - [NOTICE] callbacks.on_cycle_start(174): Cycle 1/2 (16.504 us elapsed) --------------------
2023-02-21 09:09:52.102 - [NOTICE] callbacks.on_step_start(182): Cycle 1/2, step 1/4: Discharge at C/2 until 3.0 V
2023-02-21 09:09:52.108 - [INFO] base_solver.set_up(111): Start solver set-up
2023-02-21 09:09:52.250 - [INFO] base_solver.set_up(236): Finish solver set-up
2023-02-21 09:09:54.101 - [NOTICE] callbacks.on_step_start(182): Cycle 1/2, step 2/4: Rest for 1 hour
2023-02-21 09:09:54.107 - [INFO] base_solver.set_up(111): Start solver set-up
2023-02-21 09:09:54.236 - [INFO] base_solver.set_up(236): Finish solver set-up
2023-02-21 09:09:54.910 - [NOTICE] callbacks.on_step_start(182): Cycle 1/2, step 3/4: Charge at C/2 until 4.2 V
2023-02-21 09:09:54.914 - [INFO] base_solver.set_up(111): Start solver set-up
2023-02-21 09:09:55.056 - [INFO] base_solver.set_up(236): Finish solver set-up
2023-02-21 09:09:56.719 - [NOTICE] callbacks.on_step_start(182): Cycle 1/2, step 4/4: Rest for 1 hour
2023-02-21 09:09:57.598 - [NOTICE] callbacks.on_cycle_start(174): Cycle 2/2 (5.496 s elapsed) --------------------
2023-02-21 09:09:57.598 - [NOTICE] callbacks.on_step_start(182): Cycle 2/2, step 1/4: Discharge at C/2 until 3.0 V
2023-02-21 09:09:59.232 - [NOTICE] callbacks.on_step_start(182): Cycle 2/2, step 2/4: Rest for 1 hour
2023-02-21 09:09:59.886 - [NOTICE] callbacks.on_step_start(182): Cycle 2/2, step 3/4: Charge at C/2 until 4.2 V
2023-02-21 09:10:01.516 - [NOTICE] callbacks.on_step_start(182): Cycle 2/2, step 4/4: Rest for 1 hour
2023-02-21 09:10:02.299 - [NOTICE] callbacks.on_experiment_end(222): Finish experiment simulation, took 10.198 s
[1] Weilong Ai, Niall Kirkaldy, Yang Jiang, Gregory Offer, Huizhi Wang, and Billy Wu. A composite electrode model for lithium-ion batteries with silicon/graphite negative electrodes. Journal of Power Sources, 527:231142, 2022. URL: https://www.sciencedirect.com/science/article/pii/S0378775322001604, doi:https://doi.org/10.1016/j.jpowsour.2022.231142.
[2] Weilong Ai, Ludwig Kraft, Johannes Sturm, Andreas Jossen, and Billy Wu. Electrochemical thermal-mechanical modelling of stress inhomogeneity in lithium-ion pouch cells. Journal of The Electrochemical Society, 167(1):013512, 2019. doi:10.1149/2.0122001JES.
[3] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[4] Chang-Hui Chen, Ferran Brosa Planella, Kieran O'Regan, Dominika Gastol, W. Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.
[5] Rutooj Deshpande, Mark Verbrugge, Yang-Tse Cheng, John Wang, and Ping Liu. Battery cycle life prediction with coupled chemical degradation and fatigue mechanics. Journal of the Electrochemical Society, 159(10):A1730, 2012. doi:10.1149/2.049210jes.
[6] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[7] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[8] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
Modelling coupled degradation mechanisms in PyBaMM#
This notebook shows how to set up a PyBaMM model in which many degradation mechanisms run at the same time and interact with one another.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportmatplotlib.pyplotasplt
[notice] A new release of pip available: 22.3.1 -> 23.1.2[notice] To update, run: pip install --upgrade pip
Note: you may need to restart the kernel to use updated packages.
O’Kane et al. [10] modelled four coupled degradation mechanisms: SEI growth, lithium plating, particle cracking and stress-driven loss of active material. The “SEI on cracks” option couples SEI growth and particle cracking by allowing SEI to grow on the cracks. The “partially reversible” option for lithium plating allows the SEI to influence the irreversible component of plating using a function in the OKane2022 parameter file. Particle cracking and stress-driven loss of active material are
coupled by default because the stress-strain relations inside the particles are an input for both.
[2]:
model=pybamm.lithium_ion.DFN({"SEI":"solvent-diffusion limited","SEI porosity change":"true","lithium plating":"partially reversible","lithium plating porosity change":"true",# alias for "SEI porosity change""particle mechanics":("swelling and cracking","swelling only"),"SEI on cracks":"true","loss of active material":"stress-driven","calculate discharge energy":"true",# for compatibility with older PyBaMM versions})
Depending on the parameter set being used, the particle cracking model can require a large number of mesh points inside the particles to be numerically stable.
Define a cycling protocol and solve. The protocol from O’Kane et al. [10] is used here, except with 10 ageing cycles instead of 1000.
[4]:
cycle_number=10exp=pybamm.Experiment(["Hold at 4.2 V until C/100 (5 minute period)","Rest for 4 hours (5 minute period)","Discharge at 0.1C until 2.5 V (5 minute period)",# initial capacity check"Charge at 0.3C until 4.2 V (5 minute period)","Hold at 4.2 V until C/100 (5 minute period)",]+[("Discharge at 1C until 2.5 V",# ageing cycles"Charge at 0.3C until 4.2 V (5 minute period)","Hold at 4.2 V until C/100 (5 minute period)",)]*cycle_number+["Discharge at 0.1C until 2.5 V (5 minute period)"],# final capacity check)sim=pybamm.Simulation(model,parameter_values=param,experiment=exp,var_pts=var_pts)sol=sim.solve()
Three of the degradation mechanisms - SEI, lithium plating and SEI on cracks - cause loss of lithium inventory (LLI). Plotting the different contributions to LLI against throughput capacity as opposed to cycle number allows them to be considered as continuous variables as opposed to discrete ones.
[5]:
Qt=sol["Throughput capacity [A.h]"].entriesQ_SEI=sol["Loss of capacity to negative SEI [A.h]"].entriesQ_SEI_cr=sol["Loss of capacity to negative SEI on cracks [A.h]"].entriesQ_plating=sol["Loss of capacity to negative lithium plating [A.h]"].entriesQ_side=sol["Total capacity lost to side reactions [A.h]"].entriesQ_LLI=(sol["Total lithium lost [mol]"].entries*96485.3/3600)# convert from mol to A.hplt.figure()plt.plot(Qt,Q_SEI,label="SEI",linestyle="dashed")plt.plot(Qt,Q_SEI_cr,label="SEI on cracks",linestyle="dashdot")plt.plot(Qt,Q_plating,label="Li plating",linestyle="dotted")plt.plot(Qt,Q_side,label="All side reactions",linestyle=(0,(6,1)))plt.plot(Qt,Q_LLI,label="All LLI")plt.xlabel("Throughput capacity [A.h]")plt.ylabel("Capacity loss [A.h]")plt.legend()plt.show()
The capacity loss over 10 cycles is so small that the reversible component of the lithium plating is has a larger effect than all the irreversible mechanisms combined. Most of the irreversible capacity fade that does occur is caused by SEI on cracks.
The stress-driven loss of active material (LAM) mechanism [10,11] is also included, so the three main degradation modes - LLI and LAM in each electrode - can be plotted and compared.
[6]:
Qt=sol["Throughput capacity [A.h]"].entriesLLI=sol["Loss of lithium inventory [%]"].entriesLAM_neg=sol["Loss of active material in negative electrode [%]"].entriesLAM_pos=sol["Loss of active material in positive electrode [%]"].entriesplt.figure()plt.plot(Qt,LLI,label="LLI")plt.plot(Qt,LAM_neg,label="LAM (negative)")plt.plot(Qt,LAM_pos,label="LAM (positive)")plt.xlabel("Throughput capacity [A.h]")plt.ylabel("Degradation modes [%]")plt.legend()plt.show()
Both the reversible and irreversible components of LLI are far greater than LAM for this parameter set.
A key internal variable is the porosity. Pore clogging by SEI, lithium plating and other means can trigger other degradation mechanisms and reduce the rate capability of the cell. If the porosity reaches zero, the cell becomes completely unusable and PyBaMM will terminate the simulation.
If you want to see some serious degradation, try re-running the simulation with more ageing cycles, or using param.update({}) to increase the degradation parameters beyond the ranges considered by O’Kane et al. [10]
[8]:
pybamm.print_citations()
[1] Weilong Ai, Ludwig Kraft, Johannes Sturm, Andreas Jossen, and Billy Wu. Electrochemical thermal-mechanical modelling of stress inhomogeneity in lithium-ion pouch cells. Journal of The Electrochemical Society, 167(1):013512, 2019. doi:10.1149/2.0122001JES.
[2] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[3] Chang-Hui Chen, Ferran Brosa Planella, Kieran O'Regan, Dominika Gastol, W. Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.
[4] Rutooj Deshpande, Mark Verbrugge, Yang-Tse Cheng, John Wang, and Ping Liu. Battery cycle life prediction with coupled chemical degradation and fatigue mechanics. Journal of the Electrochemical Society, 159(10):A1730, 2012. doi:10.1149/2.049210jes.
[5] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[6] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[7] Scott G. Marquis. Long-term degradation of lithium-ion batteries. PhD thesis, University of Oxford, 2020.
[8] Peyman Mohtat, Suhak Lee, Jason B Siegel, and Anna G Stefanopoulou. Towards better estimability of electrode-specific state of health: decoding the cell expansion. Journal of Power Sources, 427:101–111, 2019.
[9] Simon E. J. O'Kane, Ian D. Campbell, Mohamed W. J. Marzook, Gregory J. Offer, and Monica Marinescu. Physical origin of the differential voltage minimum associated with lithium plating in li-ion batteries. Journal of The Electrochemical Society, 167(9):090540, may 2020. URL: https://doi.org/10.1149/1945-7111/ab90ac, doi:10.1149/1945-7111/ab90ac.
[10] Simon E. J. O'Kane, Weilong Ai, Ganesh Madabattula, Diego Alonso-Alvarez, Robert Timms, Valentin Sulzer, Jacqueline Sophie Edge, Billy Wu, Gregory J. Offer, and Monica Marinescu. Lithium-ion battery degradation: how to model it. Phys. Chem. Chem. Phys., 24:7909-7922, 2022. URL: http://dx.doi.org/10.1039/D2CP00417H, doi:10.1039/D2CP00417H.
[11] Jorn M. Reniers, Grietus Mulder, and David A. Howey. Review and performance comparison of mechanical-chemical degradation models for lithium-ion batteries. Journal of The Electrochemical Society, 166(14):A3189, 2019. doi:10.1149/2.0281914jes.
[12] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[13] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
[14] Andrew Weng, Jason B Siegel, and Anna Stefanopoulou. Differential voltage analysis for battery manufacturing process control. arXiv preprint arXiv:2303.07088, 2023.
Tip
An interactive online version of this notebook is available, which can be
accessed via
Doyle-Fuller-Newman Model with particle-size distributions#
This notebook demonstrates the extension of the Doyle-Fuller-Newman (DFN) model to include a distribution of particle sizes at every macroscale location (e.g. through-cell coordinate \(x\)) within the electrodes. This model, referred to here as the Many-Particle DFN (or MP-DFN), also extends the ideas of the Many-Particle Model or MPM (an \(x\)-averaged model) into the DFN framework. Note: this differs from a “size distribution in x”, where the particle size is a function of \(x\)
but with only a single size at any given value of \(x\).
The MPM notebook describes how the particle-size distributions are implemented in PyBaMM, and how to input parameters and plot some relevant output variables. The model equations for the MP-DFN, which allow for variations in the through-cell \(x\) direction and therefore include electrolyte dynamics, are similar to those of the MPM and DFN, and are available in [5].
By default per electrode, the DFN has 1 microscale dimension (the radial coordinate within the active particles, \(r_{\mathrm{k}}\), \(\mathrm{k}=\mathrm{n,p}\)) and 1 macroscale dimension (the through-cell coordinate \(x\)), and is commonly called “pseudo-2D”. The MP-DFN adds another microscale variation with particle size \(R_{\mathrm{k}}\), \(\mathrm{k}=\mathrm{n,p}\) over an interval \([R_{\mathrm{k,min}}, R_{\mathrm{k,max}}]\), \(\mathrm{k}=\mathrm{n,p}\), and
thus can be thought of as “pseudo-3D”. See [5] for more details.
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportmatplotlib.pyplotasplt
Note: you may need to restart the kernel to use updated packages.
The MP-DFN can be accessed from the DFN model in PyBaMM, and specifying the "particlesize" option. The default option for "particlesize" is "single". Let’s change this to "distribution" and pass to the DFN when loading the model.
We now need to input and set the particle-size distributions and the minimum and maximum sizes for each electrode. Values for these are not currently in the default parameter set for the DFN, but they are easily added (to any parameter set of choice) - see the MPM notebook for more details.
Here, we start from the Marquis et al. (2019) [6] parameter set and use the convenience method pybamm.get_size_distribution_parameters(), which adds lognormals for the area-weighted size distribution in each electrode. By default, it chooses the "Negativeparticleradius[m]" and "Positiveparticleradius[m]" values already in the parameter set to be the mean of the lognormals, and sets the standard deviations to be 0.3 times the mean. (All parameters can be
overwritten with keyword arguments.)
[3]:
# Base parameter set (no distribution parameters by default)params=pybamm.ParameterValues("Marquis2019")# Add distribution parameters to the set, with default values (lognormals)params=pybamm.get_size_distribution_parameters(params)
# load parameter values into simuluationsolver=pybamm.CasadiSolver(mode="fast")sim=pybamm.Simulation(model,parameter_values=params,solver=solver)# solvesim.solve(t_eval=[0,3500])
[4]:
<pybamm.solvers.solution.Solution at 0x7fd4e17c43a0>
[5]:
# plot some variables that depend on particle sizeoutput_variables=["Negative particle surface concentration distribution [mol.m-3]","Positive particle surface concentration distribution [mol.m-3]","X-averaged negative particle surface concentration distribution [mol.m-3]","Negative area-weighted particle-size distribution [m-1]","Positive area-weighted particle-size distribution [m-1]","Voltage [V]",]sim.plot(output_variables=output_variables)
[5]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7fd4e17c45b0>
Variables that depend on particle size have names that end in "distribution", e.g. "Negativeparticlesurfaceconcentrationdistribution". The same variables without the "distribution" have been averaged over particle size, and can be compared to the corresponding variable with the same name from other models that have a single particle size (e.g. SPM).
The particle size distributions do not vary with \(x\), so you can use the “X-averaged” versions to better visualize the discretized distribution in \(R\) that PyBaMM uses.
[6]:
# The discrete sizes or "bins" usedR_p=sim.solution["Positive particle sizes [m]"].entries[:,0,0]# const in the x and current collector directionR_n=sim.solution["Negative particle sizes [m]"].entries[:,0,0]# The distributions (number, area, and volume-weighted)f_a_p=sim.solution["X-averaged positive area-weighted particle-size distribution [m-1]"].entries[:,0]f_num_p=sim.solution["X-averaged positive number-based particle-size distribution [m-1]"].entries[:,0]f_v_p=sim.solution["X-averaged positive volume-weighted particle-size distribution [m-1]"].entries[:,0]f_a_n=sim.solution["X-averaged negative area-weighted particle-size distribution [m-1]"].entries[:,0]f_num_n=sim.solution["X-averaged negative number-based particle-size distribution [m-1]"].entries[:,0]f_v_n=sim.solution["X-averaged negative volume-weighted particle-size distribution [m-1]"].entries[:,0]# plotf,axs=plt.subplots(1,2,figsize=(10,4))# negative electrodewidth_n=(R_n[-1]-R_n[-2])/1e-6axs[0].bar(R_n/1e-6,f_a_n*1e-6,width=width_n,alpha=0.3,color="tab:blue",label="area-weighted",)axs[0].bar(R_n/1e-6,f_num_n*1e-6,width=width_n,alpha=0.3,color="tab:red",label="number-weighted",)axs[0].bar(R_n/1e-6,f_v_n*1e-6,width=width_n,alpha=0.3,color="tab:green",label="volume-weighted",)axs[0].set_xlim((0,25))axs[0].set_xlabel("Particle size $R_{\mathrm{n}}$ [$\mu$m]",fontsize=12)axs[0].set_ylabel("[$\mu$m$^{-1}$]",fontsize=12)axs[0].legend(fontsize=10)axs[0].set_title("Discretized distributions (histograms) in negative electrode")# positive electrodewidth_p=(R_p[-1]-R_p[-2])/1e-6axs[1].bar(R_p/1e-6,f_a_p*1e-6,width=width_p,alpha=0.3,color="tab:blue",label="area-weighted",)axs[1].bar(R_p/1e-6,f_num_p*1e-6,width=width_p,alpha=0.3,color="tab:red",label="number-weighted",)axs[1].bar(R_p/1e-6,f_v_p*1e-6,width=width_p,alpha=0.3,color="tab:green",label="volume-weighted",)axs[1].set_xlim((0,25))axs[1].set_xlabel("Particle size $R_{\mathrm{p}}$ [$\mu$m]",fontsize=12)axs[1].set_ylabel("[$\mu$m$^{-1}$]",fontsize=12)axs[1].set_title("Positive electrode")plt.tight_layout()plt.show()
Here we show how to change the distribution parameters, or set any distribution of choice. We will set the distribution parameters for the positive distribution (leaving the one for the negative electrode as the default lognormal). We refer to the MPM notebook for more examples.
[7]:
# Set the area-weighted mean radius to be the reference value from the parameter setR_av_p_dim=params["Positive particle radius [m]"]# Standard deviation (dimensional)sd_p_dim=0.6*R_av_p_dim# Minimum and maximum particle sizes (dimensional)R_min_p=0R_max_p=3*R_av_p_dim# Set the area-weighted particle-size distribution.# Choose a lognormal (but any pybamm function could be used)deff_a_dist_p_dim(R):returnpybamm.lognormal(R,R_av_p_dim,sd_p_dim)# Note: the only argument must be the particle size R
[8]:
# input params to the dictionarydistribution_params={"Positive minimum particle radius [m]":R_min_p,"Positive maximum particle radius [m]":R_max_p,"Positive area-weighted "+"particle-size distribution [m-1]":f_a_dist_p_dim,}params.update(distribution_params,check_already_exists=False)
Solve and plot
[9]:
# load parameter values into simulationsim_custom=pybamm.Simulation(model,parameter_values=params,solver=solver)# solvesim_custom.solve(t_eval=[0,3500])# plotoutput_variables=["X-averaged negative area-weighted particle-size distribution [m-1]","X-averaged positive area-weighted particle-size distribution [m-1]","Voltage [V]",]quickplot=pybamm.QuickPlot([sim,sim_custom],output_variables=output_variables,labels=["default lognormals","custom"],)quickplot.plot(0)
The MP-DFN is an extension of the MPM (size distributions, but no x variation) and DFN models (x variation, but no size distribution). Here we compare the three for the same parameter values, for a discharge followed by a relaxation. Note: this is implemented here by specifying the current function to be a Heaviside, not using the Experiment class.
[10]:
models=[pybamm.lithium_ion.DFN(options={"particle size":"distribution"},name="MP-DFN"),pybamm.lithium_ion.MPM(name="MPM"),pybamm.lithium_ion.DFN(name="DFN"),]# parametersparams=pybamm.ParameterValues("Marquis2019")params=pybamm.get_size_distribution_parameters(params)# define current functiont_cutoff=3450# [s]t_rest=3600# [s]I_typ=params["Nominal cell capacity [A.h]"]# current for 1Cdefcurrent(t):returnI_typ*pybamm.EqualHeaviside(t,t_cutoff)params.update({"Current function [A]":current})t_eval=[0,t_cutoff+t_rest]# solvesims=[]formodelinmodels:sim=pybamm.Simulation(model,parameter_values=params,solver=solver)sim.solve(t_eval=t_eval)sims.append(sim)
During discharge, the MPM overpredicts the voltage since it neglects the transport (and therefore resistances) through the electrolyte. During the relaxation portion, the DFN overpredicts the rate of relaxation to equilibrium compared to the MP-DFN and MPM. However, the slower relaxation of the size distribution models has been shown to agree better with experiment [5].
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Toby L. Kirk, Jack Evans, Colin P. Please, and S. Jonathan Chapman. Modelling electrode heterogeneity in lithium-ion batteries: unimodal and bimodal particle-size distributions. arXiv:2006.12208, 2020. URL: https://arxiv.org/abs/2006.12208, arXiv:2006.12208.
[5] Toby L. Kirk, Colin P. Please, and S. Jon Chapman. Physical modelling of the slow voltage relaxation phenomenon in lithium-ion batteries. Journal of The Electrochemical Society, 168(6):060554, jun 2021. URL: https://doi.org/10.1149/1945-7111/ac0bf7, doi:10.1149/1945-7111/ac0bf7.
[6] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[7] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
The DFN comprises equations for charge and mass conservation in the solid the solid and electrolyte, and also prescribes behaviour for the electrochemical reactions occurring on the interface between the solid an electrolyte. For more information please see [2] or other standard texts.
Below we summarise the model, with all parameters give in the table at the end of this notebook. Here we use a roman subscript \(\text{k} \in \text{n, s, p}\) is used to denote the regions negative electrode, separator, and positive electrode, respectively.
Below we show how to solve the DFN model, using the default geometry, mesh, parameters, discretisation and solver provided with PyBaMM. For a more detailed example, see the notebook on the SPM.
In order to show off all the different points at which the process of setting up and solving a model in PyBaMM can be customised we explicitly handle the stages of choosing a geometry, setting parameters, discretising the model and solving the model. However, it is often simpler in practice to use the Simulation class, which handles many of the stages automatically, as shown here.
First we need to import pybamm, along with numpy which we will use in this notebook.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnp
WARNING: You are using pip version 21.0.1; however, version 21.1.2 is available.
You should consider upgrading via the '/home/user/Documents/PyBaMM/env/bin/python3.8 -m pip install --upgrade pip' command.
Note: you may need to restart the kernel to use updated packages.
We then load the DFN model and default geometry, and process them both using the default parameters.
[2]:
# load modelmodel=pybamm.lithium_ion.DFN()# create geometrygeometry=model.default_geometry# load parameter values and process model and geometryparam=model.default_parameter_valuesparam.process_model(model)param.process_geometry(geometry)
The next step is to set the mesh and discretise the model. Again, we choose the default settings.
[3]:
# set meshmesh=pybamm.Mesh(geometry,model.default_submesh_types,model.default_var_pts)# discretise modeldisc=pybamm.Discretisation(mesh,model.default_spatial_methods)disc.process_model(model);
The model is now ready to be solved. We select the default DAE solver for the DFN. Note that in order to successfully solve the system of DAEs we are required to give consistent initial conditions. This is handled automatically by PyBaMM during the solve operation.
[4]:
# solve modelsolver=model.default_solvert_eval=np.linspace(0,3600,300)# time in secondssolution=solver.solve(model,t_eval)
To get a quick overview of the model outputs we can use the QuickPlot class, which plots a common set of useful outputs. The method Quickplot.dynamic_plot makes a slider widget.
[8]:
quick_plot=pybamm.QuickPlot(solution,["Positive electrode interfacial current density [A.m-2]"])quick_plot.dynamic_plot();
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[5] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). ECSarXiv. February, 2020. doi:10.1149/osf.io/67ckj.
Tip
An interactive online version of this notebook is available, which can be
accessed via
This notebook demonstrates some utilities to work with electrode State of Health (also sometimes called electrode stoichiometry), using the algorithm from Mohtat et al [1]
[1] Mohtat, P., Lee, S., Siegel, J. B., & Stefanopoulou, A. G. (2019). Towards better estimability of electrode-specific state of health: Decoding the cell expansion. Journal of Power Sources, 427, 101-111.
[26]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportmatplotlib.pyplotaspltimportnumpyasnp
zsh:1: no matches found: pybamm[plot,cite]
Note: you may need to restart the kernel to use updated packages.
spm=pybamm.lithium_ion.SPM()experiment=pybamm.Experiment(["Charge at 1C until 4.2V","Hold at 4.2V until C/50","Discharge at 1C until 2.8V","Hold at 2.8V until C/50",])parameter_values=pybamm.ParameterValues("Mohtat2020")sim=pybamm.Simulation(spm,experiment=experiment,parameter_values=parameter_values)spm_sol=sim.solve()spm_sol.plot(["Voltage [V]","Current [A]","Negative electrode stoichiometry","Positive electrode stoichiometry",])
[27]:
<pybamm.plotting.quick_plot.QuickPlot at 0x17f99c580>
Given a total amount of cyclable lithium capacity, \(Q_{Li}\), electrode capacities, \(Q_n\) and \(Q_p\), and voltage limits, \(V_{min}\) and \(V_{max}\), we can solve for the min and max electrode SOCs, \(x_0\), \(x_{100}\), \(y_0\), and \(y_{100}\), and the cell capacity, \(C\), using the algorithm adapted from Mohtat et al [1]. First, we find \(x_{100}\) and \(y_{100}\) using
# First we solve for x_100 and y_100model=pybamm.BaseModel()x_100=pybamm.Variable("x_100")y_100=(Q_Li-x_100*Q_n)/Q_py_100_min=1e-10x_100_upper_limit=(Q_Li-y_100_min*Q_p)/Q_nmodel.algebraic={x_100:U_p(y_100,T_ref)-U_n(x_100,T_ref)-Vmax}model.initial_conditions={x_100:x_100_upper_limit}model.variables={"x_100":x_100,"y_100":y_100}sim=pybamm.Simulation(model,parameter_values=parameter_values)sol=sim.solve([0])x_100=sol["x_100"].data[0]y_100=sol["y_100"].data[0]forvarin["x_100","y_100"]:print(var,":",sol[var].data[0])# Based on the calculated values for x_100 and y_100 we solve for x_0model=pybamm.BaseModel()x_0=pybamm.Variable("x_0")Q=Q_n*(x_100-x_0)y_0=y_100+Q/Q_pmodel.algebraic={x_0:U_p(y_0,T_ref)-U_n(x_0,T_ref)-Vmin}model.initial_conditions={x_0:0.1}model.variables={"Q":Q,"x_0":x_0,"y_0":y_0,}sim=pybamm.Simulation(model,parameter_values=parameter_values)sol=sim.solve([0])forvarin["Q","x_0","y_0"]:print(var,":",sol[var].data[0])
How does electrode SOH depend on cyclable lithium#
We can do a parameter sweep for the amount of cyclable lithium to see how it affects the electrode SOH parameters and cell capacity
[32]:
all_parameter_sets=[kfork,vinpybamm.parameter_sets.items()ifv["chemistry"]=="lithium_ion"andknotin["Xu2019","Chen2020_composite","Ecker2015_graphite_halfcell","OKane2022_graphite_SiOx_halfcell",]]defsolve_esoh_sweep_QLi(parameter_set,param):parameter_values=pybamm.ParameterValues(parameter_set)# Vmin = parameter_values["Lower voltage cut-off [V]"]# Vmax = parameter_values["Upper voltage cut-off [V]"]Vmin=parameter_values["Open-circuit voltage at 0% SOC [V]"]Vmax=parameter_values["Open-circuit voltage at 100% SOC [V]"]Q_n=parameter_values.evaluate(param.n.Q_init)Q_p=parameter_values.evaluate(param.p.Q_init)Q=parameter_values.evaluate(param.Q/param.n_electrodes_parallel)esoh_solver=pybamm.lithium_ion.ElectrodeSOHSolver(parameter_values,param,known_value="cell capacity")inputs={"V_max":Vmax,"V_min":Vmin,"Q":Q,"Q_n":Q_n,"Q_p":Q_p}sol_init_Q=esoh_solver.solve(inputs)Q_Li_init=parameter_values.evaluate(param.Q_Li_particles_init)esoh_solver=pybamm.lithium_ion.ElectrodeSOHSolver(parameter_values,param)inputs={"V_max":Vmax,"V_min":Vmin,"Q_Li":Q_Li_init,"Q_n":Q_n,"Q_p":Q_p}sol_init_QLi=esoh_solver.solve(inputs)Q_Li_sweep=np.linspace(1e-6,Q_n+Q_p)sweep={}variables=["Q_Li","x_0","x_100","y_0","y_100","Q"]forvarinvariables:sweep[var]=[]forQ_LiinQ_Li_sweep:inputs["Q_Li"]=Q_Litry:sol=esoh_solver.solve(inputs)forvarinvariables:sweep[var].append(sol[var])except(ValueError,pybamm.SolverError):passreturnsweep,sol_init_QLi,sol_init_Qforparameter_setin["Chen2020"]:sweep,sol_init_QLi,sol_init_Q=solve_esoh_sweep_QLi(parameter_set,param)
[33]:
defplot_sweep(sweep,sol_init,sol_init_Q,parameter_set):fig,axes=plt.subplots(1,3,figsize=(10,3))parameter_values=pybamm.ParameterValues(parameter_set)parameter_values.evaluate(param.n.Q_init)parameter_values.evaluate(param.p.Q_init)# Plot min/max stoichimetric limits, including the value with the given Q_Lifori,ksinenumerate([["x_0","x_100"],["y_0","y_100"],["Q"]]):ax=axes.flat[i]forj,kinenumerate(ks):ifi==0andj==0:label1="Stoichiometric envelope"label2="Calculation from cyclable lithium"label3="Calculation from cell capacity"else:label1=label2=label3=Noneax.plot(sweep["Q_Li"],sweep[k],"b-",label=label1)ax.axhline(sol_init_QLi[k],c="k",linestyle="--",label=label2)ax.axhline(sol_init_Q[k],c="r",linestyle="--",label=label3)ax.set_xlabel("Cyclable lithium [A.h]")ax.set_ylabel(ks[0][0])ax.set_xlim([np.min(sweep["Q_Li"]),np.max(sweep["Q_Li"])])ax.axvline(sol_init_QLi["Q_Li"],c="k",linestyle="--")ax.axvline(sol_init_Q["Q_Li"],c="r",linestyle="--")# Plot capacities of electrodes# ax.axvline(Qn,c="b",linestyle="--")# ax.axvline(Qp,c="r",linestyle="--")axes[-1].set_ylabel("Cell capacity [A.h]")# Plot initial values of stoichometriesparameter_values.evaluate(param.n.prim.sto_init_av)parameter_values.evaluate(param.p.prim.sto_init_av)# axes[0].axhline(sto_n_init,c="g",linestyle="--")# axes[1].axhline(sto_p_init,c="g",linestyle="--")axes[1].set_title(parameter_set)fig.legend(loc="center left",bbox_to_anchor=(1.01,0.5))fig.tight_layout()returnfig,axesplot_sweep(sweep,sol_init_QLi,sol_init_Q,"Chen2020");
[34]:
# Skip the MSMR example parameter set since we need to set up the ESOH solver differentlyall_parameter_sets.remove("MSMR_Example")# Loop over all parameter sets and solve the ESOH problemforparameter_setinall_parameter_sets:print(parameter_set)try:sweep,sol_init_QLi,sol_init_Q=solve_esoh_sweep_QLi(parameter_set,param)fig,axes=plot_sweep(sweep,sol_init_QLi,sol_init_Q,parameter_set)exceptValueError:pass
[1] Weilong Ai, Ludwig Kraft, Johannes Sturm, Andreas Jossen, and Billy Wu. Electrochemical thermal-mechanical modelling of stress inhomogeneity in lithium-ion pouch cells. Journal of The Electrochemical Society, 167(1):013512, 2019. doi:10.1149/2.0122001JES.
[2] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[3] Daniel R Baker and Mark W Verbrugge. Multi-species, multi-reaction model for porous intercalation electrodes: part i. model formulation and a perturbation solution for low-scan-rate, linear-sweep voltammetry of a spinel lithium manganese oxide electrode. Journal of The Electrochemical Society, 165(16):A3952, 2018.
[4] Chang-Hui Chen, Ferran Brosa Planella, Kieran O'Regan, Dominika Gastol, W. Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.
[5] Madeleine Ecker, Stefan Käbitz, Izaro Laresgoiti, and Dirk Uwe Sauer. Parameterization of a Physico-Chemical Model of a Lithium-Ion Battery: II. Model Validation. Journal of The Electrochemical Society, 162(9):A1849–A1857, 2015. doi:10.1149/2.0541509jes.
[6] Madeleine Ecker, Thi Kim Dung Tran, Philipp Dechent, Stefan Käbitz, Alexander Warnecke, and Dirk Uwe Sauer. Parameterization of a Physico-Chemical Model of a Lithium-Ion Battery: I. Determination of Parameters. Journal of the Electrochemical Society, 162(9):A1836–A1848, 2015. doi:10.1149/2.0551509jes.
[7] Alastair Hales, Laura Bravo Diaz, Mohamed Waseem Marzook, Yan Zhao, Yatish Patel, and Gregory Offer. The cell cooling coefficient: a standard to define heat rejection from lithium-ion batteries. Journal of The Electrochemical Society, 166(12):A2383, 2019.
[8] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[9] Gi-Heon Kim, Kandler Smith, Kyu-Jin Lee, Shriram Santhanagopalan, and Ahmad Pesaran. Multi-domain modeling of lithium-ion batteries encompassing multi-physics in varied length scales. Journal of the Electrochemical Society, 158(8):A955–A969, 2011. doi:10.1149/1.3597614.
[10] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[11] Peyman Mohtat, Suhak Lee, Jason B Siegel, and Anna G Stefanopoulou. Towards better estimability of electrode-specific state of health: decoding the cell expansion. Journal of Power Sources, 427:101–111, 2019.
[12] Peyman Mohtat, Suhak Lee, Valentin Sulzer, Jason B. Siegel, and Anna G. Stefanopoulou. Differential Expansion and Voltage Model for Li-ion Batteries at Practical Charging Rates. Journal of The Electrochemical Society, 167(11):110561, 2020. doi:10.1149/1945-7111/aba5d1.
[13] Simon E. J. O'Kane, Ian D. Campbell, Mohamed W. J. Marzook, Gregory J. Offer, and Monica Marinescu. Physical origin of the differential voltage minimum associated with lithium plating in li-ion batteries. Journal of The Electrochemical Society, 167(9):090540, may 2020. URL: https://doi.org/10.1149/1945-7111/ab90ac, doi:10.1149/1945-7111/ab90ac.
[14] Kieran O'Regan, Ferran Brosa Planella, W. Dhammika Widanage, and Emma Kendrick. Thermal-electrochemical parameters of a high energy lithium-ion cylindrical battery. Electrochimica Acta, 425:140700, 2022. doi:10.1016/j.electacta.2022.140700.
[15] Simon E. J. O'Kane, Weilong Ai, Ganesh Madabattula, Diego Alonso-Alvarez, Robert Timms, Valentin Sulzer, Jacqueline Sophie Edge, Billy Wu, Gregory J. Offer, and Monica Marinescu. Lithium-ion battery degradation: how to model it. Phys. Chem. Chem. Phys., 24:7909-7922, 2022. URL: http://dx.doi.org/10.1039/D2CP00417H, doi:10.1039/D2CP00417H.
[16] Eric Prada, D. Di Domenico, Y. Creff, J. Bernard, Valérie Sauvant-Moynot, and François Huet. A simplified electrochemical and thermal aging model of LiFePO4-graphite Li-ion batteries: power and capacity fade simulations. Journal of The Electrochemical Society, 160(4):A616, 2013. doi:10.1149/2.053304jes.
[17] P Ramadass, Bala Haran, Parthasarathy M Gomadam, Ralph White, and Branko N Popov. Development of first principles capacity fade model for li-ion cells. Journal of the Electrochemical Society, 151(2):A196, 2004. doi:10.1149/1.1634273.
[18] Giles Richardson, Ivan Korotkin, Rahifa Ranom, Michael Castle, and Jamie M. Foster. Generalised single particle models for high-rate operation of graded lithium-ion electrodes: systematic derivation and validation. Electrochimica Acta, 339:135862, 2020. doi:10.1016/j.electacta.2020.135862.
[19] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[20] Mark Verbrugge, Daniel Baker, Brian Koch, Xingcheng Xiao, and Wentian Gu. Thermodynamic model for substitutional materials: application to lithiated graphite, spinel manganese oxide, iron phosphate, and layered nickel-manganese-cobalt oxide. Journal of The Electrochemical Society, 164(11):E3243, 2017.
[21] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
[22] Andrew Weng, Jason B Siegel, and Anna Stefanopoulou. Differential voltage analysis for battery manufacturing process control. arXiv preprint arXiv:2303.07088, 2023.
[23] Yan Zhao, Yatish Patel, Teng Zhang, and Gregory J Offer. Modeling the effects of thermal gradients induced by tab and surface cooling on lithium ion cell performance. Journal of The Electrochemical Society, 165(13):A3169, 2018.
Tip
An interactive online version of this notebook is available, which can be
accessed via
PyBaMM supports both negative and positive half-cells. In both cases, the working electrode is considered to be the positive electrode and lithium metal the negative electrode. The difference is solely down to the material the positive electrode is made of. This notebook demonstrates how to simulate half-cells made of a range of materials.
[1]:
%pip install pybamm -q # install PyBaMM if it is not installed
importpybammimportmatplotlib.pyplotasplt
Note: you may need to restart the kernel to use updated packages.
To simulate a half-cell, pass {"workingelectrode":"positive"} to the options dictionary. This deletes the negative electrode and replaces it with lithium metal, which has a fixed open-circuit voltage of zero. First, we load the NMC-based positive half-cell studied by Xu et al. [12]
exp_slow=pybamm.Experiment(["Discharge at C/25 until 3.5 V","Charge at C/25 until 4.2 V"])sim1=pybamm.Simulation(model,parameter_values=param_nmc,experiment=exp_slow)sol1=sim1.solve()t=sol1["Time [s]"].entriesV=sol1["Voltage [V]"].entriesplt.figure()plt.plot(t,V)plt.xlabel("Time [s]")plt.ylabel("Voltage [V]")plt.show()
The charge and discharge curves are the same, as expected. This is not the case for faster cycles:
[4]:
exp_fast=pybamm.Experiment(["Discharge at 1C until 3.5 V","Charge at 1C until 4.2 V"])sim2=pybamm.Simulation(model,parameter_values=param_nmc,experiment=exp_fast)sol2=sim2.solve()t=sol2["Time [s]"].entriesV=sol2["Voltage [V]"].entriesplt.figure()plt.plot(t,V)plt.xlabel("Time [s]")plt.ylabel("Voltage [V]")plt.show()
At t = 285.669 and h = 7.17426e-14, the corrector convergence failed repeatedly or with |h| = hmin.
Next, load a negative half-cell with a graphite-silicon composite as the positive electrode. This is the negative half of the full cell studied by O’Kane et al. [9] and therefore supports all the degradation mechanisms included in that paper. Just like in the coupled degradation notebook, use the options dictionary to switch the mechanisms on and off. Unlike for a full cell, the SEI option
applies to the lithium metal electrode as well as the positive electrode. To set different options for each, use a 2-tuple. The SEIoncracks and lithiumplating options do not work on the lithium metal electrode.
[5]:
model_with_degradation=pybamm.lithium_ion.DFN({"working electrode":"positive","SEI":"reaction limited",# SEI on both electrodes"SEI porosity change":"true","particle mechanics":"swelling and cracking","SEI on cracks":"true","lithium plating":"partially reversible","lithium plating porosity change":"true",# alias for "SEI porosity change"})param_GrSi=pybamm.ParameterValues("OKane2022_graphite_SiOx_halfcell")param_GrSi.update({"SEI reaction exchange current density [A.m-2]":1.5e-07})var_pts={"x_n":1,"x_s":5,"x_p":7,"r_n":1,"r_p":30}exp_degradation=pybamm.Experiment(["Charge at 0.3C until 1.5 V","Discharge at 0.3C until 0.005 V"])sim3=pybamm.Simulation(model_with_degradation,parameter_values=param_GrSi,experiment=exp_degradation,var_pts=var_pts,)sol3=sim3.solve()t=sol3["Time [s]"].entriesV=sol3["Voltage [V]"].entriesplt.figure()plt.plot(t,V)plt.xlabel("Time [s]")plt.ylabel("Voltage [V]")plt.show()
In order to get SEI growth to work on both electrodes, we had to add domains to the names of the degradation variables. Bear this in mind when writing your code.
[6]:
Q_SEI_n=sol3["Loss of capacity to negative SEI [A.h]"].entriesQ_SEI_p=sol3["Loss of capacity to positive SEI [A.h]"].entriesQ_SEI_cr=sol3["Loss of capacity to positive SEI on cracks [A.h]"].entriesQ_pl=sol3["Loss of capacity to positive lithium plating [A.h]"].entriesplt.figure()plt.plot(t,Q_SEI_n,label="Negative SEI")plt.plot(t,Q_SEI_p,label="Positive SEI")plt.plot(t,Q_SEI_cr,label="SEI on cracks")plt.plot(t,Q_pl,label="Lithium plating")plt.xlabel("Time [s]")plt.ylabel("Loss of lithium inventory [A.h]")plt.legend()plt.show()
The SEI growth is slow compared to the reversible component of the lithium plating. What happens if the SEI growth rate is increased?
[7]:
param_GrSi.update({"SEI reaction exchange current density [A.m-2]":6e-07})sim4=pybamm.Simulation(model_with_degradation,parameter_values=param_GrSi,experiment=exp_degradation,var_pts=var_pts,)sol4=sim4.solve()t=sol4["Time [s]"].entriesV=sol4["Voltage [V]"].entriesplt.figure()plt.plot(t,V)plt.xlabel("Time [s]")plt.ylabel("Voltage [V]")plt.show()
[8]:
Q_SEI_n=sol4["Loss of capacity to negative SEI [A.h]"].entriesQ_SEI_p=sol4["Loss of capacity to positive SEI [A.h]"].entriesQ_SEI_cr=sol4["Loss of capacity to positive SEI on cracks [A.h]"].entriesQ_pl=sol4["Loss of capacity to positive lithium plating [A.h]"].entriesplt.figure()plt.plot(t,Q_SEI_n,label="Negative SEI")plt.plot(t,Q_SEI_p,label="Positive SEI")plt.plot(t,Q_SEI_cr,label="SEI on cracks")plt.plot(t,Q_pl,label="Lithium plating")plt.xlabel("Time [s]")plt.ylabel("Loss of lithium inventory [A.h]")plt.legend()plt.show()
The additional SEI increases the cell resistance, preventing the graphite-silicon composite from being fully lithiated, so there is less plating than before.
[9]:
pybamm.print_citations()
[1] Weilong Ai, Ludwig Kraft, Johannes Sturm, Andreas Jossen, and Billy Wu. Electrochemical thermal-mechanical modelling of stress inhomogeneity in lithium-ion pouch cells. Journal of The Electrochemical Society, 167(1):013512, 2019. doi:10.1149/2.0122001JES.
[2] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[3] Chang-Hui Chen, Ferran Brosa Planella, Kieran O'Regan, Dominika Gastol, W. Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.
[4] Rutooj Deshpande, Mark Verbrugge, Yang-Tse Cheng, John Wang, and Ping Liu. Battery cycle life prediction with coupled chemical degradation and fatigue mechanics. Journal of the Electrochemical Society, 159(10):A1730, 2012. doi:10.1149/2.049210jes.
[5] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[6] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[7] Scott G. Marquis. Long-term degradation of lithium-ion batteries. PhD thesis, University of Oxford, 2020.
[8] Simon E. J. O'Kane, Ian D. Campbell, Mohamed W. J. Marzook, Gregory J. Offer, and Monica Marinescu. Physical origin of the differential voltage minimum associated with lithium plating in li-ion batteries. Journal of The Electrochemical Society, 167(9):090540, may 2020. URL: https://doi.org/10.1149/1945-7111/ab90ac, doi:10.1149/1945-7111/ab90ac.
[9] Simon E. J. O'Kane, Weilong Ai, Ganesh Madabattula, Diego Alonso-Alvarez, Robert Timms, Valentin Sulzer, Jacqueline Sophie Edge, Billy Wu, Gregory J. Offer, and Monica Marinescu. Lithium-ion battery degradation: how to model it. Phys. Chem. Chem. Phys., 24:7909-7922, 2022. URL: http://dx.doi.org/10.1039/D2CP00417H, doi:10.1039/D2CP00417H.
[10] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[11] Lars Ole Valøen and Jan N Reimers. Transport properties of lipf6-based li-ion battery electrolytes. Journal of The Electrochemical Society, 152(5):A882, 2005.
[12] Shanshan Xu, Kuan-Hung Chen, Neil P Dasgupta, Jason B Siegel, and Anna G Stefanopoulou. Evolution of dead lithium growth in lithium metal batteries: experimentally validated model of the apparent capacity loss. Journal of The Electrochemical Society, 166(14):A3456, 2019.
[ ]:
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this notebook we show how to set up and solve the “two-potential” model from “Homogenisation of spirally-wound high-contrast layered materials”, S. Psaltis, R. Timms, C.P. Please, S.J. Chapman, SIAM Journal on Applied Mathematics, 2020.
We consider a spirally-wound cell, such as the common 18650 lithium-ion cell. In practice these cells are constructed by rolling a sandwich of layers containing the active cathode, positive current collector, active cathode, separator, active anode, negative current collector, active anode, and separator. The “two-potential” model consists of an equation for the potential \(\phi^\pm\) in each current collector. The potential difference drives a current \(I\) through the
electrode/separator/electrode sandwich (which we refer to as the “active material” in the original paper). Thus, in non-dimensional form, the model is
For a complete description of the model and parameters, please refer to the original paper.
It can be shown that the active material can be modelled using any 1D battery model we like to describe the electrochemical/thermal behaviour in the electrode/separator/electrode sandwich. Such functionality will be added to PyBaMM in a future release and will enable efficient simulations of jelly roll cells.
In this section we consider a simplified model in which we ignore the details of the anode, cathode and separator, and treat them as a single region of active material, modelled as an Ohmic conductor, with two such regions per winding. In this case the model becomes
We begin by importing PyBaMM along with some other useful packages
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpfromnumpyimportpiimportmatplotlib.pyplotasplt
[notice] A new release of pip is available: 23.3.1 -> 23.3.2[notice] To update, run: pip install --upgrade pip
Note: you may need to restart the kernel to use updated packages.
First we will define the parameters in the model. Note the model is posed in non-dimensional form.
[2]:
N=pybamm.Parameter("Number of winds")r0=pybamm.Parameter("Inner radius")eps=(1-r0)/N# ratio of sandwich thickness to cell radiusdelta=pybamm.Parameter("Current collector thickness")delta_p=delta# assume same thicknessdelta_n=delta# assume same thicknessl=1/2-delta_p-delta_n# active material thicknesssigma_p=pybamm.Parameter("Positive current collector conductivity")sigma_n=pybamm.Parameter("Negative current collector conductivity")sigma_a=pybamm.Parameter("Active material conductivity")
Next we provide values for our parameters, and process our geometry and model, thus replacing the Parameter symbols with numerical values
[4]:
params=pybamm.ParameterValues({"Number of winds":20,"Inner radius":0.25,"Current collector thickness":0.05,"Positive current collector conductivity":5e6,"Negative current collector conductivity":5e6,"Active material conductivity":1,})params.process_geometry(geometry)params.process_model(model)
[4]:
<pybamm.models.base_model.BaseModel at 0x280b0ed90>
We choose to discretise in space using the Finite Volume method on a uniform grid
The model gives the homogenised potentials in the negative a positive current collectors. Interestingly, the solid potential has microscale structure, varying linearly in the active material. In order to see this we need to post-process the solution and plot the potential as a function of radial position, being careful to capture the spiral geometry.
[7]:
# extract numerical parameter values# Note: this overrides the definition of the `pybamm.Parameter` objectsN=params.evaluate(N)r0=params.evaluate(r0)eps=params.evaluate(eps)delta=params.evaluate(delta)
[8]:
# post-process homogenised potentialphi_n=solution["Negative potential"]phi_p=solution["Positive potential"]defalpha(r):return2*(phi_n(r=r)-phi_p(r=r))defphi_am1(r,theta):# careful here - phi always returns a column vector so we need to add a new axis to r to get the right shapereturnalpha(r)*(r[:,np.newaxis]/eps-r0/eps-delta-theta/2/pi)/(1-4*delta)+phi_p(r=r)defphi_am2(r,theta):# careful here - phi always returns a column vector so we need to add a new axis to r to get the right shapereturnalpha(r)*(r0/eps+1-delta+theta/2/pi-r[:,np.newaxis]/eps)/(1-4*delta)+phi_p(r=r)
# Setup fine mesh with nr points per layernr=10rr=np.linspace(r0,1,nr)tt=np.arange(0,(N+1)*2*pi,2*pi)# N+1 winds of pos c.c.r_mesh_pos=np.zeros((len(tt),len(rr)))foriinrange(len(tt)):r_mesh_pos[i,:]=np.linspace(spiral_pos_inner(tt[i]),spiral_pos_outer(tt[i]),nr)# N winds of neg, am1, am2r_mesh_neg=np.zeros((len(tt)-1,len(rr)))r_mesh_am1=np.zeros((len(tt)-1,len(rr)))r_mesh_am2=np.zeros((len(tt)-1,len(rr)))foriinrange(len(tt)-1):r_mesh_am2[i,:]=np.linspace(spiral_am2_inner(tt[i]),spiral_am2_outer(tt[i]),nr)r_mesh_neg[i,:]=np.linspace(spiral_neg_inner(tt[i]),spiral_neg_outer(tt[i]),nr)r_mesh_am1[i,:]=np.linspace(spiral_am1_inner(tt[i]),spiral_am1_outer(tt[i]),nr)# Combine and sortr_total_mesh=np.vstack((r_mesh_pos,r_mesh_neg,r_mesh_am1,r_mesh_am2))r_total_mesh=np.sort(r_total_mesh,axis=None)
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
latexify can also be used to get the equations in a file format like png, jpg, pdf or tex.
[5]:
model.latexify("spm_equations_.png")
will create a png file titled spm_equations.png in the working directory.
[6]:
model_spme=pybamm.lithium_ion.SPMe()
Jupyter notebook sometimes cannot render latex output that is too large. In that case we use newline=False and loop over the lines. When newline=False it returns a list, so it inherits all the properties of a list and you can also do something like this
[1] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[2] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[3] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
We compare a standard porous-electrode model for lead-acid batteries with two asymptotic reductions. For a more in-depth introduction to PyBaMM models, see the SPM notebook. Further details on the models can be found in [4].
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpimportosimportmatplotlib.pyplotaspltos.chdir(pybamm.__path__[0]+"/..")
WARNING: You are using pip version 22.0.4; however, version 22.3.1 is available.
You should consider upgrading via the '/home/mrobins/git/PyBaMM/env/bin/python -m pip install --upgrade pip' command.Note: you may need to restart the kernel to use updated packages.
We load process parameters for each model, using the same set of (default) parameters for all. In anticipation of changing the current later, we make current an input parameter
[4]:
# load modelsmodels=[loqs,full]# process parametersparam=models[0].default_parameter_valuesparam["Current function [A]"]="[input]"formodelinmodels:param.process_model(model)
Then, we discretise the models, using the default settings
[5]:
formodelinmodels:# load and process default geometrygeometry=model.default_geometryparam.process_geometry(geometry)# discretise using default settingsmesh=pybamm.Mesh(geometry,model.default_submesh_types,model.default_var_pts)disc=pybamm.Discretisation(mesh,model.default_spatial_methods)disc.process_model(model)
Finally, we solve each model using CasADi’s solver and a current of 1A
[6]:
timer=pybamm.Timer()solutions={}t_eval=np.linspace(0,3600*17,100)# time in secondsformodelinmodels:solver=pybamm.CasadiSolver()timer.reset()solution=solver.solve(model,t_eval,inputs={"Current function [A]":1})print(f"Solved the {model.name} in {timer.time()}")solutions[model]=solution
Solved the LOQS model in 64.611 ms
Solved the Full model in 736.504 ms
If we update the current, setting it to be 20 A, we observe a greater discrepancy between the full model and the reduced-order models.
[9]:
t_eval=np.linspace(0,3600,100)formodelinmodels:solver=pybamm.CasadiSolver()solutions[model]=solver.solve(model,t_eval,inputs={"Current function [A]":20})# Plotsolution_values=[solutions[model]formodelinmodels]quick_plot=pybamm.QuickPlot(solution_values)quick_plot.dynamic_plot();
At t = 0.0087774 and h = 1.94278e-62, the corrector convergence failed repeatedly or with |h| = hmin.
At t = 0.00384476 and h = 1.59607e-100, the corrector convergence failed repeatedly or with |h| = hmin.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Valentin Sulzer, S. Jon Chapman, Colin P. Please, David A. Howey, and Charles W. Monroe. Faster Lead-Acid Battery Simulations from Porous-Electrode Theory: Part I. Physical Model. Journal of The Electrochemical Society, 166(12):A2363–A2371, 2019. doi:10.1149/2.0301910jes.
[4] Valentin Sulzer, S. Jon Chapman, Colin P. Please, David A. Howey, and Charles W. Monroe. Faster Lead-Acid Battery Simulations from Porous-Electrode Theory: Part II. Asymptotic Analysis. Journal of The Electrochemical Society, 166(12):A2372–A2382, 2019. doi:10.1149/2.0441908jes.
[5] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
This notebook shows how PyBaMM [8] can be used to model both reversible and irreversible lithium plating.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportosos.chdir(pybamm.__path__[0]+"/..")
The Doyle-Fuller-Newman model [3] is upgraded with three different lithium plating models. Model 1 contains the reversible lithium plating model of O’Kane et al. [5]. Model 2 contains the same model but with the lithium stripping capability removed, making the plating irreversible. Model 3 contains the updated partially reversible plating of O’Kane et al. [6]. The parameters are taken from Chen et al.’s investigation [2] of an LG M50 cell.
[2]:
# choose modelsplating_options=["reversible","irreversible","partially reversible"]models={option:pybamm.lithium_ion.DFN(options={"lithium plating":option},name=option)foroptioninplating_options}# pick parametersparameter_values=pybamm.ParameterValues("OKane2022")parameter_values.update({"Ambient temperature [K]":268.15})parameter_values.update({"Upper voltage cut-off [V]":4.21})# parameter_values.update({"Lithium plating kinetic rate constant [m.s-1]": 1E-9})parameter_values.update({"Lithium plating transfer coefficient":0.5})parameter_values.update({"Dead lithium decay constant [s-1]":1e-4})
A series of simple fast charging experiments based on those of Ren et al. [7] is defined here. We first initialise the model at 0% SoC by performing a C/20 discharge (see more details on how to initialise a model from a simulation in this notebook).
[3]:
# specify experimentspybamm.citations.register("Ren2018")s=pybamm.step.stringexperiment_discharge=pybamm.Experiment([(s("Discharge at C/20 until 2.5 V",period="10 minutes"),s("Rest for 1 hour",period="3 minutes"),),])sims_discharge=[]formodelinmodels.values():sim_discharge=pybamm.Simulation(model,parameter_values=parameter_values,experiment=experiment_discharge)sol_discharge=sim_discharge.solve(calc_esoh=False)model.set_initial_conditions_from(sol_discharge,inplace=True)sims_discharge.append(sim_discharge)
And we can now define the different experiments to charge at different C-rates.
[4]:
C_rates=["2C","1C","C/2","C/4","C/8"]experiments={}forC_rateinC_rates:experiments[C_rate]=pybamm.Experiment([(f"Charge at {C_rate} until 4.2 V","Hold at 4.2 V until C/20","Rest for 1 hour",)])
Solve the reversible plating model first. The default CasADi [1] solver is used here.
colors=["tab:purple","tab:cyan","tab:red","tab:green","tab:blue"]linestyles=["dashed","dotted","solid"]param=models["reversible"].paramA=parameter_values.evaluate(param.L_y*param.L_z)F=parameter_values.evaluate(param.F)L_n=parameter_values.evaluate(param.n.L)currents=["X-averaged negative electrode volumetric interfacial current density [A.m-3]","X-averaged negative electrode lithium plating volumetric interfacial current density [A.m-3]","Sum of x-averaged negative electrode volumetric interfacial current densities [A.m-3]",]defplot(sims):importmatplotlib.pyplotaspltfig,axs=plt.subplots(2,2,figsize=(13,9))for(C_rate,sim),colorinzip(sims.items(),colors):# Isolate final equilibration phasesol=sim.solution.cycles[0].steps[2]# Voltage vs timet=sol["Time [min]"].entriest=t-t[0]V=sol["Voltage [V]"].entriesaxs[0,0].plot(t,V,color=color,linestyle="solid",label=C_rate)# Currentsforcurrent,lsinzip(currents,linestyles):j=sol[current].entriesaxs[0,1].plot(t,j,color=color,linestyle=ls)# Plated lithium capacityQ_Li=sol["Loss of capacity to negative lithium plating [A.h]"].entriesaxs[1,0].plot(t,Q_Li,color=color,linestyle="solid")# Capacity vs timeQ_main=(sol["Negative electrode volume-averaged concentration [mol.m-3]"].entries*F*A*L_n/3600)axs[1,1].plot(t,Q_main,color=color,linestyle="solid")axs[0,0].legend()axs[0,0].set_ylabel("Voltage [V]")axs[0,1].set_ylabel("Volumetric interfacial current density [A.m-3]")axs[0,1].legend(("Deintercalation current","Stripping current","Total current"))axs[1,0].set_ylabel("Plated lithium capacity [A.h]")axs[1,1].set_ylabel("Intercalated lithium capacity [A.h]")foraxinaxs.flat:ax.set_xlabel("Time [minutes]")fig.tight_layout()returnfig,axsplot(sims_reversible);
The results show both similarities and differences with those of Ren et al. [7]. Notably, unlike Ren et al., this model uses equations [5] that result in a small but finite amount of plated lithium being present in the steady state.
Now solve the irreversible plating model and see how it compares.
Unlike in the reversible case, there is no steady state and the capacity degrades quickly. The lithium inventory decreases by around 40 mAh in just an hour, which is unrealistic. The low temperature fast charge simulations are run one more time, with the partially reversible plating model.
The partially reversible plating model has features of both the reversible and irreversible models, which is reflected in the results. The plated lithium capacity decreases with time as lithium is reversibly stripped, but the final plated lithium capacity now depends on charge rate, indicating that some lithium was irreversibly plated during charge.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Chang-Hui Chen, Ferran Brosa Planella, Kieran O'Regan, Dominika Gastol, W. Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.
[3] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[4] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[5] Simon E. J. O'Kane, Ian D. Campbell, Mohamed W. J. Marzook, Gregory J. Offer, and Monica Marinescu. Physical origin of the differential voltage minimum associated with lithium plating in li-ion batteries. Journal of The Electrochemical Society, 167(9):090540, may 2020. URL: https://doi.org/10.1149/1945-7111/ab90ac, doi:10.1149/1945-7111/ab90ac.
[6] Simon E. J. O'Kane, Weilong Ai, Ganesh Madabattula, Diego Alonso-Alvarez, Robert Timms, Valentin Sulzer, Jacqueline Sophie Edge, Billy Wu, Gregory J. Offer, and Monica Marinescu. Lithium-ion battery degradation: how to model it. Phys. Chem. Chem. Phys., 24:7909-7922, 2022. URL: http://dx.doi.org/10.1039/D2CP00417H, doi:10.1039/D2CP00417H.
[7] Dongsheng Ren, Kandler Smith, Dongxu Guo, Xuebing Han, Xuning Feng, Languang Lu, and Minggao Ouyang. Investigation of lithium plating-stripping process in li-ion batteries at low temperature using an electrochemical model. Journal of the Electrochemistry Society, 165:A2167-A2178, 2018. doi:10.1149/2.0661810jes.
[8] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
The Many Paticle Model (MPM) of a lithium-ion battery is an extension of the Single Particle Model to account for a continuous distribution of active particle sizes in each electrode \(\text{k}=\text{n},\text{p}\). Therefore, many of the same model assumptions hold, e.g., the transport in the electrolyte is instantaneous and hence the through-cell variation (in \(x\)) is neglected. The full set of assumptions and description of the particle size geometry is given in
[4]. Note that the MPM in [4] is for a half cell and the version implemented in PyBaMM is for a full cell and uses the notation and scaling given in [5].
In each electrode \(\text{k}=\text{n},\text{p}\), there are spherical particles of each radius \(R_\text{k}\) in the range \(R_\text{k,min}<R_\text{k}<R_\text{k,max}\), with the fraction of all particles of a given radius \(R_\text{k}\) given by the particle-size distribution (base on number) \(f_\text{k,num}(R_\text{k})\). However, it is more convenient to deal with the fraction of surface area contributed by particles of radius \(R_\text{k}\), which we denote
\(f_{\text{k},a}(R_\text{k})\) and refer to as the area-weighted particle-size distribution. The area and number-based distributions are related via
The total amount of surface area (per unit volume) \(a_\text{k,tot}\) accounting for all particles is expressed in terms of the active material volume fraction \(\epsilon_{s,\text{k}}\), similar to the other models in PyBaMM (SPM, DFN):
\[\epsilon_{s,\text{k}}= \int \frac{1}{3} R_\text{k} \underbrace{a_\text{k,tot}f_{\text{k},a}(R_\text{k})}_{\text{area }a_\text{k}(R_\text{k})\text{ of particles size }R_\text{k}}\,\text{d}R_\text{k}\]
Rearranging and defining \(\bar{R}_{\text{k},a}=\int R_\text{k} f_{\text{k},a}(R_\text{k})\,\text{d}R_\text{k}\) as the mean of the area-weighted distribution, we find
Then \(a_\text{k,tot}\) is the aggregate surface area of the particle population and analogous to the variables "X-averagednegativeelectrodesurfaceareatovolumeratio[m-1]", etc. in the SPM, SPMe, and DFN models, and can be calculated in a similar way as shown above using the area-weighted mean radius\(\bar{R}_{\text{k},a}\) (other mean radii do not have this property). See [4] for more details on the different types of distribution and mean radii.
Another common way to express the size distribution is via particle volume. The fraction of volume contributed by the particles of radius \(R_\text{k}\), denoted the volume-weighted particle-size distribution is related to the number and area ones via
In each electrode, only one representative particle of each size \(R_\text{k}\) needs to be modelled. The concentration of lithium in the solid particles is denoted \(c_{\text{s,k}}(t,R_\text{k}, r_\text{k})\), which varies with time, particle radius \(R_\text{k}\), and the radial coordinate \(r_{\text{k}} \in[0,R_{\text{k}}]\) within the spherical particle. The potential is uniform across all particles in the electrode, \(\phi_{\text{s,k}}(t)\).
The equations for molar conservation of lithium (\(c_{\text{s,k}}\)) are then:
where \(D_{\text{s,k}}\) is the diffusion coefficient in the solid, \(N_{\text{s,k}}\) denotes the flux of lithium ions in the solid particle, \(F\) is Faraday’s constant. The interfacial current density is given by \(j_\text{k}\), which also varies with particle size.
The potentials \(\phi_{\text{s,k}}(t)\) are determined via the integral constraint that the total current flowing across the electrode interface must equal (up to a minus sign) the through-cell current density \(i\). Writing this in terms of the potential differences \(\Delta \phi_{\text{s,k}} = \phi_{\text{s,k}} - \phi_{\text{e}}\),
This gives an integral (or algebraic once discretized) equation for \(\Delta \phi_{\text{s,k}}\) which is coupled to the concentration equations above. The voltage is then obtained from
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportmatplotlib.pyplotasplt
Note: you may need to restart the kernel to use updated packages.
Create an instance of the model
[2]:
model=pybamm.lithium_ion.MPM()
First, let’s inspect some variables (e.g. the lithium concentration and interfacial current densities) that depend on particle size \(R_\text{k}\). The variables of interest are X-averaged versions as there is no dependence on \(x\).
Notice that the secondary domain is 'negativeparticlesize', which is treated as another (microscale) domain in PyBaMM.
The variable without the "distribution" has been “size averaged” and can be compared to the variable with the same name from the other lithium-ion models in PyBaMM with only a single particle size. The concentration within the particles is a volume-based quantity and is thus averaged by volume (to preserve the total amount of lithium):
In particular, if the variance of the particle-size distribution \(f_{\text{k},a}\) is shrunk to zero and all particles become concentrated at its mean radius \(\bar{R}_{\text{k},a}\), the variable "X-averagednegativeparticleconcentration[mol.m-3]" will coincide with the same variable from an SPM with particle radius \(R_\text{k}=\bar{R}_{\text{k},a}\). However, "X-averagednegativeparticleconcentrationdistribution[mol.m-3]" will remain “particle-size dependent”.
The convention of adding "distribution" to the end of a variable name to indicate particle-size dependence has been used for other variables, such as the interfacial current density:
[5]:
model.variables.search("X-averaged negative electrode interfacial current density")
X-averaged negative electrode interfacial current density
X-averaged negative electrode interfacial current density [A.m-2]
X-averaged negative electrode interfacial current density distribution
X-averaged negative electrode interfacial current density distribution [A.m-2]
X-averaged negative electrode interfacial current density per volume [A.m-3]
As the interfacial current density is a flux per unit area on the particle surface, the “size averaging” is done by area (to preserve the total flux of lithium):
The averaging is merely done to allow comparison to variables from other models with only a single size, and are not necessarily used within the MPM itself, or are physically meaningful.
Note: not all variables have a “distribution” version, such as the potentials or temperature variables, as they do not vary with particle size in the MPM as implemented here.
By default, the size domain is discretized into 30 grid points on a uniform 1D mesh.
[6]:
fork,tinmodel.default_submesh_types.items():print(k,"is of type",t.__name__)forvar,nptsinmodel.default_var_pts.items():print(var,"has",npts,"mesh points")
negative electrode is of type Uniform1DSubMesh
separator is of type Uniform1DSubMesh
positive electrode is of type Uniform1DSubMesh
negative particle is of type Uniform1DSubMesh
positive particle is of type Uniform1DSubMesh
negative particle size is of type Uniform1DSubMesh
positive particle size is of type Uniform1DSubMesh
current collector is of type SubMesh0D
x_n has 20 mesh points
x_s has 20 mesh points
x_p has 20 mesh points
r_n has 20 mesh points
r_p has 20 mesh points
y has 10 mesh points
z has 10 mesh points
R_n has 30 mesh points
R_p has 30 mesh points
Now solve the MPM with the default parameters and size distributions.
[7]:
sim=pybamm.Simulation(model)sim.solve(t_eval=[0,3600])# plot some variables that depend on Routput_variables=["X-averaged negative particle surface concentration distribution [mol.m-3]","X-averaged positive particle surface concentration distribution [mol.m-3]","X-averaged positive electrode interfacial current density distribution [A.m-2]","X-averaged negative area-weighted particle-size distribution [m-1]","X-averaged positive area-weighted particle-size distribution [m-1]","Voltage [V]",]sim.plot(output_variables=output_variables)
[7]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7fd39283a7f0>
We can also visualise the concentration within the particles.
[8]:
# Concentrations as a function of t, r and Rc_s_n=sim.solution["X-averaged negative particle concentration distribution [mol.m-3]"]c_s_p=sim.solution["X-averaged positive particle concentration distribution [mol.m-3]"]# r_n, r_pr_n=sim.solution["r_n [m]"].entries[:,0,0]r_p=sim.solution["r_p [m]"].entries[:,0,0]# dimensional R_n, R_pR_n=sim.solution["Negative particle sizes [m]"].entries[:,0]R_p=sim.solution["Positive particle sizes [m]"].entries[:,0]t=sim.solution["Time [s]"].entriesdefplot_concentrations(t):f,axs=plt.subplots(1,2,figsize=(10,3))plot_c_n=axs[0].pcolormesh(R_n,r_n,c_s_n(r=r_n,R=R_n,t=t),vmin=0.15,vmax=0.8)plot_c_p=axs[1].pcolormesh(R_p,r_p,c_s_p(r=r_p,R=R_p,t=t),vmin=0.6,vmax=0.95)axs[0].set_xlabel(r"$R_n$ [$\mu$m]")axs[1].set_xlabel(r"$R_p$ [$\mu$m]")axs[0].set_ylabel(r"$r_n / R_n$")axs[1].set_ylabel(r"$r_p / R_p$")axs[0].set_title("Concentration in negative particles [mol.m-3]")axs[1].set_title("Concentration in positive particles [mol.m-3]")plt.colorbar(plot_c_n,ax=axs[0])plt.colorbar(plot_c_p,ax=axs[1])plt.show()# initial timeplot_concentrations(t[0])
In order to solve the MPM, one must input the area-weighted particle-size distribution \(f_{\text{k},a}\) for each electrode \(\text{k}=\text{n,p}\) and the minimum and maximum radius limits \(R_\text{k,min}\), \(R_\text{k,max}\). The default distributions \(f_{\text{k},a}\), usable with the Marquis et al. [6] parameter set, are lognormals with means equal to the "Negativeparticleradius[m]" and "Positiveparticleradius[m]" values, and standard
deviations equal to 0.3 times the mean.
You can input any size distribution \(f_{\text{k},a}(R_\text{k})\) as a function of \(R_\text{k}\), which we will now demonstrate.
Note: \(f_{\text{k},a}(R_\text{k})\) should ideally integrate to 1 over the specified \(R_\text{k}\) range, although it is automatically normalized within PyBaMM anyway. A distribution such as a lognormal, once restricted to \([R_\text{k,min},R_\text{k,max}]\), discretized, and then renormalized, strictly will not integrate to 1 or have the originally desired mean or variance. The mean and variance of the final discretized distribution can be checked as output variables (see below).
Having a sufficient number of mesh points in \(R_\text{k}\) or a sufficiently wide interval \([R_\text{k,min},R_\text{k,max}]\) should alleviate this issue, however.
[10]:
# Parameter set (no distribution parameters by default)params=pybamm.ParameterValues("Marquis2019")# Extract the radii values. We will choose these to be the means of our area-weighted distributionsR_a_n_dim=params["Negative particle radius [m]"]R_a_p_dim=params["Positive particle radius [m]"]# Standard deviations (dimensional)sd_a_n_dim=0.2*R_a_n_dimsd_a_p_dim=0.6*R_a_p_dim# Minimum and maximum particle sizes (dimensional)R_min_n=0R_min_p=0R_max_n=2*R_a_n_dimR_max_p=3*R_a_p_dim# Set the area-weighted particle-size distributions.# Choose a lognormal (but any pybamm function could be used)deff_a_dist_n_dim(R):returnpybamm.lognormal(R,R_a_n_dim,sd_a_n_dim)deff_a_dist_p_dim(R):returnpybamm.lognormal(R,R_a_p_dim,sd_a_p_dim)# Note: the only argument must be the particle size R
[11]:
# input distribution params to the dictionarydistribution_params={"Negative minimum particle radius [m]":R_min_n,"Positive minimum particle radius [m]":R_min_p,"Negative maximum particle radius [m]":R_max_n,"Positive maximum particle radius [m]":R_max_p,"Negative area-weighted "+"particle-size distribution [m-1]":f_a_dist_n_dim,"Positive area-weighted "+"particle-size distribution [m-1]":f_a_dist_p_dim,}params.update(distribution_params,check_already_exists=False)
<pybamm.plotting.quick_plot.QuickPlot at 0x7fd393666a60>
The discretized size distributions can be plotted as histograms. Only the area-weighted distribution has been input, but the corresponding number and volume-weighted ones are also given as output variables.
[13]:
# The discrete sizes or "bins" used, and the distributionsR_p=sim.solution["Positive particle sizes [m]"].entries[:,0]# const in the current collector direction# The distributionsf_a_p=sim.solution["X-averaged positive area-weighted particle-size distribution [m-1]"].entries[:,0]f_num_p=sim.solution["X-averaged positive number-based particle-size distribution [m-1]"].entries[:,0]f_v_p=sim.solution["X-averaged positive volume-weighted particle-size distribution [m-1]"].entries[:,0]# plotwidth_p=(R_p[-1]-R_p[-2])/1e-6plt.bar(R_p/1e-6,f_a_p*1e-6,width=width_p,alpha=0.3,color="tab:blue",label="area-weighted",)plt.bar(R_p/1e-6,f_num_p*1e-6,width=width_p,alpha=0.3,color="tab:red",label="number-weighted",)plt.bar(R_p/1e-6,f_v_p*1e-6,width=width_p,alpha=0.3,color="tab:green",label="volume-weighted",)plt.xlim((0,30))plt.xlabel("Particle size $R_{\mathrm{p}}$ [$\mu$m]",fontsize=12)plt.ylabel("[$\mu$m$^{-1}$]",fontsize=12)plt.legend(fontsize=10)plt.title("Discretized distributions (histograms) in positive electrode")plt.show()
You may define the standard deviation (or other distribution parameters except for the min or max radii) of the distribution as a pybamm “input” parameter, to quickly change the distribution at the solve stage.
[14]:
# Define standard deviation in negative electrode to varysd_a_p_dim=pybamm.Parameter("Positive electrode area-weighted particle-size standard deviation [m]")# Set the area-weighted particle-size distributiondeff_a_dist_p_dim(R):returnpybamm.lognormal(R,R_a_p_dim,sd_a_p_dim)# input to param dictionarydistribution_params={"Positive electrode area-weighted particle-size "+"standard deviation [m]":"[input]","Positive area-weighted "+"particle-size distribution [m-1]":f_a_dist_p_dim,}params.update(distribution_params,check_already_exists=False)
[15]:
# Experiment with a relaxation period, to see the effect of distribution widthexperiment=pybamm.Experiment(["Discharge at 1 C for 3400 s","Rest for 1 hours"])sim=pybamm.Simulation(model,parameter_values=params,experiment=experiment)solutions=[]forsd_a_pin[0.4,0.6,0.8]:solution=sim.solve(inputs={"Positive electrode area-weighted particle-size "+"standard deviation [m]":sd_a_p*R_a_p_dim})solutions.append(solution)pybamm.dynamic_plot(solutions,output_variables=output_variables,labels=["MPM, sd_a_p=0.4","MPM, sd_a_p=0.6","MPM, sd_a_p=0.8"],)
[15]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7fd392c3d610>
The mean and standard deviations of the final discretized distributions can be investigated using the output variables "Negativearea-weightedmeanparticleradius" and "Negativearea-weightedparticle-sizestandarddeviation", etc.
[16]:
print("The mean of the input lognormal was:",R_a_p_dim)print("The means of discretized distributions are:")forsolutioninsolutions:R=solution["Positive area-weighted mean particle radius [m]"]print("Positive area-weighted mean particle radius [m]",R.entries[0])
The mean of the input lognormal was: 1e-05
The means of discretized distributions are:
Positive area-weighted mean particle radius [m] 9.972515783613799e-06
Positive area-weighted mean particle radius [m] 9.673853099212895e-06
Positive area-weighted mean particle radius [m] 9.124186918191047e-06
[17]:
print("The standard deviations of the input lognormal were:")print(0.4*R_a_p_dim)print(0.6*R_a_p_dim)print(0.8*R_a_p_dim)print("The standard deviations of discretized distributions are:")forsolutioninsolutions:sd=solution["Positive area-weighted particle-size standard deviation [m]"]print("Positive area-weighted particle-size standard deviation [m]",sd.entries[0])
The standard deviations of the input lognormal were:
4.000000000000001e-06
6e-06
8.000000000000001e-06
The standard deviations of discretized distributions are:
Positive area-weighted particle-size standard deviation [m] 3.918218937679725e-06
Positive area-weighted particle-size standard deviation [m] 5.180362201055076e-06
Positive area-weighted particle-size standard deviation [m] 5.815728559306213e-06
The MPM can also be easily compared to PyBaMM models with a single particle size. The standard output variables are computed in the MPM, averaging over the particle size domain.
The MPM is compatible with the current collector and thermal models (except the “x-full” thermal option). Currently, the MPM is not compatible with the various degradation submodels in PyBaMM (i.e. SEI models, particle cracking/swelling, or lithium plating).
Add another macroscale dimension “z”, employing the “potential pair” option solving for the potential in the current collectors.
[20]:
# choose model optionsmodel_cc=pybamm.lithium_ion.MPM(options={"current collector":"potential pair","dimensionality":1,"particle":"uniform profile",# to reduce computation time})# solvesim_cc=pybamm.Simulation(model_cc)sim_cc.solve(t_eval=[0,3600])# variables to plotoutput_variables=["X-averaged negative particle surface concentration distribution [mol.m-3]","X-averaged positive particle surface concentration distribution [mol.m-3]","X-averaged positive electrode interfacial current density distribution [A.m-2]","Negative current collector potential [V]","Positive current collector potential [V]","Voltage [V]",]pybamm.dynamic_plot(sim_cc,output_variables=output_variables)
[20]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7fd3307d5190>
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Toby L. Kirk, Jack Evans, Colin P. Please, and S. Jonathan Chapman. Modelling electrode heterogeneity in lithium-ion batteries: unimodal and bimodal particle-size distributions. arXiv:2006.12208, 2020. URL: https://arxiv.org/abs/2006.12208, arXiv:2006.12208.
[5] Toby L. Kirk, Colin P. Please, and S. Jon Chapman. Physical modelling of the slow voltage relaxation phenomenon in lithium-ion batteries. Journal of The Electrochemical Society, 168(6):060554, jun 2021. URL: https://doi.org/10.1149/1945-7111/ac0bf7, doi:10.1149/1945-7111/ac0bf7.
[6] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[7] Peyman Mohtat, Suhak Lee, Jason B Siegel, and Anna G Stefanopoulou. Towards better estimability of electrode-specific state of health: decoding the cell expansion. Journal of Power Sources, 427:101–111, 2019.
[8] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[9] Robert Timms, Scott G Marquis, Valentin Sulzer, Colin P. Please, and S Jonathan Chapman. Asymptotic Reduction of a Lithium-ion Pouch Cell Model. SIAM Journal on Applied Mathematics, 81(3):765–788, 2021. doi:10.1137/20M1336898.
[10] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
Tip
An interactive online version of this notebook is available, which can be
accessed via
Here we briefly outline the models used for the open-circuit potential, kinetics, and solid phase transport used in the MSMR model, as described in Baker and Verbrugge (2018). The remaining physics is modelled differently depending on which options are selected. By default, the rest of the battery model is as described in Maquis et al. (2019). In the following we give equations for a single electrode.
The MSMR model is developed by assuming that all electrochemical reactions at the electrode/electrolyte interface in a lithium insertion cell can be expressed in the form
For each species \(j\), a vacant host site \(\text{H}_{j}\) can accommodate one lithium leading to a filled host site \((\text{Li--H})_{j}\). The OCV for this reaction is written as
where \(f = (RT)/F\), and \(R\), \(T\), and \(F\) are the universal gas constant, temperature in Kelvin, and Faraday’s constant, respectively. Here \(X_j\) represents the total fraction of available host sites which can be occupied by species \(j\), \(x_j\) is the fraction of filled sites occupied by species \(j\), \(U_j^0\) is a concentration independent standard electrode potential, and the \(\omega_j\) is an unitless parameter that describes the level
of disorder of the reaction represented by gallery \(j\).
The equation for each reaction can be inverted to give
which is an explicit closed form expression for the inverse of the OCV. This is opposite to many battery models where one typically gives the OCV as an explicit function of the state of charge (or stoichiometry).
At a particle interface with the electrolyte, local equilibrium requires that
The kinetics of the insertion reaction are given as
\[i_j = i_{0,j}[e^{(1-\alpha_j)f\eta} - e^{-\alpha_jf\eta}], \qquad i = \sum_j i_j,\]
where \(i_j\) is the interfacial current associated with reaction \(j\), \(\alpha_j\) is the symmetry factor, \(\eta\) is the overpotential, given by
\[\eta = \phi_s - \phi_e - U(x),\]
where \(\phi_s\) and \(\phi_e\) are the solid phase and electrolyte potentials, respectively, and \(i_{0,j}\) is the exchange current density of reaction \(j\), given by
where \(N\) is the flux of lithiated sites, \(N_{\text{H}}\) is the flux of unlithiated sites, \(c_{\text{T}}\) is the total concentration of lithiated and delithiated sites, and \(D\) is a diffusion coefficient. Ignoring volumetric expansion during lithiation, the total flux of sites vanishes
where \(R\) is the particle radius. This must be supplemented with a suitable initial condition for the electrode state of charge.
Solution of this problem requires evaluate of the function \(U(x)\) and the derivative \(\text{d}U/\text{d}x\), but these functions cannot be explicitly integrated. This problem can be avoided by replacing the dependent variable \(x\) with a new dependent variable \(U\) subject to the transformation
The behaviour of MSMR model is characterised by the parameters \(X_j\), \(U^0_j\), \(\omega_j\), \(\alpha_j\), and \(i_{0,j}^{ref}\). Let’s take a look at their values in the example parameter set provided in PyBaMM. The thermodynamic parameter values are taken from Verbrugge et al. (2017) and correspond to a graphite negative electrode and NMC positive electrode. The remaining value are based on a parameterization of the LG M50 cell, from Chen et al. (2020).
We first load in the MSMR model and specify the number of reactions in each electrode
[2]:
model=pybamm.lithium_ion.MSMR({"number of MSMR reactions":("6","4")})
Then we can inspect the parameter values
[3]:
parameter_values=model.default_parameter_values# Loop over domainsfordomainin["negative","positive"]:print(f"{domain} electrode:")d=domain[0]# Loop over reactionsN=int(parameter_values["Number of reactions in "+domain+" electrode"])foriinrange(N):print(f"X_{d}_{i} = {parameter_values[f'X_{d}_{i}']}, "f"U0_{d}_{i} = {parameter_values[f'U0_{d}_{i}']}, "f"w_{d}_{i} = {parameter_values[f'w_{d}_{i}']}, "f"a_{d}_{i} = {parameter_values[f'a_{d}_{i}']} "f"j0_ref_{d}_{i} = {parameter_values[f'j0_ref_{d}_{i}']}")
We can plot the functional form of the open-circuit potential \(U\), fractional occupancies \(x_j\), and exchange current densities \(i_{0,j}\) as a function of stoichiometry \(x\)
[4]:
# get symbolic parametersparam=model.paramparam_n=param.n.primparam_p=param.p.prim# set up ranges for plottingU_n=pybamm.linspace(0.05,1.1,1000)U_p=pybamm.linspace(2.8,4.4,1000)# get reference electrolyte concentration and temperaturec_e=param.c_e_initT=param.T_init# set up figurefig,ax=plt.subplots(3,2,figsize=(10,10))colors=["r","g","b","c","m","y"]# sto vs potentialx_n=param_n.x(U_n)x_p=param_p.x(U_p)ax[0,0].plot(parameter_values.evaluate(x_n),parameter_values.evaluate(U_n),"k-")ax[0,1].plot(parameter_values.evaluate(x_p),parameter_values.evaluate(U_p),"k-")ax[0,0].set_xlabel("x_n")ax[0,0].set_ylabel("U_n [V]")ax[0,1].set_xlabel("x_p")ax[0,1].set_ylabel("U_p [V]")# fractional occupancy vs potentialforiinrange(6):xj=param_n.x_j(U_n,i)ax[1,0].plot(parameter_values.evaluate(x_n),parameter_values.evaluate(xj),color=colors[i],label=f"x_n_{i}",)ax[1,0].set_xlabel("x_n")ax[1,0].set_ylabel("x_n_j")ax[1,0].legend()foriinrange(4):xj=param_p.x_j(U_p,i)ax[1,1].plot(parameter_values.evaluate(x_p),parameter_values.evaluate(xj),color=colors[i],label=f"x_p_{i}",)ax[1,1].set_xlabel("x_p")ax[1,1].set_ylabel("x_p_j")ax[1,1].legend()# exchange current density vs potentialforiinrange(6):xj=param_n.x_j(U_n,i)j0=param_n.j0_j(c_e,U_n,T,i)ax[2,0].plot(parameter_values.evaluate(x_n),parameter_values.evaluate(j0),color=colors[i],label=f"j0_n_{i}",)ax[2,0].set_xlabel("x_n")ax[2,0].set_ylabel("j0_n_j [A.m-2]")ax[2,0].legend()foriinrange(4):xj=param_p.x_j(U_p,i)j0=param_p.j0_j(c_e,U_p,T,i)ax[2,1].plot(parameter_values.evaluate(x_p),parameter_values.evaluate(j0),color=colors[i],label=f"j0_p_{i}",)ax[2,1].set_ylim([0,0.5])ax[2,1].set_xlabel("x_p")ax[2,1].set_ylabel("j0_p_j [A.m-2]")ax[2,1].legend()plt.tight_layout()
Below we show how to set up and solve a CCCV experiment using the MSMR model in PyBaMM. We already created the model in the previous section, so we can go ahead and define our experiment, before creating and solving a simulation
[5]:
experiment=pybamm.Experiment([("Discharge at 1C for 1 hour or until 3 V","Rest for 1 hour","Charge at C/3 until 4.2 V","Hold at 4.2 V until 10 mA","Rest for 1 hour",),],period="10 seconds",)sim=pybamm.Simulation(model,experiment=experiment)sim.solve()
At t = 275.026 and h = 2.68649e-11, the corrector convergence failed repeatedly or with |h| = hmin.
At t = 275.028 and h = 4.19765e-11, the corrector convergence failed repeatedly or with |h| = hmin.
[5]:
<pybamm.solvers.solution.Solution at 0x2853c7d00>
Finally we can plot the results. In the MSMR model we can look at both the potential and stoichiometry as a function of position through the electrode and within the particle
[6]:
sim.plot(["Negative particle stoichiometry","Positive particle stoichiometry","X-averaged negative electrode open-circuit potential [V]","X-averaged positive electrode open-circuit potential [V]","Negative particle potential [V]","Positive particle potential [V]","Current [A]","Voltage [V]",],variable_limits="tight",# make axes tight to plot at each timestep)
[6]:
<pybamm.plotting.quick_plot.QuickPlot at 0x285cd5640>
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Daniel R Baker and Mark W Verbrugge. Multi-species, multi-reaction model for porous intercalation electrodes: part i. model formulation and a perturbation solution for low-scan-rate, linear-sweep voltammetry of a spinel lithium manganese oxide electrode. Journal of The Electrochemical Society, 165(16):A3952, 2018.
[3] Chang-Hui Chen, Ferran Brosa Planella, Kieran O'Regan, Dominika Gastol, W. Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.
[4] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[5] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[6] Peyman Mohtat, Suhak Lee, Jason B Siegel, and Anna G Stefanopoulou. Towards better estimability of electrode-specific state of health: decoding the cell expansion. Journal of Power Sources, 427:101–111, 2019.
[7] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[8] Mark Verbrugge, Daniel Baker, Brian Koch, Xingcheng Xiao, and Wentian Gu. Thermodynamic model for substitutional materials: application to lithiated graphite, spinel manganese oxide, iron phosphate, and layered nickel-manganese-cobalt oxide. Journal of The Electrochemical Society, 164(11):E3243, 2017.
[9] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
[10] Andrew Weng, Jason B Siegel, and Anna Stefanopoulou. Differential voltage analysis for battery manufacturing process control. arXiv preprint arXiv:2303.07088, 2023.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this notebook we compare the solutions of two reduced-order models of a lithium-ion pouch cell with the full solution obtained using COMSOL. This example is based on the results in [6]. The code used to produce the results in [6] can be found here.
The full model is based on the Doyle-Fuller-Newman model [2] and, in the interest of simplicity, considers a one-dimensional current collector (i.e. variation in one of the current collector dimensions is ignored), resulting in a 2D macroscopic model.
The first of the reduced order models, which is applicable in the limit of large conductivity in the current collectors, solves a one-dimensional problem in the current collectors coupled to a one-dimensional DFN model describing the through-cell electrochemistry at each point. We refer to this as a 1+1D model, though since the DFN is already a pseudo-two-dimensional model, perhaps it is more properly a 1+1+1D model.
The second reduced order model, which is applicable in the limit of very large conductivity in the current collectors, solves a single (averaged) one-dimensional DFN model for the through-cell behaviour and an uncoupled problem for the distribution of potential in the current collectors (from which the resistance and heat source can be calculated). We refer to this model as the DFNCC, where the “CC” indicates the additional (uncoupled) current collector problem.
All of the model equations, and derivations of the reduced-order models, can be found in [6].
Solving the reduced-order pouch cell models in PyBaMM#
We begin by importing PyBaMM along with the other packages required in this notebook
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportpickleimportmatplotlib.pyplotaspltimportnumpyasnpimportscipy.interpolateasinterp
[notice] A new release of pip is available: 23.3.1 -> 23.3.2[notice] To update, run: pip install --upgrade pip
Note: you may need to restart the kernel to use updated packages.
We then need to load up the appropriate models. For the DFNCC we require a 1D model of the current collectors and an average 1D DFN model for the through-cell electrochemistry. The 1+1D pouch cell model is built directly into PyBaMM and are accessed by passing the model option “dimensionality” which can be 1 or 2, corresponding to 1D or 2D current collectors. This option can be passed to any existing electrochemical model (e.g. SPM, SPMe,
DFN). Here we choose the DFN model.
For both electrochemical models we choose an “x-lumped” thermal model, meaning we assume that the temperature is uniform in the through-cell direction \(x\), but account for the variation in temperature in the transverse direction \(z\).
Next we update the parameters to match those used in the COMSOL simulation. In particular, we set the current to correspond to a 3C discharge and assume uniform Newton cooling on all boundaries.
[4]:
param=dfn.default_parameter_valuesI_1C=param["Nominal cell capacity [A.h]"]# 1C current is cell capacity multipled by 1 hourparam.update({"Current function [A]":I_1C*3,"Negative electrode diffusivity [m2.s-1]":3.9*10**(-14),"Positive electrode diffusivity [m2.s-1]":10**(-13),"Negative current collector surface heat transfer coefficient [W.m-2.K-1]":10,"Positive current collector surface heat transfer coefficient [W.m-2.K-1]":10,"Negative tab heat transfer coefficient [W.m-2.K-1]":10,"Positive tab heat transfer coefficient [W.m-2.K-1]":10,"Edge heat transfer coefficient [W.m-2.K-1]":10,"Total heat transfer coefficient [W.m-2.K-1]":10,})
In this example we choose to discretise in space using 16 nodes per domain.
Next we loop over the models, creating and solving a simulation for each.
[7]:
simulations={}solutions={}# store solutions in a separate dict for easy access laterforname,modelinmodels.items():sim=pybamm.Simulation(model,parameter_values=param,var_pts=var_pts)simulations[name]=sim# store simulation for laterifname=="Current collector":# model is independent of time, so just solve arbitrarily at t=0 using# the default algebraic solvert_eval=np.array([0])solutions[name]=sim.solve(t_eval=t_eval)else:# solve at COMSOL times using Casadi solver in "fast" modet_eval=comsol_variables["time"]solutions[name]=sim.solve(solver=pybamm.CasadiSolver(mode="fast"),t_eval=t_eval)
In this section we show how to create a PyBaMM “model” from the COMSOL solution. If you are just interested in seeing the comparison the skip ahead to the section “Comparing the full and reduced-order models”.
To create a PyBaMM model from the COMSOL data we must create a pybamm.Function object for each variable. We do this by interpolating in space to match the PyBaMM mesh and then creating a function to interpolate in time. The following cell defines the function that handles the creation of the pybamm.Function object.
[8]:
# set up timescomsol_t=comsol_variables["time"]pybamm_t=comsol_t# set up spacemesh=simulations["1+1D DFN"].meshL_z=param.evaluate(dfn.param.L_z)pybamm_z=mesh["current collector"].nodesz_interp=pybamm_zdefget_interp_fun_curr_coll(variable_name):""" Create a :class:`pybamm.Function` object using the variable (interpolate in space to match nodes, and then create function to interpolate in time) """comsol_z=comsol_variables[variable_name+"_z"]variable=comsol_variables[variable_name]variable=interp.interp1d(comsol_z,variable,axis=0,kind="linear")(z_interp)# Make sure to use dimensional timefun=pybamm.Interpolant(comsol_t,variable.T,pybamm.t,name=variable_name+"_comsol")fun.domains={"primary":"current collector"}fun.mesh=mesh.combine_submeshes("current collector")fun.secondary_mesh=Nonereturnfun
We then pass the variables of interest to the interpolating function
comsol_model=pybamm.BaseModel()comsol_model._geometry=pybamm.battery_geometry(options={"dimensionality":1})comsol_model.variables={"Voltage [V]":comsol_voltage,"Negative current collector potential [V]":comsol_phi_s_cn,"Positive current collector potential [V]":comsol_phi_s_cp,"Current collector current density [A.m-2]":comsol_current,"X-averaged cell temperature [K]":comsol_temperature,# Add spatial variables to match pybamm model"z [m]":simulations["1+1D DFN"].built_model.variables["z [m]"],}
We then add the solution object from the 1+1D model. This is just so that PyBaMM uses the same times behind the scenes when dealing with COMSOL model and the reduced-order models: the variables in comsol_model.variables are functions of time only that return the (interpolated in space) COMSOL solution.
The DFNCC requires some post-processing to extract the solution variables. In particular, we need to pass the current and voltage from the average DFN model to the current collector model in order to compute the distribution of the potential in the current collectors and to account for the effect of the current collector resistance in the voltage.
This process is automated by the method post_process which accepts the current collector solution object, the parameters and the voltage and current from the average DFN model. The results are stored in the dictionary dfncc_vars
[12]:
V_av=solutions["Average DFN"]["Voltage [V]"]I_av=solutions["Average DFN"]["Total current density [A.m-2]"]dfncc_vars=cc_model.post_process(solutions["Current collector"],param,V_av,I_av)
Next we create a function to create some custom plots. For a given variable the plots will show: (a) the COMSOL results as a function of position in the current collector \(z\) and time \(t\); (b) a comparison of the full and reduced-order models and a sequence of times; (c) the time-averaged error between the full and reduced-order models as a function of space; and (d) the space-averaged error between the full and reduced-order models as a function of time.
[13]:
defplot(t_plot,z_plot,t_slices,var_name,units,comsol_var_fun,dfn_var_fun,dfncc_var_fun,param,cmap="viridis",):fig,ax=plt.subplots(2,2,figsize=(13,7))fig.subplots_adjust(left=0.15,bottom=0.1,right=0.95,top=0.95,wspace=0.4,hspace=0.8)# plot comsol varcomsol_var=comsol_var_fun(t=t_plot,z=z_plot)comsol_var_plot=ax[0,0].pcolormesh(z_plot*1e3,t_plot,np.transpose(comsol_var),shading="gouraud",cmap=cmap)if"cn"invar_name:format="%.0e"elif"cp"invar_name:format="%.0e"else:format=Nonefig.colorbar(comsol_var_plot,ax=ax,format=format,location="top",shrink=0.42,aspect=20,anchor=(0.0,0.0),)# plot slicesccmap=plt.get_cmap("inferno")forind,tinenumerate(t_slices):color=ccmap(float(ind)/len(t_slices))comsol_var_slice=comsol_var_fun(t=t,z=z_plot)dfn_var_slice=dfn_var_fun(t=t,z=z_plot)dfncc_var_slice=dfncc_var_fun(t=np.array([t]),z=z_plot)ax[0,1].plot(z_plot*1e3,comsol_var_slice,"o",fillstyle="none",color=color)ax[0,1].plot(z_plot*1e3,dfn_var_slice,"-",color=color,label=f"{t_slices[ind]:.0f} s",)ax[0,1].plot(z_plot*1e3,dfncc_var_slice,":",color=color)# add dummy points for legend of styles(comsol_p,)=ax[0,1].plot(np.nan,np.nan,"ko",fillstyle="none")(pybamm_p,)=ax[0,1].plot(np.nan,np.nan,"k-",fillstyle="none")(dfncc_p,)=ax[0,1].plot(np.nan,np.nan,"k:",fillstyle="none")# compute errorsdfn_var=dfn_var_fun(t=t_plot,z=z_plot)dfncc_var=dfncc_var_fun(t=t_plot,z=z_plot)error=np.abs(comsol_var-dfn_var)error_bar=np.abs(comsol_var-dfncc_var)# plot time averaged errorax[1,0].plot(z_plot*1e3,np.nanmean(error,axis=1),"k-",label=r"$1+1$D")ax[1,0].plot(z_plot*1e3,np.nanmean(error_bar,axis=1),"k:",label="DFNCC")# plot z averaged errorax[1,1].plot(t_plot,np.nanmean(error,axis=0),"k-",label=r"$1+1$D")ax[1,1].plot(t_plot,np.nanmean(error_bar,axis=0),"k:",label="DFNCC")# set ticksax[0,0].tick_params(which="both")ax[0,1].tick_params(which="both")ax[1,0].tick_params(which="both")ifvar_namein["$\mathcal{I}^*$"]:ax[1,0].set_yscale("log")ax[1,0].set_yticks=[1e-5,1e-4,1e-3,1e-2,1e-1,1e-2,1e-1,1]else:ax[1,0].ticklabel_format(style="sci",scilimits=(-2,2),axis="y")ax[1,1].tick_params(which="both")ifvar_namein["$\phi^*_{\mathrm{s,cn}}$","$\phi^*_{\mathrm{s,cp}} - V^*$"]:ax[1,0].ticklabel_format(style="sci",scilimits=(-2,2),axis="y")else:ax[1,1].set_yscale("log")ax[1,1].set_yticks=[1e-5,1e-4,1e-3,1e-2,1e-1,1e-2,1e-1,1]# set labelsax[0,0].set_xlabel(r"$z^*$ [mm]")ax[0,0].set_ylabel(r"$t^*$ [s]")ax[0,0].set_title(rf"{var_name}{units}",y=1.5)ax[0,1].set_xlabel(r"$z^*$ [mm]")ax[0,1].set_ylabel(rf"{var_name}")ax[1,0].set_xlabel(r"$z^*$ [mm]")ax[1,0].set_ylabel("Time-averaged"+"\n"+rf"absolute error {units}")ax[1,1].set_xlabel(r"$t^*$ [s]")ax[1,1].set_ylabel("Space-averaged"+"\n"+rf"absolute error {units}")ax[0,0].text(-0.1,1.6,"(a)",transform=ax[0,0].transAxes)ax[0,1].text(-0.1,1.6,"(b)",transform=ax[0,1].transAxes)ax[1,0].text(-0.1,1.2,"(c)",transform=ax[1,0].transAxes)ax[1,1].text(-0.1,1.2,"(d)",transform=ax[1,1].transAxes)leg1=ax[0,1].legend(bbox_to_anchor=(0,1.1,1.0,0.102),loc="lower left",borderaxespad=0.0,ncol=3,mode="expand",)ax[0,1].legend([comsol_p,pybamm_p,dfncc_p],["COMSOL",r"$1+1$D","DFNCC"],bbox_to_anchor=(0,1.5,1.0,0.102),loc="lower left",borderaxespad=0.0,ncol=3,mode="expand",)ax[0,1].add_artist(leg1)ax[1,0].legend(bbox_to_anchor=(0.0,1.1,1.0,0.102),loc="lower right",borderaxespad=0.0,ncol=3,)ax[1,1].legend(bbox_to_anchor=(0.0,1.1,1.0,0.102),loc="lower right",borderaxespad=0.0,ncol=3,)
We then set up the times and points in space to use in the plots
var="Current collector current density [A.m-2]"comsol_var_fun=comsol_solution[var]dfn_var_fun=solutions["1+1D DFN"][var]I_av=solutions["Average DFN"][var]defdfncc_var_fun(t,z):"In the DFNCC the current is just the average current"returnnp.transpose(np.repeat(I_av(t)[:,np.newaxis],len(z),axis=1))plot(t_plot,z_plot,t_slices,"$\mathcal{I}^*$","[A/m${}^2$]",comsol_var_fun,dfn_var_fun,dfncc_var_fun,param,cmap="plasma",)
and the temperature with respect to reference temperature
[18]:
T_ref=param.evaluate(dfn.param.T_ref)var="X-averaged cell temperature [K]"comsol_var=comsol_solution[var]defcomsol_var_fun(t,z):returncomsol_var(t=t,z=z)-T_refdfn_var=solutions["1+1D DFN"][var]defdfn_var_fun(t,z):returndfn_var(t=t,z=z)-T_refT_av=solutions["Average DFN"][var]defdfncc_var_fun(t,z):"In the DFNCC the temperature is just the average temperature"returnnp.transpose(np.repeat(T_av(t)[:,np.newaxis],len(z),axis=1))-T_refplot(t_plot,z_plot,t_slices,"$\\bar{T}^* - \\bar{T}_0^*$","[K]",comsol_var_fun,dfn_var_fun,dfncc_var_fun,param,cmap="inferno",)
We see that the electrical conductivity of the current collectors is sufficiently high that the potentials remain fairly uniform in space, and both the 1+1D DFN and DFNCC models are able to accurately capture the potential distribution in the current collectors.
In the plot of the current we see that positioning both tabs at the top of the cell means that for most of the simulation the current preferentially travels through the upper part of the cell. Eventually, as the cell continues to discharge, this part becomes more (de)lithiated until the resultant local increase in through-cell resistance is sufficient for it to become preferential for the current to travel further along the current collectors and through the lower part of the cell. This
behaviour is well captured by the 1+1D model. In the DFNCC formulation the through-cell current density is assumed uniform, so the greatest error is found at the ends of the current collectors where the current density deviates most from its average.
For the parameters used in this example we find that the temperature exhibits a relatively weak variation along the length of the current collectors.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[5] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[6] Robert Timms, Scott G Marquis, Valentin Sulzer, Colin P. Please, and S Jonathan Chapman. Asymptotic Reduction of a Lithium-ion Pouch Cell Model. SIAM Journal on Applied Mathematics, 81(3):765–788, 2021. doi:10.1137/20M1336898.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this notebook we use PyBaMM to generate rate capability plots and Ragone plots. Rate capability plots show discharge capacity at different C-rates. Similarly, multiplying the discharge current and capacity by the average discharge voltage we can obtain Ragon plots (that is energy vs power).
First we need to import pybamm and the other necessary packages.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpimportmatplotlib.pyplotasplt
Note: you may need to restart the kernel to use updated packages.
In this example we use the SPMe model with the default settings, but we could change it to other models, parameters, geometries… For example, you generate rate capability plots using the DFN model in this script.
[2]:
model=pybamm.lithium_ion.SPMe()
We now cycle a loop of different C-rates and run full discharge simulations for each C-rate. For each simulation we register the final discharge capacity, the applied current and the average discharge voltage. We can then generate the rate capability and the Ragone plots.
[3]:
C_rates=np.linspace(0.05,5,20)capacities=np.zeros_like(C_rates)currents=np.zeros_like(C_rates)voltage_av=np.zeros_like(C_rates)fori,C_rateinenumerate(C_rates):experiment=pybamm.Experiment([f"Discharge at {C_rate:.4f}C until 3.2V"],period=f"{10/C_rate:.4f} seconds")sim=pybamm.Simulation(model,experiment=experiment,solver=pybamm.CasadiSolver(dt_max=120))sim.solve()time=sim.solution["Time [s]"].entriescapacity=sim.solution["Discharge capacity [A.h]"]current=sim.solution["Current [A]"]voltage=sim.solution["Voltage [V]"]capacities[i]=capacity(time[-1])currents[i]=current(time[-1])voltage_av[i]=np.mean(voltage(time))plt.figure(1)plt.scatter(C_rates,capacities)plt.xlabel("C-rate")plt.ylabel("Capacity [Ah]")plt.figure(2)plt.scatter(currents*voltage_av,capacities*voltage_av)plt.xlabel("Power [W]")plt.ylabel("Energy [Wh]")plt.show()
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[4] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). ECSarXiv. February, 2020. doi:10.1149/osf.io/67ckj.
Tip
An interactive online version of this notebook is available, which can be
accessed via
Models which are discretised (i.e. ready to solve/ previously solved, see this notebook for more information on the pybamm.Discretisation class) can be serialised and saved to a JSON file, ready to be read in again either in PyBaMM, or a different modelling library.
In the example below, we build a basic DFN model, and then save the model out to sim_model_example.json, which should have appear in the ‘models’ directory.
[10]:
%pip install pybamm -q # install PyBaMM if it is not installed
importpybamm# do the exampledfn_model=pybamm.lithium_ion.DFN()dfn_sim=pybamm.Simulation(dfn_model)# discretise and build the modeldfn_sim.build()dfn_sim.save_model("sim_model_example")
Note: you may need to restart the kernel to use updated packages.
This model file can then be read in and solved by choosing a solver, and running as below.
[11]:
# Recreate the pybamm model from the JSON filenew_dfn_model=pybamm.load_model("sim_model_example.json")sim_reloaded=pybamm.Simulation(new_dfn_model)sim_reloaded.solve([0,3600])
[11]:
<pybamm.solvers.solution.Solution at 0x2a4e10550>
It would be nice to plot the results of the two models, to confirm that they are producing the same result.
However, notice that running the code below generates an error stating that the model variables were not provided during the reading in of the model.
---------------------------------------------------------------------------AttributeError Traceback (most recent call last)
/Users/pipliggins/Documents/repos/pybamm-local/docs/source/examples/notebooks/models/saving_models.ipynb Cell 7 line 8
<a href='vscode-notebook-cell:/Users/pipliggins/Documents/repos/pybamm-local/docs/source/examples/notebooks/models/saving_models.ipynb#W6sZmlsZQ%3D%3D?line=4'>5</a> plot_sim.solve([0, 3600])
<a href='vscode-notebook-cell:/Users/pipliggins/Documents/repos/pybamm-local/docs/source/examples/notebooks/models/saving_models.ipynb#W6sZmlsZQ%3D%3D?line=5'>6</a> sims.append(plot_sim)
----> <a href='vscode-notebook-cell:/Users/pipliggins/Documents/repos/pybamm-local/docs/source/examples/notebooks/models/saving_models.ipynb#W6sZmlsZQ%3D%3D?line=7'>8</a> pybamm.dynamic_plot(sims, time_unit="seconds")
File ~/Documents/repos/pybamm-local/pybamm/plotting/dynamic_plot.py:20, in dynamic_plot(*args, **kwargs) 8 """
9 Creates a :class:`pybamm.QuickPlot` object (with arguments 'args' and keyword
10 arguments 'kwargs') and then calls :meth:`pybamm.QuickPlot.dynamic_plot`.
(...) 17 The 'QuickPlot' object that was created
18 """
19 kwargs_for_class = {k: v for k, v in kwargs.items() if k != "testing"}
---> 20 plot = pybamm.QuickPlot(*args, **kwargs_for_class)
21 plot.dynamic_plot(kwargs.get("testing", False))
22 return plot
File ~/Documents/repos/pybamm-local/pybamm/plotting/quick_plot.py:146, in QuickPlot.__init__(self, solutions, output_variables, labels, colors, linestyles, shading, figsize, n_rows, time_unit, spatial_unit, variable_limits) 144 # check variables have been provided after any serialisation
145 if any(len(m.variables) == 0 for m in models):
--> 146 raise AttributeError("No variables to plot")
148 self.n_rows = n_rows or int(
149 len(output_variables) // np.sqrt(len(output_variables))
150 )
151 self.n_cols = int(np.ceil(len(output_variables) / self.n_rows))
AttributeError: No variables to plot
To be able to plot the results from a serialised model, the mesh and model variables need to be saved alongside the model itself.
To do this, set the variables option to True when saving the model as in the example below; notice how the models will now plot nicely.
[13]:
# using the first simulation, save a new file which includes a list of the model variablesdfn_sim.save_model("sim_model_variables",variables=True)# read the model back inmodel_with_vars=pybamm.load_model("sim_model_variables.json")# plot the pre- and post-serialisation models together to prove they behave the samemodels=[dfn_model,model_with_vars]sims=[]formodelinmodels:sim=pybamm.Simulation(model)sim.solve([0,3600])sims.append(sim)pybamm.dynamic_plot(sims,time_unit="seconds")
[13]:
<pybamm.plotting.quick_plot.QuickPlot at 0x111963010>
Alternatively, the model can be saved directly from the Model class.
Note that at the moment, only models derived from the BaseBatteryModel class can be serialised; those built from scratch using pybamm.BaseModel() are currently unsupported.
First set up the model, as explained in detail for the SPM.
[14]:
# create the modelspm_model=pybamm.lithium_ion.SPM()# set up and discretise ready to solvegeometry=spm_model.default_geometryparam=spm_model.default_parameter_valuesparam.process_model(spm_model)param.process_geometry(geometry)mesh=pybamm.Mesh(geometry,spm_model.default_submesh_types,spm_model.default_var_pts)disc=pybamm.Discretisation(mesh,spm_model.default_spatial_methods)disc.process_model(spm_model)
[14]:
<pybamm.models.full_battery_models.lithium_ion.spm.SPM at 0x29cf65c90>
Then save the model. Note that in this case the model variables and the mesh must be provided directly.
[15]:
# Serialise the spm_model, providing the varaibles and the meshspm_model.save_model("example_model",variables=spm_model.variables,mesh=mesh)
Now you can read the model back in, solve and plot.
[16]:
# read back innew_spm_model=pybamm.load_model("example_model.json")# select a solver and solvenew_spm_solver=new_spm_model.default_solvernew_spm_solution=new_spm_solver.solve(new_spm_model,[0,3600])# plot the solutionnew_spm_solution.plot()
[16]:
<pybamm.plotting.quick_plot.QuickPlot at 0x29c8a3d10>
As mentioned at the begining of this notebook, only models which have already been discretised can be serialised and readh back in. This means that after serialisation, the model cannot be edited, as the non-discretised elements of the model such as the original rhs are not saved.
If you are likely to want to save a model and then edit it in the future, you may wish to use the Simulation.save() functionality to pickle your simulation, as described in tutorial 6.
Before finishing we will remove the data files we saved so that we leave the directory as we found it
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[5] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[ ]:
Tip
An interactive online version of this notebook is available, which can be
accessed via
This notebook provides a short demonsration of how the SEI and particle mechanics submodels can be combined to simulate SEI growth on particle cracks.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportmatplotlib.pyplotasplt
[notice] A new release of pip available: 22.3.1 -> 23.0.1[notice] To update, run: pip install --upgrade pip
Note: you may need to restart the kernel to use updated packages.
Define two models. In model1, the only degradation mechanism is solvent-diffusion limited SEI growth. model2 includes the same SEI growth mechanism but also includes particle cracking and SEI growth on the cracks. The SEI model is run twice: once on the initial surface and once on the cracks. The equations for SEI on cracks are reported by O’Kane et al. [9] To ensure a fair experiment, particle swelling is enabled in both models.
[2]:
model1=pybamm.lithium_ion.DFN({"SEI":"solvent-diffusion limited","particle mechanics":"swelling only"})model2=pybamm.lithium_ion.DFN({"particle mechanics":"swelling and cracking","SEI":"solvent-diffusion limited","SEI on cracks":"true",})
Depending on the parameter set being used, the particle cracking model can require a large number of mesh points inside the particles to be numerically stable.
Solve the models with and without cracking. The steps before the 1C discharge make the model more numerically stable so fewer mesh points are required.
[4]:
exp=pybamm.Experiment(["Hold at 4.2 V until C/100","Rest for 1 hour","Discharge at 1C until 2.5 V"])sim1=pybamm.Simulation(model1,parameter_values=param,experiment=exp,var_pts=var_pts)sol1=sim1.solve(calc_esoh=False)sim2=pybamm.Simulation(model2,parameter_values=param,experiment=exp,var_pts=var_pts)sol2=sim2.solve(calc_esoh=False)
At t = 426.174, , mxstep steps taken before reaching tout.
At t = 186.174, , mxstep steps taken before reaching tout.
At t = 430.603, , mxstep steps taken before reaching tout.
At t = 190.603, , mxstep steps taken before reaching tout.
[5]:
t1=sol1["Time [s]"].entriesV1=sol1["Voltage [V]"].entriesSEI1=sol1["Loss of lithium to negative SEI [mol]"].entrieslithium_neg1=sol1["Total lithium in negative electrode [mol]"].entrieslithium_pos1=sol1["Total lithium in positive electrode [mol]"].entriest2=sol2["Time [s]"].entriesV2=sol2["Voltage [V]"].entriesSEI2=(sol2["Loss of lithium to negative SEI [mol]"].entries+sol2["Loss of lithium to negative SEI on cracks [mol]"].entries)lithium_neg2=sol2["Total lithium in negative electrode [mol]"].entrieslithium_pos2=sol2["Total lithium in positive electrode [mol]"].entries
[6]:
fig,(ax1,ax2)=plt.subplots(1,2,figsize=(18,4))ax1.plot(t1,V1,label="without cracking")ax1.plot(t2,V2,label="with cracking")ax1.set_xlabel("Time [s]")ax1.set_ylabel("Voltage [V]")ax1.legend()ax2.plot(t1,SEI1,label="without cracking")ax2.plot(t2,SEI2,label="with cracking")ax2.set_xlabel("Time [s]")ax2.set_ylabel("Loss of lithium to SEI [mol]")ax2.legend()plt.show()
The SEI on cracks consumes far more capacity than the SEI on the initial surface, in agreement with the literature. Finally, check lithium is conserved:
[7]:
fig,ax=plt.subplots()ax.plot(t2,lithium_neg2+lithium_pos2)ax.plot(t2,lithium_neg2[0]+lithium_pos2[0]-SEI2,linestyle="dashed")ax.set_xlabel("Time [s]")ax.set_ylabel("Total lithium in electrodes [mol]")plt.show()
[8]:
pybamm.print_citations()
[1] Weilong Ai, Ludwig Kraft, Johannes Sturm, Andreas Jossen, and Billy Wu. Electrochemical thermal-mechanical modelling of stress inhomogeneity in lithium-ion pouch cells. Journal of The Electrochemical Society, 167(1):013512, 2019. doi:10.1149/2.0122001JES.
[2] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[3] Chang-Hui Chen, Ferran Brosa Planella, Kieran O'Regan, Dominika Gastol, W. Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.
[4] Rutooj Deshpande, Mark Verbrugge, Yang-Tse Cheng, John Wang, and Ping Liu. Battery cycle life prediction with coupled chemical degradation and fatigue mechanics. Journal of the Electrochemical Society, 159(10):A1730, 2012. doi:10.1149/2.049210jes.
[5] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[6] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[7] Scott G. Marquis. Long-term degradation of lithium-ion batteries. PhD thesis, University of Oxford, 2020.
[8] Simon E. J. O'Kane, Ian D. Campbell, Mohamed W. J. Marzook, Gregory J. Offer, and Monica Marinescu. Physical origin of the differential voltage minimum associated with lithium plating in li-ion batteries. Journal of The Electrochemical Society, 167(9):090540, may 2020. URL: https://doi.org/10.1149/1945-7111/ab90ac, doi:10.1149/1945-7111/ab90ac.
[9] Simon E. J. O'Kane, Weilong Ai, Ganesh Madabattula, Diego Alonso-Alvarez, Robert Timms, Valentin Sulzer, Jacqueline Sophie Edge, Billy Wu, Gregory J. Offer, and Monica Marinescu. Lithium-ion battery degradation: how to model it. Phys. Chem. Chem. Phys., 24:7909-7922, 2022. URL: http://dx.doi.org/10.1039/D2CP00417H, doi:10.1039/D2CP00417H.
[10] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this notebook we show how to insert a reference electrode to mimic a 3E cell.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybamm
Note: you may need to restart the kernel to use updated packages.
We first load a model
[2]:
model=pybamm.lithium_ion.DFN()
Next we use the helper function insert_reference_electrode to insert a reference electrode into the model. This function takes the position of the reference electrode as an optional argument. If no position is given, the reference electrode is inserted at the midpoint of the separator. The helper function adds the new variables “Reference electrode potential [V]”, “Negative electrode 3E potential [V]” and “Positive electrode 3E potential [V]” to the model.
In this example we will explicitly pass a position to show how it is done
Next we can set up a simulation and solve the model as usual
[4]:
sim=pybamm.Simulation(model)sim.solve([0,3600])
[4]:
<pybamm.solvers.solution.Solution at 0x169dc5950>
Let’s plot a comparison of the 3E potentials and the potential difference between the solid and electrolyte phases at the electrode/separator interfaces
[5]:
sim.plot([["Negative electrode surface potential difference at separator interface [V]","Negative electrode 3E potential [V]",],["Positive electrode surface potential difference at separator interface [V]","Positive electrode 3E potential [V]",],"Voltage [V]",])
[5]:
<pybamm.plotting.quick_plot.QuickPlot at 0x169816a90>
[6]:
pybamm.print_citations()
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[5] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[ ]:
Tip
An interactive online version of this notebook is available, which can be
accessed via
Run simulations with O’Regan 2022 parameter set (LG M50)#
In this notebook we show an example on how to run the DFN model with the O’Regan 2022 parameter set for the LG M50 cell. Because of the concentration dependent diffusion coefficient, we need to customise the mesh so the simulations converge.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybamm
Note: you may need to restart the kernel to use updated packages.
We first initialise the model and define the parameter set and the solver:
[2]:
# DFN + lumped thermaloptions={"thermal":"lumped","dimensionality":0,"cell geometry":"arbitrary"}model=pybamm.lithium_ion.DFN(options=options)# O'Regan 2022 parameter setparam=pybamm.ParameterValues("ORegan2022")# Choose CasADI fast (we do a short discharge so there are no events, if events are needed choose "fast with events")solver=pybamm.CasadiSolver(mode="fast")
Now we define the new mesh. Because the particle diffusivity depends on concentration and get drop very abruptly, we set a fine mesh (40 points in each particle). Additionally, given that the larger gradients occur towards the particle surface, we choose an exponential mesh weighted towards the right (i.e. the particle surface). This generates a non-uniform mesh with a finer grid towards the particle surface and a coarser grid towards the particle centre.
[3]:
var_pts={"x_n":30,"x_s":30,"x_p":30,"r_n":40,"r_p":40}submesh_types=model.default_submesh_typessubmesh_types["negative particle"]=pybamm.MeshGenerator(pybamm.Exponential1DSubMesh,submesh_params={"side":"right"})submesh_types["positive particle"]=pybamm.MeshGenerator(pybamm.Exponential1DSubMesh,submesh_params={"side":"right"})# Define the simulationsim=pybamm.Simulation(model,parameter_values=param,C_rate=1,solver=solver,var_pts=var_pts,submesh_types=submesh_types,)
Finally, we can solve the simulation and plot the results. Note that because the nonlinear diffusion and the fine mesh, if we want to solve for longer time, such as full discharge or other experiments, the simulations might take a few minutes to run.
[4]:
sim.solve([0,10])# solving time kept short for testing purposes, feel free to extend itsim.plot()
[4]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7fe0c1824eb0>
[5]:
pybamm.print_citations()
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Chang-Hui Chen, Ferran Brosa Planella, Kieran O'Regan, Dominika Gastol, W. Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.
[3] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[4] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[5] Kieran O'Regan, Ferran Brosa Planella, W. Dhammika Widanage, and Emma Kendrick. Thermal-electrochemical parametrisation of a lithium-ion battery: mapping Li concentration and temperature dependencies. Journal of The Electrochemical Society, ():, 2021. doi:.
[6] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[7] Robert Timms, Scott G Marquis, Valentin Sulzer, Colin P. Please, and S Jonathan Chapman. Asymptotic Reduction of a Lithium-ion Pouch Cell Model. SIAM Journal on Applied Mathematics, 81(3):765–788, 2021. doi:10.1137/20M1336898.
Tip
An interactive online version of this notebook is available, which can be
accessed via
The SPM consists of two spherically symmetric diffusion equations: one within a representative negative particle (\(\text{k}=\text{n}\)) and one within a representative positive particle (\(\text{k}=\text{p}\)). In the centre of the particle the standard no-flux condition is imposed. Since the SPM assumes that all particles in an electrode behave in exactly the same way, the flux on the surface of a particle is simply the current \(I\) divided by the thickness of the electrode
\(L_{\text{k}}\). The concentration of lithium in electrode \(\text{k}\) is denoted \(c_{\text{k}}\) and the current is denoted by \(I\). The model equations for the SPM are then:
where \(D_{\text{s,k}}\) is the diffusion coefficient in the solid, \(N_{\text{s,k}}\) denotes the flux of lithium ions in the solid particle within the region \(\text{k}\), and \(r_{\text{k}} \in[0,1]\) is the radial coordinate of the particle in electrode \(\text{k}\).
Below we show how to solve the Single Particle Model, using the default geometry, mesh, parameters, discretisation and solver provided with PyBaMM. In this notebook we explicitly handle all the stages of setting up, processing and solving the model in order to explain them in detail. However, it is often simpler in practice to use the Simulation class, which handles many of the stages automatically, as shown here.
First we need to import pybamm, and then change our working directory to the root of the pybamm folder.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpimportosimportmatplotlib.pyplotaspltos.chdir(pybamm.__path__[0]+"/..")
Note: you may need to restart the kernel to use updated packages.
We then create an instance of the SPM:
[2]:
model=pybamm.lithium_ion.SPM()
The model object is a subtype of `pybamm.BaseModel <https://docs.pybamm.org/en/latest/source/api/models/base_models/base_model.html>`__, and contains all the equations that define this particular model. For example, the rhs dict contained in model has a dictionary mapping variables such as \(c_n\) to the equation representing its rate of change with time (i.e. \(\partial{c_n}/\partial{t}\)). We can see this explicitly by visualising this entry in the rhs dict:
[3]:
variable=list(model.rhs.keys())[1]equation=list(model.rhs.values())[1]print("rhs equation for variable '",variable,"' is:")path="docs/source/examples/notebooks/models/"equation.visualise(path+"spm1.png")
rhs equation for variable ' Throughput capacity [A.h] ' is:
We need a geometry in which to define our model equations. In pybamm this is represented by the `pybamm.Geometry <https://docs.pybamm.org/en/latest/source/api/geometry/index.html>`__ class. In this case we use the default geometry object defined by the model
[4]:
geometry=model.default_geometry
This geometry object defines a number of domains, each with its own name, spatial variables and min/max limits (the latter are represented as equations similar to the rhs equation shown above). For instance, the SPM has the following domains:
Both the model equations and the geometry include parameters, such as \(L_p\). We can substitute these symbolic parameters in the model with values by using the `pybamm.ParameterValues <https://docs.pybamm.org/en/latest/source/api/parameters/parameter_values.html>`__ class, which takes either a python dictionary or CSV file with the mapping between parameter names and values. Rather than create our own instance of pybamm.ParameterValues, we will use the default parameter set included
in the model
[6]:
param=model.default_parameter_values
We can then apply this parameter set to the model and geometry
The next step is to mesh the input geometry. We can do this using the `pybamm.Mesh <https://docs.pybamm.org/en/latest/source/api/meshes/index.html>`__ class. This class takes in the geometry of the problem, and also two dictionaries containing the type of mesh to use within each domain of the geometry (i.e. within the positive or negative electrode domains), and the number of mesh points.
The default mesh types and the default number of points to use in each variable for the SPM are:
[8]:
fork,tinmodel.default_submesh_types.items():print(k,"is of type",t.__name__)forvar,nptsinmodel.default_var_pts.items():print(var,"has",npts,"mesh points")
negative electrode is of type Uniform1DSubMesh
separator is of type Uniform1DSubMesh
positive electrode is of type Uniform1DSubMesh
negative particle is of type Uniform1DSubMesh
positive particle is of type Uniform1DSubMesh
negative primary particle is of type Uniform1DSubMesh
positive primary particle is of type Uniform1DSubMesh
negative secondary particle is of type Uniform1DSubMesh
positive secondary particle is of type Uniform1DSubMesh
negative particle size is of type Uniform1DSubMesh
positive particle size is of type Uniform1DSubMesh
current collector is of type SubMesh0D
x_n has 20 mesh points
x_s has 20 mesh points
x_p has 20 mesh points
r_n has 20 mesh points
r_p has 20 mesh points
r_n_prim has 20 mesh points
r_p_prim has 20 mesh points
r_n_sec has 20 mesh points
r_p_sec has 20 mesh points
y has 10 mesh points
z has 10 mesh points
R_n has 30 mesh points
R_p has 30 mesh points
With these defaults, we can then create our mesh of the given geometry:
The next step is to discretise the model equations using this mesh. We do this using the `pybamm.Discretisation <https://docs.pybamm.org/en/latest/source/api/spatial_methods/discretisation.html>`__ class, which takes both the mesh we have already created, and a dictionary of spatial methods to use for each geometry domain. For the case of the SPM, we use the following defaults for the spatial discretisation methods:
macroscale is discretised using FiniteVolume method
negative particle is discretised using FiniteVolume method
positive particle is discretised using FiniteVolume method
negative primary particle is discretised using FiniteVolume method
positive primary particle is discretised using FiniteVolume method
negative secondary particle is discretised using FiniteVolume method
positive secondary particle is discretised using FiniteVolume method
negative particle size is discretised using FiniteVolume method
positive particle size is discretised using FiniteVolume method
current collector is discretised using ZeroDimensionalSpatialMethod method
We then create the pybamm.Discretisation object, and use this to discretise the model equations
<pybamm.models.full_battery_models.lithium_ion.spm.SPM at 0x17eb5f790>
After this stage, all of the variables in model have been discretised into pybamm.StateVector objects, and spatial operators have been replaced by matrix-vector multiplications, ready to be evaluated within a time-stepping algorithm of a given solver. For example, the rhs expression for \(\partial{c_n}/\partial{t}\) that we visualised above is now represented by:
Now we are ready to run the time-stepping routine to solve the model. Once again we use the default ODE solver.
[13]:
# Solve the model at the given time points (in seconds)solver=model.default_solvern=250t_eval=np.linspace(0,3600,n)print("Solving using",type(solver).__name__,"solver...")solution=solver.solve(model,t_eval)print("Finished.")
Solving using CasadiSolver solver...
Finished.
Each model in pybamm has a list of relevant variables defined in the model, for use in visualising the model solution or for comparison with other models. The SPM defines the following variables:
[14]:
print("SPM model variables:")forvinmodel.variables.keys():print("\t-",v)
SPM model variables:
- Time [s]
- Time [min]
- Time [h]
- x [m]
- x_n [m]
- x_s [m]
- x_p [m]
- r_n [m]
- r_p [m]
- Current variable [A]
- Total current density [A.m-2]
- Current [A]
- C-rate
- Discharge capacity [A.h]
- Throughput capacity [A.h]
- Discharge energy [W.h]
- Throughput energy [W.h]
- Porosity
- Negative electrode porosity
- X-averaged negative electrode porosity
- Separator porosity
- X-averaged separator porosity
- Positive electrode porosity
- X-averaged positive electrode porosity
- Porosity change
- Negative electrode porosity change [s-1]
- X-averaged negative electrode porosity change [s-1]
- Separator porosity change [s-1]
- X-averaged separator porosity change [s-1]
- Positive electrode porosity change [s-1]
- X-averaged positive electrode porosity change [s-1]
- Negative electrode interface utilisation variable
- X-averaged negative electrode interface utilisation variable
- Negative electrode interface utilisation
- X-averaged negative electrode interface utilisation
- Positive electrode interface utilisation variable
- X-averaged positive electrode interface utilisation variable
- Positive electrode interface utilisation
- X-averaged positive electrode interface utilisation
- Negative particle crack length [m]
- X-averaged negative particle crack length [m]
- Negative particle cracking rate [m.s-1]
- X-averaged negative particle cracking rate [m.s-1]
- Positive particle crack length [m]
- X-averaged positive particle crack length [m]
- Positive particle cracking rate [m.s-1]
- X-averaged positive particle cracking rate [m.s-1]
- Negative electrode active material volume fraction
- X-averaged negative electrode active material volume fraction
- Negative electrode capacity [A.h]
- Negative particle radius
- Negative particle radius [m]
- X-averaged negative particle radius [m]
- Negative electrode surface area to volume ratio [m-1]
- X-averaged negative electrode surface area to volume ratio [m-1]
- Negative electrode active material volume fraction change [s-1]
- X-averaged negative electrode active material volume fraction change [s-1]
- Loss of lithium due to loss of active material in negative electrode [mol]
- Positive electrode active material volume fraction
- X-averaged positive electrode active material volume fraction
- Positive electrode capacity [A.h]
- Positive particle radius
- Positive particle radius [m]
- X-averaged positive particle radius [m]
- Positive electrode surface area to volume ratio [m-1]
- X-averaged positive electrode surface area to volume ratio [m-1]
- Positive electrode active material volume fraction change [s-1]
- X-averaged positive electrode active material volume fraction change [s-1]
- Loss of lithium due to loss of active material in positive electrode [mol]
- Separator pressure [Pa]
- X-averaged separator pressure [Pa]
- negative electrode transverse volume-averaged velocity [m.s-1]
- X-averaged negative electrode transverse volume-averaged velocity [m.s-1]
- separator transverse volume-averaged velocity [m.s-1]
- X-averaged separator transverse volume-averaged velocity [m.s-1]
- positive electrode transverse volume-averaged velocity [m.s-1]
- X-averaged positive electrode transverse volume-averaged velocity [m.s-1]
- Transverse volume-averaged velocity [m.s-1]
- negative electrode transverse volume-averaged acceleration [m.s-2]
- X-averaged negative electrode transverse volume-averaged acceleration [m.s-2]
- separator transverse volume-averaged acceleration [m.s-2]
- X-averaged separator transverse volume-averaged acceleration [m.s-2]
- positive electrode transverse volume-averaged acceleration [m.s-2]
- X-averaged positive electrode transverse volume-averaged acceleration [m.s-2]
- Transverse volume-averaged acceleration [m.s-2]
- Negative electrode volume-averaged velocity [m.s-1]
- Negative electrode volume-averaged acceleration [m.s-2]
- X-averaged negative electrode volume-averaged acceleration [m.s-2]
- Negative electrode pressure [Pa]
- X-averaged negative electrode pressure [Pa]
- Positive electrode volume-averaged velocity [m.s-1]
- Positive electrode volume-averaged acceleration [m.s-2]
- X-averaged positive electrode volume-averaged acceleration [m.s-2]
- Positive electrode pressure [Pa]
- X-averaged positive electrode pressure [Pa]
- Negative particle stoichiometry
- Negative particle concentration
- Negative particle concentration [mol.m-3]
- X-averaged negative particle concentration
- X-averaged negative particle concentration [mol.m-3]
- R-averaged negative particle concentration
- R-averaged negative particle concentration [mol.m-3]
- Average negative particle concentration
- Average negative particle concentration [mol.m-3]
- Negative particle surface stoichiometry
- Negative particle surface concentration
- Negative particle surface concentration [mol.m-3]
- X-averaged negative particle surface concentration
- X-averaged negative particle surface concentration [mol.m-3]
- Negative electrode extent of lithiation
- X-averaged negative electrode extent of lithiation
- Minimum negative particle concentration
- Maximum negative particle concentration
- Minimum negative particle concentration [mol.m-3]
- Maximum negative particle concentration [mol.m-3]
- Minimum negative particle surface concentration
- Maximum negative particle surface concentration
- Minimum negative particle surface concentration [mol.m-3]
- Maximum negative particle surface concentration [mol.m-3]
- Positive particle stoichiometry
- Positive particle concentration
- Positive particle concentration [mol.m-3]
- X-averaged positive particle concentration
- X-averaged positive particle concentration [mol.m-3]
- R-averaged positive particle concentration
- R-averaged positive particle concentration [mol.m-3]
- Average positive particle concentration
- Average positive particle concentration [mol.m-3]
- Positive particle surface stoichiometry
- Positive particle surface concentration
- Positive particle surface concentration [mol.m-3]
- X-averaged positive particle surface concentration
- X-averaged positive particle surface concentration [mol.m-3]
- Positive electrode extent of lithiation
- X-averaged positive electrode extent of lithiation
- Minimum positive particle concentration
- Maximum positive particle concentration
- Minimum positive particle concentration [mol.m-3]
- Maximum positive particle concentration [mol.m-3]
- Minimum positive particle surface concentration
- Maximum positive particle surface concentration
- Minimum positive particle surface concentration [mol.m-3]
- Maximum positive particle surface concentration [mol.m-3]
- Porosity times concentration [mol.m-3]
- Negative electrode porosity times concentration [mol.m-3]
- Separator porosity times concentration [mol.m-3]
- Positive electrode porosity times concentration [mol.m-3]
- Total lithium in electrolyte [mol]
- Electrolyte flux [mol.m-2.s-1]
- Ambient temperature [K]
- Cell temperature [K]
- Negative current collector temperature [K]
- Positive current collector temperature [K]
- X-averaged cell temperature [K]
- Volume-averaged cell temperature [K]
- Negative electrode temperature [K]
- X-averaged negative electrode temperature [K]
- Separator temperature [K]
- X-averaged separator temperature [K]
- Positive electrode temperature [K]
- X-averaged positive electrode temperature [K]
- Ambient temperature [C]
- Cell temperature [C]
- Negative current collector temperature [C]
- Positive current collector temperature [C]
- X-averaged cell temperature [C]
- Volume-averaged cell temperature [C]
- Negative electrode temperature [C]
- X-averaged negative electrode temperature [C]
- Separator temperature [C]
- X-averaged separator temperature [C]
- Positive electrode temperature [C]
- X-averaged positive electrode temperature [C]
- Negative current collector potential [V]
- Inner SEI thickness [m]
- Outer SEI thickness [m]
- X-averaged inner SEI thickness [m]
- X-averaged outer SEI thickness [m]
- SEI [m]
- Total SEI thickness [m]
- X-averaged SEI thickness [m]
- X-averaged total SEI thickness [m]
- X-averaged negative electrode resistance [Ohm.m2]
- Inner SEI interfacial current density [A.m-2]
- X-averaged inner SEI interfacial current density [A.m-2]
- Outer SEI interfacial current density [A.m-2]
- X-averaged outer SEI interfacial current density [A.m-2]
- SEI interfacial current density [A.m-2]
- X-averaged SEI interfacial current density [A.m-2]
- Inner SEI on cracks thickness [m]
- Outer SEI on cracks thickness [m]
- X-averaged inner SEI on cracks thickness [m]
- X-averaged outer SEI on cracks thickness [m]
- SEI on cracks [m]
- Total SEI on cracks thickness [m]
- X-averaged SEI on cracks thickness [m]
- X-averaged total SEI on cracks thickness [m]
- Inner SEI on cracks interfacial current density [A.m-2]
- X-averaged inner SEI on cracks interfacial current density [A.m-2]
- Outer SEI on cracks interfacial current density [A.m-2]
- X-averaged outer SEI on cracks interfacial current density [A.m-2]
- SEI on cracks interfacial current density [A.m-2]
- X-averaged SEI on cracks interfacial current density [A.m-2]
- Lithium plating concentration [mol.m-3]
- X-averaged lithium plating concentration [mol.m-3]
- Dead lithium concentration [mol.m-3]
- X-averaged dead lithium concentration [mol.m-3]
- Lithium plating thickness [m]
- X-averaged lithium plating thickness [m]
- Dead lithium thickness [m]
- X-averaged dead lithium thickness [m]
- Loss of lithium to lithium plating [mol]
- Loss of capacity to lithium plating [A.h]
- Negative electrode lithium plating reaction overpotential [V]
- X-averaged negative electrode lithium plating reaction overpotential [V]
- Lithium plating interfacial current density [A.m-2]
- X-averaged lithium plating interfacial current density [A.m-2]
- Negative crack surface to volume ratio [m-1]
- Negative electrode roughness ratio
- X-averaged negative electrode roughness ratio
- Positive crack surface to volume ratio [m-1]
- Positive electrode roughness ratio
- X-averaged positive electrode roughness ratio
- Electrolyte transport efficiency
- Negative electrolyte transport efficiency
- X-averaged negative electrolyte transport efficiency
- Separator electrolyte transport efficiency
- X-averaged separator electrolyte transport efficiency
- Positive electrolyte transport efficiency
- X-averaged positive electrolyte transport efficiency
- Electrode transport efficiency
- Negative electrode transport efficiency
- X-averaged negative electrode transport efficiency
- Separator electrode transport efficiency
- X-averaged separator electrode transport efficiency
- Positive electrode transport efficiency
- X-averaged positive electrode transport efficiency
- Separator volume-averaged velocity [m.s-1]
- Separator volume-averaged acceleration [m.s-2]
- X-averaged separator volume-averaged acceleration [m.s-2]
- Volume-averaged velocity [m.s-1]
- Volume-averaged acceleration [m.s-1]
- X-averaged volume-averaged acceleration [m.s-1]
- Pressure [Pa]
- Negative electrode stoichiometry
- Negative electrode volume-averaged concentration
- Negative electrode volume-averaged concentration [mol.m-3]
- Total lithium in primary phase in negative electrode [mol]
- Positive electrode stoichiometry
- Positive electrode volume-averaged concentration
- Positive electrode volume-averaged concentration [mol.m-3]
- Total lithium in primary phase in positive electrode [mol]
- Electrolyte concentration concatenation [mol.m-3]
- Negative electrolyte concentration [mol.m-3]
- X-averaged negative electrolyte concentration [mol.m-3]
- Separator electrolyte concentration [mol.m-3]
- X-averaged separator electrolyte concentration [mol.m-3]
- Positive electrolyte concentration [mol.m-3]
- X-averaged positive electrolyte concentration [mol.m-3]
- Negative electrolyte concentration [Molar]
- X-averaged negative electrolyte concentration [Molar]
- Separator electrolyte concentration [Molar]
- X-averaged separator electrolyte concentration [Molar]
- Positive electrolyte concentration [Molar]
- X-averaged positive electrolyte concentration [Molar]
- Electrolyte concentration [mol.m-3]
- X-averaged electrolyte concentration [mol.m-3]
- Electrolyte concentration [Molar]
- X-averaged electrolyte concentration [Molar]
- Ohmic heating [W.m-3]
- X-averaged Ohmic heating [W.m-3]
- Volume-averaged Ohmic heating [W.m-3]
- Irreversible electrochemical heating [W.m-3]
- X-averaged irreversible electrochemical heating [W.m-3]
- Volume-averaged irreversible electrochemical heating [W.m-3]
- Reversible heating [W.m-3]
- X-averaged reversible heating [W.m-3]
- Volume-averaged reversible heating [W.m-3]
- Total heating [W.m-3]
- X-averaged total heating [W.m-3]
- Volume-averaged total heating [W.m-3]
- Current collector current density [A.m-2]
- Inner SEI concentration [mol.m-3]
- X-averaged inner SEI concentration [mol.m-3]
- Outer SEI concentration [mol.m-3]
- X-averaged outer SEI concentration [mol.m-3]
- SEI concentration [mol.m-3]
- X-averaged SEI concentration [mol.m-3]
- Loss of lithium to SEI [mol]
- Loss of capacity to SEI [A.h]
- X-averaged negative electrode SEI interfacial current density [A.m-2]
- Negative electrode SEI interfacial current density [A.m-2]
- Positive electrode SEI interfacial current density [A.m-2]
- X-averaged positive electrode SEI volumetric interfacial current density [A.m-2]
- Positive electrode SEI volumetric interfacial current density [A.m-3]
- Negative electrode SEI volumetric interfacial current density [A.m-3]
- X-averaged negative electrode SEI volumetric interfacial current density [A.m-3]
- Inner SEI on cracks concentration [mol.m-3]
- X-averaged inner SEI on cracks concentration [mol.m-3]
- Outer SEI on cracks concentration [mol.m-3]
- X-averaged outer SEI on cracks concentration [mol.m-3]
- SEI on cracks concentration [mol.m-3]
- X-averaged SEI on cracks concentration [mol.m-3]
- Loss of lithium to SEI on cracks [mol]
- Loss of capacity to SEI on cracks [A.h]
- X-averaged negative electrode SEI on cracks interfacial current density [A.m-2]
- Negative electrode SEI on cracks interfacial current density [A.m-2]
- Positive electrode SEI on cracks interfacial current density [A.m-2]
- X-averaged positive electrode SEI on cracks volumetric interfacial current density [A.m-2]
- Positive electrode SEI on cracks volumetric interfacial current density [A.m-3]
- Negative electrode SEI on cracks volumetric interfacial current density [A.m-3]
- X-averaged negative electrode SEI on cracks volumetric interfacial current density [A.m-3]
- Negative electrode lithium plating interfacial current density [A.m-2]
- X-averaged negative electrode lithium plating interfacial current density [A.m-2]
- Lithium plating volumetric interfacial current density [A.m-3]
- X-averaged lithium plating volumetric interfacial current density [A.m-3]
- X-averaged positive electrode lithium plating interfacial current density [A.m-2]
- X-averaged positive electrode lithium plating volumetric interfacial current density [A.m-3]
- Positive electrode lithium plating interfacial current density [A.m-2]
- Positive electrode lithium plating volumetric interfacial current density [A.m-3]
- Negative electrode lithium plating volumetric interfacial current density [A.m-3]
- X-averaged negative electrode lithium plating volumetric interfacial current density [A.m-3]
- Negative electrode open-circuit potential [V]
- X-averaged negative electrode open-circuit potential [V]
- Negative electrode bulk open-circuit potential [V]
- Negative particle concentration overpotential [V]
- Negative electrode entropic change [V.K-1]
- X-averaged negative electrode entropic change [V.K-1]
- Positive electrode open-circuit potential [V]
- X-averaged positive electrode open-circuit potential [V]
- Positive electrode bulk open-circuit potential [V]
- Positive particle concentration overpotential [V]
- Positive electrode entropic change [V.K-1]
- X-averaged positive electrode entropic change [V.K-1]
- X-averaged negative electrode total interfacial current density [A.m-2]
- X-averaged negative electrode total volumetric interfacial current density [A.m-3]
- SEI film overpotential [V]
- X-averaged SEI film overpotential [V]
- Negative electrode exchange current density [A.m-2]
- X-averaged negative electrode exchange current density [A.m-2]
- Negative electrode reaction overpotential [V]
- X-averaged negative electrode reaction overpotential [V]
- X-averaged negative electrode surface potential difference [V]
- Negative electrode interfacial current density [A.m-2]
- X-averaged negative electrode interfacial current density [A.m-2]
- Negative electrode volumetric interfacial current density [A.m-3]
- X-averaged negative electrode volumetric interfacial current density [A.m-3]
- X-averaged positive electrode total interfacial current density [A.m-2]
- X-averaged positive electrode total volumetric interfacial current density [A.m-3]
- Positive electrode exchange current density [A.m-2]
- X-averaged positive electrode exchange current density [A.m-2]
- Positive electrode reaction overpotential [V]
- X-averaged positive electrode reaction overpotential [V]
- X-averaged positive electrode surface potential difference [V]
- Positive electrode interfacial current density [A.m-2]
- X-averaged positive electrode interfacial current density [A.m-2]
- Positive electrode volumetric interfacial current density [A.m-3]
- X-averaged positive electrode volumetric interfacial current density [A.m-3]
- Negative particle rhs [mol.m-3.s-1]
- Negative particle bc [mol.m-2]
- Negative particle effective diffusivity [m2.s-1]
- X-averaged negative particle effective diffusivity [m2.s-1]
- Negative particle flux [mol.m-2.s-1]
- X-averaged negative particle flux [mol.m-2.s-1]
- Positive particle rhs [mol.m-3.s-1]
- Positive particle bc [mol.m-2]
- Positive particle effective diffusivity [m2.s-1]
- X-averaged positive particle effective diffusivity [m2.s-1]
- Positive particle flux [mol.m-2.s-1]
- X-averaged positive particle flux [mol.m-2.s-1]
- Negative electrode potential [V]
- X-averaged negative electrode potential [V]
- Negative electrode ohmic losses [V]
- X-averaged negative electrode ohmic losses [V]
- Gradient of negative electrode potential [V.m-1]
- Negative electrode current density [A.m-2]
- Electrolyte potential [V]
- X-averaged electrolyte potential [V]
- X-averaged electrolyte overpotential [V]
- Gradient of electrolyte potential [V.m-1]
- Negative electrolyte potential [V]
- X-averaged negative electrolyte potential [V]
- Gradient of negative electrolyte potential [V.m-1]
- Separator electrolyte potential [V]
- X-averaged separator electrolyte potential [V]
- Gradient of separator electrolyte potential [V.m-1]
- Positive electrolyte potential [V]
- X-averaged positive electrolyte potential [V]
- Gradient of positive electrolyte potential [V.m-1]
- Electrolyte current density [A.m-2]
- Negative electrolyte current density [A.m-2]
- Positive electrolyte current density [A.m-2]
- X-averaged concentration overpotential [V]
- X-averaged electrolyte ohmic losses [V]
- Negative electrode surface potential difference [V]
- Negative electrode surface potential difference at separator interface [V]
- Sum of negative electrode electrolyte reaction source terms [A.m-3]
- Sum of x-averaged negative electrode electrolyte reaction source terms [A.m-3]
- Sum of negative electrode volumetric interfacial current densities [A.m-3]
- Sum of x-averaged negative electrode volumetric interfacial current densities [A.m-3]
- Sum of positive electrode electrolyte reaction source terms [A.m-3]
- Sum of x-averaged positive electrode electrolyte reaction source terms [A.m-3]
- Sum of positive electrode volumetric interfacial current densities [A.m-3]
- Sum of x-averaged positive electrode volumetric interfacial current densities [A.m-3]
- Interfacial current density [A.m-2]
- Exchange current density [A.m-2]
- Sum of volumetric interfacial current densities [A.m-3]
- Sum of electrolyte reaction source terms [A.m-3]
- Positive electrode potential [V]
- X-averaged positive electrode potential [V]
- Positive electrode ohmic losses [V]
- X-averaged positive electrode ohmic losses [V]
- Gradient of positive electrode potential [V.m-1]
- Positive electrode current density [A.m-2]
- Electrode current density [A.m-2]
- Positive current collector potential [V]
- Local voltage [V]
- Terminal voltage [V]
- Voltage [V]
- Contact overpotential [V]
- Positive electrode surface potential difference [V]
- Positive electrode surface potential difference at separator interface [V]
- Surface open-circuit voltage [V]
- Bulk open-circuit voltage [V]
- Particle concentration overpotential [V]
- X-averaged reaction overpotential [V]
- X-averaged solid phase ohmic losses [V]
- Battery open-circuit voltage [V]
- Battery negative electrode bulk open-circuit potential [V]
- Battery positive electrode bulk open-circuit potential [V]
- Battery particle concentration overpotential [V]
- Battery negative particle concentration overpotential [V]
- Battery positive particle concentration overpotential [V]
- X-averaged battery reaction overpotential [V]
- X-averaged battery negative reaction overpotential [V]
- X-averaged battery positive reaction overpotential [V]
- X-averaged battery solid phase ohmic losses [V]
- X-averaged battery negative solid phase ohmic losses [V]
- X-averaged battery positive solid phase ohmic losses [V]
- X-averaged battery electrolyte ohmic losses [V]
- X-averaged battery concentration overpotential [V]
- Battery voltage [V]
- Change in open-circuit voltage [V]
- Local ECM resistance [Ohm]
- Terminal power [W]
- Power [W]
- Resistance [Ohm]
- Total lithium in negative electrode [mol]
- LAM_ne [%]
- Loss of active material in negative electrode [%]
- Total lithium in positive electrode [mol]
- LAM_pe [%]
- Loss of active material in positive electrode [%]
- LLI [%]
- Loss of lithium inventory [%]
- Loss of lithium inventory, including electrolyte [%]
- Total lithium [mol]
- Total lithium in particles [mol]
- Total lithium capacity [A.h]
- Total lithium capacity in particles [A.h]
- Total lithium lost [mol]
- Total lithium lost from particles [mol]
- Total lithium lost from electrolyte [mol]
- Total lithium lost to side reactions [mol]
- Total capacity lost to side reactions [A.h]
To help visualise the results, pybamm provides the pybamm.ProcessedVariable class, which takes the output of a solver and a variable, and allows the user to evaluate the value of that variable at any given time or \(x\) value. These processed variables are automatically created by the solution dictionary.
One we have these variables in hand, we can begin generating plots using a library such as Matplotlib. Below we plot the voltage and surface particle concentrations versus time
[16]:
t=solution["Time [s]"].entriesx=solution["x [m]"].entries[:,0]f,(ax1,ax2,ax3)=plt.subplots(1,3,figsize=(13,4))ax1.plot(t,voltage(t))ax1.set_xlabel(r"$Time [s]$")ax1.set_ylabel("Voltage [V]")ax2.plot(t,c_s_n_surf(t=t,x=x[0]))# can evaluate at arbitrary x (single representative particle)ax2.set_xlabel(r"$Time [s]$")ax2.set_ylabel("Negative particle surface concentration")ax3.plot(t,c_s_p_surf(t=t,x=x[-1]))# can evaluate at arbitrary x (single representative particle)ax3.set_xlabel(r"$Time [s]$")ax3.set_ylabel("Positive particle surface concentration")plt.tight_layout()plt.show()
Some of the output variables are defined over space as well as time. Once option to visualise these variables is to use the interact slider widget. Below we plot the negative/positive particle concentration over \(r\), using a slider to change the current time point
c_s_n=solution["Negative particle concentration"]c_s_p=solution["Positive particle concentration"]r_n=solution["r_n [m]"].entries[:,0,0]r_p=solution["r_p [m]"].entries[:,0,0]defplot_concentrations(t):f,(ax1,ax2)=plt.subplots(1,2,figsize=(10,5))(plot_c_n,)=ax1.plot(r_n,c_s_n(r=r_n,t=t,x=x[0]))# can evaluate at arbitrary x (single representative particle)(plot_c_p,)=ax2.plot(r_p,c_s_p(r=r_p,t=t,x=x[-1]))# can evaluate at arbitrary x (single representative particle)ax1.set_ylabel("Negative particle concentration")ax2.set_ylabel("Positive particle concentration")ax1.set_xlabel(r"$r_n$ [m]")ax2.set_xlabel(r"$r_p$ [m]")ax1.set_ylim(0,1)ax2.set_ylim(0,1)plt.show()importipywidgetsaswidgetswidgets.interact(plot_concentrations,t=widgets.FloatSlider(min=0,max=3600,step=10,value=0));
The QuickPlot class can be used to plot the common set of useful outputs which should give you a good initial overview of the model. The method Quickplot.dynamic_plot employs the slider widget.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[4] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
The SPMe comprises an equation for the lithium concentration in a representative particle in the negative electrode \(c_{\text{s,n}}\), an equation for the lithium concentration in a representative particle in the positive electrode \(c_{\text{s,p}}\), and an equation which governs the behaviour of the first-order correction to the lithium concentration in the electrolyte \(c_{\text{e,k}}\). Here we use a roman subscript \(\text{k} \in \text{n, s, p}\) is used to denote the
regions negative electrode, separator, and positive electrode, respectively.
At the centre of each particle the standard no-flux condition is imposed, and the flux on the surface of the particle is simply the current \(I\) divided by the thickness of the electrode \(L_{\text{k}}\), as in the SPM. Since lithium is transferred between the electrolyte and particles, the flux through the particle surface also enters the electrolyte diffusion equation as a source/sink term. There is no transfer of lithium between the electrolyte and current collectors, which
leads to no flux boundary conditions on the lithium concentration in the electrolyte \(c_{\text{e,k}}\) at either end of the cell.
We must also impose initial conditions which correspond to setting an initial concentration in each particle \(c_{\text{s,k}}(t=0) = c_{\text{s,k,0}}\), and to having no deviation from the initial (uniform) lithium concentration in the electrolyte \(c_{\text{e,k}}(t=0) = c_{\text{e,0}}\).
where \(D_{\text{s,k}}\) is the diffusion coefficient in the solid, \(N_{\text{s,k}}\) denotes the flux of lithium ions in the solid particle within the region \(\text{k}\), and \(r_{\text{k}} \in[0,R_{\text{k}}]\) is the radial coordinate of the particle in electrode \(\text{k}\). All other relevant parameters are given in the table at the end of this notebook.
where \(D_{\text{e}}\) is the diffusion coefficient in the solid, \(N_{\text{e,k}}\) denotes the flux of lithium ions in the electrolyte within the region \(\text{k}\), and \(x\in[0,L]\) is the macroscopic through-cell distance. This equation is also solved subject to continuity of concentration and flux at the electrode/separator interfaces.
Below we show how to solve the SPMe model using the Simulation class with all the default settings. For a more detailed example, see the notebook on the SPM.
First we need to import pybamm
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybamm
Note: you may need to restart the kernel to use updated packages.
We then load the SPMe model and create a simulation
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[4] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). ECSarXiv. February, 2020. doi:10.1149/osf.io/67ckj.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this notebook we show how to use the crack submodel with battery DFN or SPM models. To see all of the models and submodels available in PyBaMM, please take a look at the documentation here.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportosimportmatplotlib.pyplotaspltos.chdir(pybamm.__path__[0]+"/..")
Then we load the DFN, SPMe or SPM, by choosing one and commenting the others.
When you load a model in PyBaMM it builds by default. Building the model sets all of the model variables and sets up any variables which are coupled between different submodels: this is the process which couples the submodels together and allows one submodel to access variables from another.
[2]:
model=pybamm.lithium_ion.DFN(options={"particle":"Fickian diffusion","particle mechanics":"swelling and cracking",# other options are "none", "swelling only"})
Load the parameter set Ai2020 which contains mechanical parameters. Other sets may not contain mechanical parameters should add them manually.
[3]:
param=pybamm.ParameterValues("Ai2020")## It can update the speed of crack propagation using the commands below:# param.update({"Negative electrode Cracking rate":3.9e-20*10}, check_already_exists=False)
We can get the default parameters for the model and update them with the parameters required by the cracking model. Eventually, we would like these to be added to their own chemistry (you might need to adjust the path to the parameters file to your system). Now the model can be processed and solved in the usual way, and we still have access to model defaults such as the default geometry and default spatial methods
Depending on the parameter set being used, the particle cracking model can require a large number of mesh points inside the particles to be numerically stable.
# extract voltageE_n=param["Negative electrode Young's modulus [Pa]"]stress_t_n_surf=solution["Negative particle surface tangential stress [Pa]"]x=solution["x [m]"].entries[0:19,0]c_s_n=solution["Negative particle concentration"]r_n=solution["r_n [m]"].entries[:,0,0]# plotdefplot_concentrations(t):f,(ax1,ax2,ax3,ax4)=plt.subplots(1,4,figsize=(20,4))ax1.plot(x,stress_t_n_surf(t=t,x=x)/E_n)ax1.set_xlabel(r"$x_n$ [m]")ax1.set_ylabel("$\sigma_t/E_n$")(plot_c_n,)=ax2.plot(r_n,c_s_n(r=r_n,t=t,x=x[0]))# can evaluate at arbitrary x (single representative particle)ax2.set_ylabel("Negative particle concentration")ax2.set_xlabel(r"$r_n$ [m]")ax2.set_ylim(0,1)ax2.set_title("Close to current collector")ax2.grid()(plot_c_n,)=ax3.plot(r_n,c_s_n(r=r_n,t=t,x=x[10]))# can evaluate at arbitrary x (single representative particle)ax3.set_ylabel("Negative particle concentration")ax3.set_xlabel(r"$r_n$ [m]")ax3.set_ylim(0,1)ax3.set_title("In the middle")ax3.grid()(plot_c_n,)=ax4.plot(r_n,c_s_n(r=r_n,t=t,x=x[-1]))# can evaluate at arbitrary x (single representative particle)ax4.set_ylabel("Negative particle concentration")ax4.set_xlabel(r"$r_n$ [m]")ax4.set_ylim(0,1)ax4.set_title("Close to separator")ax4.grid()plt.show()importipywidgetsaswidgetswidgets.interact(plot_concentrations,t=widgets.FloatSlider(min=0,max=3600,step=10,value=0));
[1] Weilong Ai, Ludwig Kraft, Johannes Sturm, Andreas Jossen, and Billy Wu. Electrochemical thermal-mechanical modelling of stress inhomogeneity in lithium-ion pouch cells. Journal of The Electrochemical Society, 167(1):013512, 2019. doi:10.1149/2.0122001JES.
[2] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[3] Rutooj Deshpande, Mark Verbrugge, Yang-Tse Cheng, John Wang, and Ping Liu. Battery cycle life prediction with coupled chemical degradation and fatigue mechanics. Journal of the Electrochemical Society, 159(10):A1730, 2012. doi:10.1149/2.049210jes.
[4] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[5] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[6] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
The first model we consider is the stress-driven submodel, which follows equation (25) from Reniers et al (2019), and the stresses are calculated by equations (7)-(9) in Ai et al (2020). To see all of the models and submodels available in PyBaMM, please take a look at the documentation.
As usual, we start by defining the model. We choose a DFN model with stress-driven loss of active material, and we also include SEI growth. We then define the parameters and experiments, and solve the simulation.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammmodel=pybamm.lithium_ion.DFN(options={"SEI":"solvent-diffusion limited","SEI porosity change":"false","particle mechanics":"swelling only","loss of active material":"stress-driven",})param=pybamm.ParameterValues("Ai2020")param.update({"Negative electrode LAM constant proportional term [s-1]":1e-4/3600})param.update({"Positive electrode LAM constant proportional term [s-1]":1e-4/3600})total_cycles=2experiment=pybamm.Experiment(["Discharge at 1C until 3 V","Rest for 600 seconds","Charge at 1C until 4.2 V","Hold at 4.199 V for 600 seconds",]*total_cycles)sim=pybamm.Simulation(model,experiment=experiment,parameter_values=param,solver=pybamm.CasadiSolver("fast with events"),)solution=sim.solve(calc_esoh=False)
WARNING: pybamm 23.5 does not provide the extra 'cite'WARNING: pybamm 23.5 does not provide the extra 'plot'[notice] A new release of pip is available: 23.0.1 -> 23.2.1[notice] To update, run: pip install --upgrade pip
Note: you may need to restart the kernel to use updated packages.
We can now plot the results as usual.
[2]:
sim.plot(["Voltage [V]","Current [A]","Sum of x-averaged positive electrode volumetric interfacial current densities [A.m-3]","Sum of x-averaged negative electrode volumetric interfacial current densities [A.m-3]","X-averaged positive electrode active material volume fraction","X-averaged negative electrode active material volume fraction","X-averaged positive particle surface tangential stress [Pa]","X-averaged negative particle surface tangential stress [Pa]",])
[2]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7f91cc5d2d00>
To understand the effect of the LAM constant proportional term, let’s perform a parameter sweep.
[3]:
ks=[1e-4,1e-3,1e-2]solutions=[]forkinks:param.update({"Positive electrode LAM constant proportional term [s-1]":k/3600})param.update({"Negative electrode LAM constant proportional term [s-1]":k/3600})sim=pybamm.Simulation(model,experiment=experiment,parameter_values=param,solver=pybamm.CasadiSolver("fast with events"),)solution=sim.solve(calc_esoh=False)solutions.append(solution)pybamm.dynamic_plot(solutions,output_variables=["Voltage [V]","Current [A]","Sum of x-averaged positive electrode volumetric interfacial current densities [A.m-3]","Sum of x-averaged negative electrode volumetric interfacial current densities [A.m-3]","X-averaged positive electrode active material volume fraction","X-averaged negative electrode active material volume fraction","X-averaged positive electrode surface area to volume ratio [m-1]","X-averaged negative electrode surface area to volume ratio [m-1]",],labels=[f"k={k:.0e}"forkinks],)
[3]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7f9179084910>
Another option is to use reaction-driven (i.e. SEI) LAM. In this case we need to choose the "reaction-driven" option in the model, and proceed along the lines of the previous example.
[4]:
model=pybamm.lithium_ion.DFN(options={"SEI":"solvent-diffusion limited","loss of active material":"reaction-driven",})param=pybamm.ParameterValues("Chen2020")param.update({"Negative electrode reaction-driven LAM factor [m3.mol-1]":1e-3,})total_cycles=2experiment=pybamm.Experiment(["Discharge at 1C until 3 V","Rest for 600 seconds","Charge at 1C until 4.2 V","Hold at 4.199 V for 600 seconds",]*total_cycles)sim=pybamm.Simulation(model,experiment=experiment,parameter_values=param,solver=pybamm.CasadiSolver("fast with events"),)solution=sim.solve(calc_esoh=False)sim.plot(["Voltage [V]","Current [A]","Sum of x-averaged negative electrode volumetric interfacial current densities [A.m-3]","X-averaged negative electrode active material volume fraction","Negative total SEI thickness [m]","X-averaged negative total SEI thickness [m]",])
[4]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7f91cc77d9a0>
Both stress-driven and reaction-driven can be combined by calling the "stressandreaction-driven" option.
The final submodel is current-driven LAM, which follows equation (26) from Reniers et al (2019). In this case we need to define the RHS of the equation as a function of current density and temperature. The example here is illustrative and does not represent any real scenario.
[5]:
defcurrent_LAM(i,T):return-1e-10*(abs(i)+1e3*abs(i)**0.5)model=pybamm.lithium_ion.DFN(options={"loss of active material":"current-driven",})param=pybamm.ParameterValues("Chen2020")param.update({"Positive electrode current-driven LAM rate":current_LAM,"Negative electrode current-driven LAM rate":current_LAM,},check_already_exists=False,)total_cycles=2experiment=pybamm.Experiment(["Discharge at 1C until 3 V","Rest for 600 seconds","Charge at 1C until 4.2 V","Hold at 4.199 V for 600 seconds",]*total_cycles)sim=pybamm.Simulation(model,experiment=experiment,parameter_values=param,solver=pybamm.CasadiSolver("fast with events"),)solution=sim.solve(calc_esoh=False)sim.plot(["Voltage [V]","Current [A]","X-averaged positive electrode active material volume fraction","X-averaged negative electrode active material volume fraction",])
[5]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7f9170283dc0>
[1] Weilong Ai, Ludwig Kraft, Johannes Sturm, Andreas Jossen, and Billy Wu. Electrochemical thermal-mechanical modelling of stress inhomogeneity in lithium-ion pouch cells. Journal of The Electrochemical Society, 167(1):013512, 2019. doi:10.1149/2.0122001JES.
[2] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[3] Chang-Hui Chen, Ferran Brosa Planella, Kieran O'Regan, Dominika Gastol, W. Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.
[4] Rutooj Deshpande, Mark Verbrugge, Yang-Tse Cheng, John Wang, and Ping Liu. Battery cycle life prediction with coupled chemical degradation and fatigue mechanics. Journal of the Electrochemical Society, 159(10):A1730, 2012. doi:10.1149/2.049210jes.
[5] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[6] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[7] Scott G. Marquis. Long-term degradation of lithium-ion batteries. PhD thesis, University of Oxford, 2020.
[8] Jorn M. Reniers, Grietus Mulder, and David A. Howey. Review and performance comparison of mechanical-chemical degradation models for lithium-ion batteries. Journal of The Electrochemical Society, 166(14):A3189, 2019. doi:10.1149/2.0281914jes.
[9] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
There are a number of thermal submodels available in PyBaMM. In this notebook we give details of each of the models, and highlight any relevant parameters. At present PyBaMM includes an isothermal and a lumped thermal model, both of which can be used with any cell geometry, as well as a 1D thermal model which accounts for the through-cell variation in temperature in a pouch cell, and “1+1D” and “2+1D” pouch cell models which assumed the temperature is uniform through the thickness of the pouch,
but accounts for variations in temperature in the remaining dimensions. Here we give the governing equations for each model (except the isothermal model, which just sets the temperature to be equal to to the parameter “Ambient temperature [K]”).
A more comprehensive review of the pouch cell models, including how to properly compute the effective cooling terms, can be found in references [4] and [6] at the end of this notebook.
[4]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybamm
[notice] A new release of pip is available: 23.3.1 -> 23.3.2[notice] To update, run: pip install --upgrade pip
Note: you may need to restart the kernel to use updated packages.
where \(\rho_{eff}\) is effective volumetric heat capacity, \(T\) is the temperature, \(t\) is time, \(\bar{Q}\) is the averaged heat source term, \(h\) is the heat transfer coefficient, \(A\) is the surface area (available for cooling), \(V\) is the cell volume, and \(T_{\infty}\) is the ambient temperature. An initial temperature \(T_0\) must be prescribed.
The effective volumetric heat capacity is computed as
where \(\rho_k\) is the density, \(c_{p,k}\) is the specific heat, and \(L_k\) is the thickness of each component. The subscript \(k \in \{cn, n, s, p, cp\}\) is used to refer to the components negative current collector, negative electrode, separator, positive electrode, and positive current collector.
The heat source term accounts for Ohmic heating \(Q_{Ohm,k}\) due to resistance in the solid and electrolyte, irreverisble heating due to electrochemical reactions \(Q_{rxn,k}\), and reversible heating due to entropic changes in the the electrode \(Q_{rev,k}\):
Here \(i_k\) is the current, \(\phi_k\) the potential, \(a_k\) the surface area to volume ratio, \(j_k\) the interfacial current density, \(\eta_k\) the overpotential, and \(U\) the open-circuit potential. The averaged heat source term \(\bar{Q}\) is computed by taking the volume-average of \(Q\).
When using the option {"cellgeometry":"arbitrary"} the relevant parameters to specify the cooling conditions are:
“Total heat transfer coefficient [W.m-2.K-1]”
“Cell cooling surface area [m2]”
“Cell volume [m3]”
which correspond directly to the parameters \(h\), \(A\) and \(V\) in the governing equation.
When using the option {"cellgeometry":"pouch"} the parameter \(A\) and \(V\) are computed automatically from the pouch dimensions, assuming a single-layer pouch cell, i.e. \(A\) is the total surface area of a single-layer pouch cell and \(V\) is the volume. The parameter \(h\) is still set by the “Total heat transfer coefficient [W.m-2.K-1]” parameter.
The lumped thermal option can be selected as follows
The 1D model solves for \(T(x,t)\), capturing variations through the thickness of the cell, but ignoring variations in the other dimensions. The temperature is found as the solution of a partial differential equation, given here in dimensional terms
Here \(\lambda_k\) is the thermal conductivity of component \(k\), and the heat transfer coefficients \(h_{cn}\) and \(h_{cp}\) correspond to heat transfer at the large surface of the pouch on the side of the negative current collector, heat transfer at the large surface of the pouch on the side of the positive current collector, respectively. The heat source term \(Q\) is as described in the section on lumped models. The term \(Q_cool\) accounts for additional heat
losses due to heat transfer at the sides of the pouch, as well as the tabs. This term is computed automatically by PyBaMM based on the cell geometry and heat transfer coefficients on the edges and tabs of the cell.
The relevant heat transfer parameters are: “Negative current collector surface heat transfer coefficient [W.m-2.K-1]” “Positive current collector surface heat transfer coefficient [W.m-2.K-1]” “Negative tab heat transfer coefficient [W.m-2.K-1]” “Positive tab heat transfer coefficient [W.m-2.K-1]” “Edge heat transfer coefficient [W.m-2.K-1]”
The 1D model is termed “x-full” (since it fully accounts for variation in the x direction) and can be selected as follows
These pouch cell thermal models ignore any variation in temperature through the thickness of the cell (x direction), and solve for \(T(y,z,t)\). It is therefore referred to as an “x-lumped” model. The temperature is found as the solution of a partial differential equation, given here in dimensional terms,
\[-\lambda_{eff} \nabla_\perp T \cdot \boldsymbol{n} = \frac{L_{cn}h_{cn} + (L_n+L_s+L_p+L_{cp})h_{edge}}{L_{cn}+L_n+L_s+L_p+L_{cp}}(T-T_\infty),\]
at the negative tab,
\[-\lambda_{eff} \nabla_\perp T \cdot \boldsymbol{n} = \frac{(L_{cn}+L_n+L_s+L_p)h_{edge}+L_{cp}h_{cp}}{L_{cn}+L_n+L_s+L_p+L_{cp}}(T-T_\infty),\]
at the positive tab, and
\[-\lambda_{eff} \nabla_\perp T \cdot \boldsymbol{n} = h_{edge}(T-T_\infty),\]
elsewhere. Again, an initial temperature \(T_0\) must be prescribed.
Here the heat source term is averaged in the x direction so that \(\bar{Q}=\bar{Q}(y,z)\). The parameter \(\lambda_{eff}\) is the effective thermal conductivity, computed as
The heat transfer coefficients \(h_{cn}\), \(h_{cp}\) and \(h_{egde}\) correspond to heat transfer at the large surface of the pouch on the side of the negative current collector, heat transfer at the large surface of the pouch on the side of the positive current collector, and heat transfer at the remaining, respectively.
The relevant heat transfer parameters are: “Negative current collector surface heat transfer coefficient [W.m-2.K-1]” “Positive current collector surface heat transfer coefficient [W.m-2.K-1]” “Negative tab heat transfer coefficient [W.m-2.K-1]” “Positive tab heat transfer coefficient [W.m-2.K-1]” “Edge heat transfer coefficient [W.m-2.K-1]”
Here we compare the “full” one-dimensional model with the lumped model for a pouch cell. We first set up our models, passing the relevant options, and then show how to adjust the parameters to so that the lumped and full models give the same behaviour
For the “full” model we use a heat transfer coefficient of \(5\, \text{Wm}^{-2}\text{K}^{-1}\) on the large surfaces of the pouch and zero heat transfer coefficient on the tabs and edges
[11]:
full_params=parameter_values.copy()full_params.update({"Negative current collector"+" surface heat transfer coefficient [W.m-2.K-1]":5,"Positive current collector"+" surface heat transfer coefficient [W.m-2.K-1]":5,"Negative tab heat transfer coefficient [W.m-2.K-1]":0,"Positive tab heat transfer coefficient [W.m-2.K-1]":0,"Edge heat transfer coefficient [W.m-2.K-1]":0,})
For the lumped model we set the “Total heat transfer coefficient [W.m-2.K-1]” parameter as well as the “Cell cooling surface area [m2]” parameter. Since the “full” model only accounts for cooling from the large surfaces of the pouch, we set the “Surface area for cooling” parameter to the area of the large surfaces of the pouch, and the total heat transfer coefficient to \(5\, \text{Wm}^{-2}\text{K}^{-1}\)
[12]:
A=parameter_values["Electrode width [m]"]*parameter_values["Electrode height [m]"]lumped_params=parameter_values.copy()lumped_params.update({"Total heat transfer coefficient [W.m-2.K-1]":5,"Cell cooling surface area [m2]":2*A,})
Let’s run simulations with both options and compare the results. For demonstration purposes we’ll increase the current to amplify the thermal effects
[13]:
params=[full_params,lumped_params]# loop over the models and solvesols=[]formodel,paraminzip(models,params):param["Current function [A]"]=3*0.68sim=pybamm.Simulation(model,parameter_values=param)sim.solve([0,3600])sols.append(sim.solution)# plotoutput_variables=["Voltage [V]","X-averaged cell temperature [K]","Cell temperature [K]",]pybamm.dynamic_plot(sols,output_variables)# plot the resultspybamm.dynamic_plot(sols,["Volume-averaged cell temperature [K]","Volume-averaged total heating [W.m-3]","Current [A]","Voltage [V]",],)
[13]:
<pybamm.plotting.quick_plot.QuickPlot at 0x17f501e90>
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[5] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[6] Robert Timms, Scott G Marquis, Valentin Sulzer, Colin P. Please, and S Jonathan Chapman. Asymptotic Reduction of a Lithium-ion Pouch Cell Model. SIAM Journal on Applied Mathematics, 81(3):765–788, 2021. doi:10.1137/20M1336898.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this notebook we create and solve a model for unsteady heat diffusion in 1D, with a spatially dependent heat source term. The notebook is adapted from example 4.1.2 on pg.16 of the online notes found here.
We consider the heat equation
\[T_{t} = kT_{xx} + Q(x), \quad 0 < x < L, \quad t > 0,\]
As in the example, we solve the problem on the domain \(0 < x < 2\) (i.e. we take \(L=2\)). We extended the example to include a thermal diffusivity \(k\), which we take to be equal to 0.75.
As always, we start by importing PyBaMM, along with any other packages we require.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpimportmatplotlib.pyplotasplt
Note: you may need to restart the kernel to use updated packages.
We then load up an instance of the pybamm.BaseModel class.
[2]:
model=pybamm.BaseModel()
We now define the model variables and parameters. Note that we also need to define the spatial variable \(x\) here, so that we can write down the spatially dependent source term \(Q(x)\). Since we are solving in 1D we have decided to call the domain “rod”, but we could name it anything we like. Note that in PyBaMM variables and parameters can be given useful and meaningful names, such as “Temperature”, so that they can be easily referred to later.
Now that we have defined the variables, we can write down the model equations and add them to the model.rhs dictionary. This dictionary stores the right hand sides of any time-dependent differential equations (ordinary or partial). The key is the variable inside the time derivative (in this case \(T\)). To define the heat source term we use a pybamm.Function class. The first argument of the class is the function, and the second argument is the input.
[4]:
N=-k*pybamm.grad(T)# Heat fluxQ=1-pybamm.Function(np.abs,x-1)# Source termdTdt=-pybamm.div(N)+Q# The right hand side of the PDEmodel.rhs={T:dTdt}# Add to model
We now add the boundary conditions into the model.boundary_conditions dictionary. The keys of the dictionary indicate which end of the boundary the condition is applied to (in 1D this can be “left” or “right”), the entry is then give as a tuple of the value and type. In this example we have homogeneous Dirichlet boundary conditions at both ends.
We also need to add the initial conditions to the model.initial_conditions dictionary.
[6]:
model.initial_conditions={T:2*x-x**2}
Finally, we add any output variables to the model.variables dictionary. These variables can be easily accessed after the model has been solved. You can add any variables of interest to this dictionary. Here we have added the temperature, heat flux and heat source.
Now that the model has been constructed we can go ahead and define our geometry and parameter values. We start by defining the geometry for our “rod” domain. We need to define the spatial direction(s) in which spatial operators act (such as gradients). In this case it is simply \(x\). We then set the minimum and maximum values \(x\) can take. In this example we are solving the problem on the domain \(0<x<2\).
We also need to provide the values of any parameters using the pybamm.ParameterValues class. This class accepts a dictionary of parameter names and values. Note that the name we provide is the string name of the parameters and not its symbol.
Before we disctretise the spatial operators, we must choose and mesh and a spatial method. Here we choose to use a uniformly spaced 1D mesh with 30 points, and discretise the equations in space using the finite volume method. The information about the mesh is stored in a pybamm.Mesh object, wheres the spatial methods are stored in a dictionary which maps domain names to a spatial method. This allows the user to discretise different (sub)domains in a problem using different spatial methods.
All of this information goes into a pybamm.Discretisation object, which accepts a mesh and a dictionary of spatial methods.
The model is then processed using the discretisation, turning the spaital operators into matrix-vector multiplications.
[12]:
disc.process_model(model)
[12]:
<pybamm.models.base_model.BaseModel at 0x7f39430e6ad0>
Now that the model has been discretised we are ready to solve. We must first choose a solver to use. For this model we choose the Scipy ODE solver, but other solvers are available in PyBaMM (see here). To solve the model, we use the method solver.solve which takes in a model and an array of times at which we would like the solution to be returned. Ths solution is then stored in the solution object. The times and states
can be accessed with solver.t and solver.y.
After solving, we can process variables using the class pybamm.ProcessedVariable. This returns a callable object which can be evaluated at any time and position by means of interpolating the solution. Processed variables provide a convinient way of comparing the solution to solutions from different models, or to exact solutions. Since all of the variables are names with informative strings, the user doesn’t need to keep track of where they are stored in the state vector solution.y. This
is particularly useful in complex models with lots of variables, and is automatically handled by the solution dictionary.
[14]:
T_out=solution["Temperature"]
2021-01-24 20:03:41,111 - [WARNING] processed_variable.get_spatial_scale(518): No length scale set for rod. Using default of 1 [m].
We construct the exact solution by summing over some large number \(N\) of terms in the Fourier series.
[15]:
N=100# number of Fourier modes to sumk_val=param["Thermal diffusivity"]# extract value of diffusivity from the parameters dictionary# Fourier coefficientsdefq(n):return(8/(n**2*np.pi**2))*np.sin(n*np.pi/2)defc(n):return(16/(n**3*np.pi**3))*(1-np.cos(n*np.pi))defb(n):returnc(n)-4*q(n)/(k_val*n**2*np.pi**2)defT_n(t,n):return(4*q(n)/(k_val*n**2*np.pi**2))+b(n)*np.exp(-k_val*(n*np.pi/2)**2*t)# Sum series to get the temperaturedefT_exact(x,t):out=0forninnp.arange(1,N):out+=T_n(t,n)*np.sin(n*np.pi*x/2)returnout
Finally, we plot the numerical and exact solutions at a series of different times. The plot demonstrates an excellent agreement between the numerical solution provided by PyBaMM (dots) and the exact solution (solid lines). Note that in the finite volume method the variable is evaluated at the cell centres.
[16]:
x_nodes=mesh["rod"].nodes# numerical gridpointsxx=np.linspace(0,2,101)# fine mesh to plot exact solutionplot_times=np.linspace(0,1,5)# times at which to plotplt.figure(figsize=(15,8))cmap=plt.get_cmap("inferno")fori,tinenumerate(plot_times):color=cmap(float(i)/len(plot_times))plt.plot(x_nodes,T_out(t,x=x_nodes),"o",color=color,label="Numerical"ifi==0else"",)plt.plot(xx,T_exact(xx,t),"-",color=color,label=f"Exact (t={plot_times[i]})",)plt.xlabel("x",fontsize=16)plt.ylabel("T",fontsize=16)plt.legend()plt.show()
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). ECSarXiv. February, 2020. doi:10.1149/osf.io/67ckj.
[4] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this notebook we show how to pass options to models. This allows users to do things such as include extra physics (e.g. thermal effects) or change the macroscopic dimension of the problem (e.g. change from a 1D model to a 2+1D pouch cell model). To see all of the options currently available in PyBaMM, please take a look at the documentation here. For more information on combining submodels explicitly
to create your own custom model, please see the Using Submodels notebook.
Example: Solving the SPMe with a lumped thermal model#
PyBaMM is designed to be a flexible modelling package that allows users to easily include different physics within a model without having to start from scratch. In this example, we show how to pass model options to include thermal effects in the SPMe (for more information on the SPMe see here). First we import PyBaMM and any other packages we need
[ ]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportosos.chdir(pybamm.__path__[0]+"/..")
Note: you may need to restart the kernel to use updated packages.
We then choose out model options, which a set as a dictionary. We choose to model the behaviour in the particle by assuming the concentration profile is quadratic within the particle. We also choose a lumped thermal model (note that this is fully-coupled, i.e. parameters can depend on temperature). For an in-depth look at the thermal models see the thermal models notebook
<pybamm.solvers.solution.Solution at 0x7f6169de4690>
Finally we plot the voltage and the cell temperature
[6]:
simulation.plot(["Voltage [V]","X-averaged cell temperature [K]",])
Note that the variable “X-averaged cell temperature [K]” is the scalar-valued lumped temperature, whereas the variable “Cell temperature [K]” is the value of the lumped temperature broadcasted across the whole cell domain. This type of behaviour is purposefully designed to allow easy comparison of different models and settings. For instance we may wish to compare a simulation that uses a lumped thermal model with a simulation that uses a full thermal model (i.e. one that solves the heat equation
in the x-direction). When comparing these two model we could then plot the same variable “Cell temperature [K]” to compare the temperature throughout the cell.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[4] Venkat R. Subramanian, Vinten D. Diwakar, and Deepak Tapriyal. Efficient macro-micro scale coupled modeling of batteries. Journal of The Electrochemical Society, 152(10):A2002, 2005. doi:10.1149/1.2032427.
[5] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). ECSarXiv. February, 2020. doi:10.1149/osf.io/67ckj.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this notebook we show how to modify existing models by swapping out submodels, and how to build your own model from scratch using existing submodels. To see all of the models and submodels available in PyBaMM, please take a look at the documentation here.
PyBaMM is designed to be a flexible modelling package that allows users to easily compare different models and numerical techniques within a common framework. Battery models within PyBaMM are built up using a number of submodels that describe different physics included within the model, such as mass conservation in the electrolyte or charge conservation in the solid. For ease of use, a number of popular battery models are pre-configured in PyBaMM. As an example, we look at the Single Particle
Model (for more information see here).
First we import pybamm
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybamm
Note: you may need to restart the kernel to use updated packages.
Then we load the SPM
[2]:
model=pybamm.lithium_ion.SPM()
We can look at the submodels that make up the SPM by accessing model.submodels, which is a dictionary of the submodel’s name (i.e. the physics it represents) and the submodel that is selected
external circuit <pybamm.models.submodels.external_circuit.explicit_control_external_circuit.ExplicitCurrentControl object at 0x7f9cd479ea00>
porosity <pybamm.models.submodels.porosity.constant_porosity.Constant object at 0x7f9cd479ecd0>
Negative interface utilisation <pybamm.models.submodels.interface.interface_utilisation.full_utilisation.Full object at 0x7f9cd479ee80>
Positive interface utilisation <pybamm.models.submodels.interface.interface_utilisation.full_utilisation.Full object at 0x7f9cd479eee0>
negative particle mechanics <pybamm.models.submodels.particle_mechanics.no_mechanics.NoMechanics object at 0x7f9cd479ef40>
positive particle mechanics <pybamm.models.submodels.particle_mechanics.no_mechanics.NoMechanics object at 0x7f9cd479efd0>
negative primary active material <pybamm.models.submodels.active_material.constant_active_material.Constant object at 0x7f9cd47a60a0>
positive primary active material <pybamm.models.submodels.active_material.constant_active_material.Constant object at 0x7f9cd47a6160>
electrolyte transport efficiency <pybamm.models.submodels.transport_efficiency.bruggeman_transport_efficiency.Bruggeman object at 0x7f9cd479ed30>
electrode transport efficiency <pybamm.models.submodels.transport_efficiency.bruggeman_transport_efficiency.Bruggeman object at 0x7f9cd47a61f0>
transverse convection <pybamm.models.submodels.convection.transverse.no_convection.NoConvection object at 0x7f9cd47a6220>
through-cell convection <pybamm.models.submodels.convection.through_cell.no_convection.NoConvection object at 0x7f9cd47a62b0>
negative primary open-circuit potential <pybamm.models.submodels.interface.open_circuit_potential.single_ocp.SingleOpenCircuitPotential object at 0x7f9cd479ec40>
positive primary open-circuit potential <pybamm.models.submodels.interface.open_circuit_potential.single_ocp.SingleOpenCircuitPotential object at 0x7f9cd479ed60>
negative interface <pybamm.models.submodels.interface.kinetics.inverse_kinetics.inverse_butler_volmer.InverseButlerVolmer object at 0x7f9cd479ea90>
negative interface current <pybamm.models.submodels.interface.kinetics.inverse_kinetics.inverse_butler_volmer.CurrentForInverseButlerVolmer object at 0x7f9cd479e880>
positive interface <pybamm.models.submodels.interface.kinetics.inverse_kinetics.inverse_butler_volmer.InverseButlerVolmer object at 0x7f9cd479e610>
positive interface current <pybamm.models.submodels.interface.kinetics.inverse_kinetics.inverse_butler_volmer.CurrentForInverseButlerVolmer object at 0x7f9cd479e6d0>
negative primary particle <pybamm.models.submodels.particle.fickian_diffusion.FickianDiffusion object at 0x7f9cd479e3a0>
negative primary total particle concentration <pybamm.models.submodels.particle.total_particle_concentration.TotalConcentration object at 0x7f9cd479e490>
positive primary particle <pybamm.models.submodels.particle.fickian_diffusion.FickianDiffusion object at 0x7f9cd479e460>
positive primary total particle concentration <pybamm.models.submodels.particle.total_particle_concentration.TotalConcentration object at 0x7f9cd479e2e0>
negative electrode potential <pybamm.models.submodels.electrode.ohm.leading_ohm.LeadingOrder object at 0x7f9cd479e310>
positive electrode potential <pybamm.models.submodels.electrode.ohm.leading_ohm.LeadingOrder object at 0x7f9cd49119a0>
electrolyte diffusion <pybamm.models.submodels.electrolyte_diffusion.constant_concentration.ConstantConcentration object at 0x7f9cd4816d30>
leading-order electrolyte conductivity <pybamm.models.submodels.electrolyte_conductivity.leading_order_conductivity.LeadingOrder object at 0x7f9cd4911fd0>
negative surface potential difference <pybamm.models.submodels.electrolyte_conductivity.surface_potential_form.explicit_surface_form_conductivity.Explicit object at 0x7f9cd4911e50>
positive surface potential difference <pybamm.models.submodels.electrolyte_conductivity.surface_potential_form.explicit_surface_form_conductivity.Explicit object at 0x7f9cd4911cd0>
thermal <pybamm.models.submodels.thermal.isothermal.Isothermal object at 0x7f9cd4911bb0>
current collector <pybamm.models.submodels.current_collector.homogeneous_current_collector.Uniform object at 0x7f9cd4911ac0>
negative primary sei <pybamm.models.submodels.interface.sei.no_sei.NoSEI object at 0x7f9cd4907220>
positive primary sei <pybamm.models.submodels.interface.sei.no_sei.NoSEI object at 0x7f9cd49078b0>
negative primary sei on cracks <pybamm.models.submodels.interface.sei.no_sei.NoSEI object at 0x7f9cd4819880>
positive primary sei on cracks <pybamm.models.submodels.interface.sei.no_sei.NoSEI object at 0x7f9cd4819610>
negative lithium plating <pybamm.models.submodels.interface.lithium_plating.no_plating.NoPlating object at 0x7f9cd48e41c0>
positive lithium plating <pybamm.models.submodels.interface.lithium_plating.no_plating.NoPlating object at 0x7f9cd48f66a0>
total interface <pybamm.models.submodels.interface.total_interfacial_current.TotalInterfacialCurrent object at 0x7f9cd4911700>
When you load a model in PyBaMM it builds by default. Building the model sets all of the model variables and sets up any variables which are coupled between different submodels: this is the process which couples the submodels together and allows one submodel to access variables from another. If you would like to swap out a submodel in an existing battery model you need to load it without building it by passing the keyword build=False
[4]:
model=pybamm.lithium_ion.SPM(build=False)
This collects all of the submodels which make up the SPM, but doesn’t build the model. Now you are free to swap out one submodel for another. For instance, you may want to assume that diffusion within the negative particles is infinitely fast, so that the PDE describing diffusion is replaced with an ODE for the uniform particle concentration. To change a submodel you simply update the dictionary entry, in this case to the XAveragedPolynomialProfile submodel
where we pass in the model parameters, the electrode (negative or positive) the submodel corresponds to, and the name of the polynomial we want to use. In the example we assume uniform concentration within the particle, corresponding to a zero-order polynomial.
Now if we look at the submodels again we see that the model for the negative particle has been changed
external circuit <pybamm.models.submodels.external_circuit.explicit_control_external_circuit.ExplicitCurrentControl object at 0x7f9cd48d94c0>
porosity <pybamm.models.submodels.porosity.constant_porosity.Constant object at 0x7f9cd48cde80>
Negative interface utilisation <pybamm.models.submodels.interface.interface_utilisation.full_utilisation.Full object at 0x7f9cd48cd970>
Positive interface utilisation <pybamm.models.submodels.interface.interface_utilisation.full_utilisation.Full object at 0x7f9cd48cd850>
negative particle mechanics <pybamm.models.submodels.particle_mechanics.no_mechanics.NoMechanics object at 0x7f9cd48cd790>
positive particle mechanics <pybamm.models.submodels.particle_mechanics.no_mechanics.NoMechanics object at 0x7f9cd48cd520>
negative primary active material <pybamm.models.submodels.active_material.constant_active_material.Constant object at 0x7f9cd48cd7c0>
positive primary active material <pybamm.models.submodels.active_material.constant_active_material.Constant object at 0x7f9cd48cd460>
electrolyte transport efficiency <pybamm.models.submodels.transport_efficiency.bruggeman_transport_efficiency.Bruggeman object at 0x7f9cd48cdb50>
electrode transport efficiency <pybamm.models.submodels.transport_efficiency.bruggeman_transport_efficiency.Bruggeman object at 0x7f9cd491dc70>
transverse convection <pybamm.models.submodels.convection.transverse.no_convection.NoConvection object at 0x7f9cd48b0ac0>
through-cell convection <pybamm.models.submodels.convection.through_cell.no_convection.NoConvection object at 0x7f9cd48c48e0>
negative primary open-circuit potential <pybamm.models.submodels.interface.open_circuit_potential.single_ocp.SingleOpenCircuitPotential object at 0x7f9cd481fd30>
positive primary open-circuit potential <pybamm.models.submodels.interface.open_circuit_potential.single_ocp.SingleOpenCircuitPotential object at 0x7f9cd481f8e0>
negative interface <pybamm.models.submodels.interface.kinetics.inverse_kinetics.inverse_butler_volmer.InverseButlerVolmer object at 0x7f9cd481fd00>
negative interface current <pybamm.models.submodels.interface.kinetics.inverse_kinetics.inverse_butler_volmer.CurrentForInverseButlerVolmer object at 0x7f9cd481f9d0>
positive interface <pybamm.models.submodels.interface.kinetics.inverse_kinetics.inverse_butler_volmer.InverseButlerVolmer object at 0x7f9cd481fa60>
positive interface current <pybamm.models.submodels.interface.kinetics.inverse_kinetics.inverse_butler_volmer.CurrentForInverseButlerVolmer object at 0x7f9cd481f850>
negative primary particle <pybamm.models.submodels.particle.x_averaged_polynomial_profile.XAveragedPolynomialProfile object at 0x7f9cd4952160>
negative primary total particle concentration <pybamm.models.submodels.particle.total_particle_concentration.TotalConcentration object at 0x7f9cd481f6a0>
positive primary particle <pybamm.models.submodels.particle.fickian_diffusion.FickianDiffusion object at 0x7f9cd481f7c0>
positive primary total particle concentration <pybamm.models.submodels.particle.total_particle_concentration.TotalConcentration object at 0x7f9cd481f4c0>
negative electrode potential <pybamm.models.submodels.electrode.ohm.leading_ohm.LeadingOrder object at 0x7f9cd481f550>
positive electrode potential <pybamm.models.submodels.electrode.ohm.leading_ohm.LeadingOrder object at 0x7f9cd481f1c0>
electrolyte diffusion <pybamm.models.submodels.electrolyte_diffusion.constant_concentration.ConstantConcentration object at 0x7f9cd48c4a60>
leading-order electrolyte conductivity <pybamm.models.submodels.electrolyte_conductivity.leading_order_conductivity.LeadingOrder object at 0x7f9cd481fd90>
negative surface potential difference <pybamm.models.submodels.electrolyte_conductivity.surface_potential_form.explicit_surface_form_conductivity.Explicit object at 0x7f9cd481fe20>
positive surface potential difference <pybamm.models.submodels.electrolyte_conductivity.surface_potential_form.explicit_surface_form_conductivity.Explicit object at 0x7f9cd481fe80>
thermal <pybamm.models.submodels.thermal.isothermal.Isothermal object at 0x7f9cd481fdf0>
current collector <pybamm.models.submodels.current_collector.homogeneous_current_collector.Uniform object at 0x7f9cd481ff10>
negative primary sei <pybamm.models.submodels.interface.sei.no_sei.NoSEI object at 0x7f9cd48330d0>
positive primary sei <pybamm.models.submodels.interface.sei.no_sei.NoSEI object at 0x7f9cd4833100>
negative primary sei on cracks <pybamm.models.submodels.interface.sei.no_sei.NoSEI object at 0x7f9cd48331f0>
positive primary sei on cracks <pybamm.models.submodels.interface.sei.no_sei.NoSEI object at 0x7f9cd48331c0>
negative lithium plating <pybamm.models.submodels.interface.lithium_plating.no_plating.NoPlating object at 0x7f9cd4833280>
positive lithium plating <pybamm.models.submodels.interface.lithium_plating.no_plating.NoPlating object at 0x7f9cd48332e0>
total interface <pybamm.models.submodels.interface.total_interfacial_current.TotalInterfacialCurrent object at 0x7f9cd481ff70>
Building the model also sets up the equations, boundary and initial conditions for the model. For example, if we look at model.rhs before building we see that it is empty
[7]:
model.rhs
[7]:
{}
If we try to use this empty model, PyBaMM will give an error. So, before proceeding we must build the model
[8]:
model.build_model()
Now if we look at model.rhs we see that it contains an entry relating to the concentration in each particle, as expected for the SPM
Now the model can be used in a simulation and solved in the usual way, and we still have access to model defaults such as the default geometry and default spatial methods which are used in the simulation
Instead of editing a pre-existing model, you may wish to build your own model from scratch by combining existing submodels of you choice. In this section, we build a Single Particle Model in which the diffusion is assumed infinitely fast in both particles.
To begin, we load a base lithium-ion model. This sets up the basic model structure behind the scenes, and also sets the default parameters to be those corresponding to a lithium-ion battery. Note that the base model does not select any default submodels, so there is no need to pass build=False.
[11]:
model=pybamm.lithium_ion.BaseModel()
Submodels can be added to the model.submodels dictionary in the same way that we changed the submodels earlier.
We use the simplest model for the external circuit, which is the explicit “current control” submodel
We want to build a 1D model, so select the Uniform current collector model (if the current collectors are behaving uniformly, then a 1D model is appropriate). We also want the model to be isothermal, so select the thermal model accordingly. Further, we assume that the porosity and active material are constant in space and time.
[13]:
model.submodels["current collector"]=pybamm.current_collector.Uniform(model.param)model.submodels["thermal"]=pybamm.thermal.isothermal.Isothermal(model.param)model.submodels["porosity"]=pybamm.porosity.Constant(model.param,model.options)model.submodels["negative active material"]=pybamm.active_material.Constant(model.param,"negative",model.options)model.submodels["positive active material"]=pybamm.active_material.Constant(model.param,"positive",model.options)
We assume that the current density varies linearly in the electrodes. This corresponds the the leading-order terms in Ohm’s law in the limit in which the SPM is derived in [3]
We assume uniform concentration in both the negative and positive particles. We also have to separately specify a model for the total concentration in each electrode, which is calculated from the concentration in the particles (not a separate ODE)
[15]:
options={**model.options,"particle":"uniform profile"}model.submodels["negative primary particle"]=pybamm.particle.XAveragedPolynomialProfile(model.param,"negative",options)model.submodels["positive primary particle"]=pybamm.particle.XAveragedPolynomialProfile(model.param,"positive",options)model.submodels["negative total particle concentration"]=pybamm.particle.TotalConcentration(model.param,"negative",options)model.submodels["positive total particle concentration"]=pybamm.particle.TotalConcentration(model.param,"positive",options)
In the Single Particle Model, the overpotential can be obtained by inverting the Butler-Volmer relation, so we choose the InverseButlerVolmer submodel for the interface, with the “main” lithium-ion reaction (and default lithium ion options). Because of how the current is implemented, we also need to separately specify the CurrentForInverseButlerVolmer submodel. We also need to specify the submodel for open-circuit potential.
We don’t want any particle mechanics, SEI formation or lithium plating in this model
[17]:
model.submodels["Negative particle mechanics"]=pybamm.particle_mechanics.NoMechanics(model.param,"negative",model.options)model.submodels["Positive particle mechanics"]=pybamm.particle_mechanics.NoMechanics(model.param,"positive",model.options)model.submodels["Negative sei"]=pybamm.sei.NoSEI(model.param,"negative",model.options)model.submodels["Positive sei"]=pybamm.sei.NoSEI(model.param,"positive",model.options)model.submodels["Negative sei on cracks"]=pybamm.sei.NoSEI(model.param,"negative",model.options,cracks=True)model.submodels["Positive sei on cracks"]=pybamm.sei.NoSEI(model.param,"positive",model.options,cracks=True)model.submodels["Negative lithium plating"]=pybamm.lithium_plating.NoPlating(model.param,"Negative")model.submodels["Positive lithium plating"]=pybamm.lithium_plating.NoPlating(model.param,"Positive")
Finally, for the electrolyte we assume that diffusion is infinitely fast so that the concentration is uniform, and also use the leading-order model for charge conservation, which leads to a linear variation in ionic current in the electrodes
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[4] Venkat R. Subramanian, Vinten D. Diwakar, and Deepak Tapriyal. Efficient macro-micro scale coupled modeling of batteries. Journal of The Electrochemical Society, 152(10):A2002, 2005. doi:10.1149/1.2032427.
[5] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this notebook, we show how to use pybamm to reproduce the experimental results for the Enertech cells (LCO-G). To see all of the models and submodels available in PyBaMM, please take a look at the documentation here.
[15]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportosimportmatplotlib.pyplotaspltos.chdir(pybamm.__path__[0]+"/..")
Note: you may need to restart the kernel to use updated packages.
We can get the parameter set Ai2020 for the model, which includes the mechanical properties required by the mechanical model.
[17]:
# update parameters, making C-rate and inputparam=pybamm.ParameterValues("Ai2020")capacity=param["Nominal cell capacity [A.h]"]param.update({"Current function [A]":capacity*pybamm.InputParameter("C-rate")})# update the meshvar=pybamm.standard_spatial_varsvar_pts={var.x_n:50,var.x_s:50,var.x_p:50,var.r_n:21,var.r_p:21,}# define the simulationsim=pybamm.Simulation(model,var_pts=var_pts,parameter_values=param,solver=pybamm.CasadiSolver(mode="fast"),)# solve for different C-ratesCrates=[0.5,1,2]solutions=[]forCrateinCrates:print(f"{Crate} C")sol=sim.solve(t_eval=[0,3600/Crate*1.05],inputs={"C-rate":Crate})solutions.append(sol)# unpack solutionssolution05C,solution1C,solution2C=solutions
0.5 C
1 C
2 C
Load experimental results of the Enertech cells (see [1])
[1] Weilong Ai, Ludwig Kraft, Johannes Sturm, Andreas Jossen, and Billy Wu. Electrochemical thermal-mechanical modelling of stress inhomogeneity in lithium-ion pouch cells. Journal of The Electrochemical Society, 167(1):013512, 2019. doi:10.1149/2.0122001JES.
[2] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[3] Rutooj Deshpande, Mark Verbrugge, Yang-Tse Cheng, John Wang, and Ping Liu. Battery cycle life prediction with coupled chemical degradation and fatigue mechanics. Journal of the Electrochemical Society, 159(10):A1730, 2012. doi:10.1149/2.049210jes.
[4] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[5] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[6] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[7] Robert Timms, Scott G Marquis, Valentin Sulzer, Colin P. Please, and S Jonathan Chapman. Asymptotic Reduction of a Lithium-ion Pouch Cell Model. SIAM Journal on Applied Mathematics, 81(3):765–788, 2021. doi:10.1137/20M1336898.
Tip
An interactive online version of this notebook is available, which can be
accessed via
Changing the input current when solving PyBaMM models#
This notebook shows you how to change the input current when solving PyBaMM models. It also explains how to load in current data from a file, and how to add a user-defined current function. For more examples of different drive cycles see here.
In this notebook we will use the SPM as the example model, and change the input current from the default option. If you are not familiar with running a model in PyBaMM, please see this notebook for more details.
In PyBaMM, the current function is set using the parameter “Current function [A]”. Below we load the SPM with the default parameters, and then change the the current function to be an input parameter, so that we can change it easily later.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpimportosos.chdir(pybamm.__path__[0]+"/..")# create the modelmodel=pybamm.lithium_ion.DFN()# set the default model parametersparam=model.default_parameter_values# change the current function to be an input parameterparam["Current function [A]"]="[input]"
Note: you may need to restart the kernel to use updated packages.
We can set up a simulation in the usual way, making sure we pass in our updated parameters. We choose to solve with a 1.6A current. In order to do this we must pass a dictionary of inputs whose keys are the parameter names and values are the values we want to use for that call to solve
[2]:
# set up simlationsimulation=pybamm.Simulation(model,parameter_values=param)# solve the model at the given time points, passing the current as an inputt_eval=np.linspace(0,600,300)simulation.solve(t_eval,inputs={"Current function [A]":1.6})# plotsimulation.plot()
PyBaMM can also simulate rest behaviour by setting the current function to zero:
[3]:
# solve the model at the given time pointssimulation.solve(t_eval,inputs={"Current function [A]":0})# plotsimulation.plot()
To run drive cycles from data we can create an interpolant and pass it as the current function.
[4]:
importpandasaspd# needed to read the csv data filemodel=pybamm.lithium_ion.DFN()# import drive cycle from filedrive_cycle=pd.read_csv("pybamm/input/drive_cycles/US06.csv",comment="#",header=None).to_numpy()# load parameter valuesparam=model.default_parameter_values# create interpolant - must be a function of *dimensional* timecurrent_interpolant=pybamm.Interpolant(drive_cycle[:,0],drive_cycle[:,1],pybamm.t)# set drive cycleparam["Current function [A]"]=current_interpolant# set up simulation - for drive cycles we recommend using the CasadiSolver in "fast" modesolver=pybamm.CasadiSolver(mode="fast")simulation=pybamm.Simulation(model,parameter_values=param,solver=solver)
Note that when simulating drive cycles there is no need to pass a list of times at which to return the solution, the results are automatically returned at the time points in the data. If you would like the solution returned at times different to those in the data then you can pass an array of times t_eval to solve in the usual way.
Note that some solvers try to evaluate the model equations at a very large value of t during the first step. This may raise a warning if the time requested by the solver is outside of the range of the data provided. However, this does not affect the solve since this large timestep is rejected by the solver, and a suitable shorter initial step is taken.
Note that the function returns a function which takes the input time. Then the model may be loaded and the “Current function” parameter updated to my_fun called with a specific value of A and omega
[7]:
model=pybamm.lithium_ion.SPM()# load default parameter valuesparam=model.default_parameter_values# set user defined current functionA=model.param.I_typomega=0.1param["Current function [A]"]=my_fun(A,omega)
Note that when my_fun is evaluated with A and omega, this creates a new function current(t) which can then be used in the expression tree. The model may then be solved in the usual way
[8]:
# set up simulationsimulation=pybamm.Simulation(model,parameter_values=param)# Example: simulate for 30 secondssimulation_time=30# end time in secondsnpts=int(50*simulation_time*omega)# need enough timesteps to resolve outputt_eval=np.linspace(0,simulation_time,npts)solution=simulation.solve(t_eval)label=[f"Frequency: {omega} Hz"]# plot current and voltageoutput_variables=["Current [A]","Voltage [V]"]simulation.plot(output_variables,labels=label)
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[5] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). ECSarXiv. February, 2020. doi:10.1149/osf.io/67ckj.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this notebook, we explain how parameter values are set for a model. Information on creating new parameter sets is provided in our online documentation
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpimportosimportmatplotlib.pyplotaspltos.chdir(pybamm.__path__[0]+"/..")
Note: you may need to restart the kernel to use updated packages.
In pybamm, the object that sets parameter values for a model is the ParameterValues class, which extends dict. This takes the values of the parameters as input, which can be either a dictionary,
[13]:
param_dict={"a":1,"b":2,"c":3}parameter_values=pybamm.ParameterValues(param_dict)print(f"parameter values are {parameter_values}")
chem_parameter_values=pybamm.ParameterValues("Marquis2019")print("Negative current collector thickness is {} m".format(chem_parameter_values["Negative current collector thickness [m]"]))
Negative current collector thickness is 2.5e-05 m
We can input functions into the parameter value (note we bypass the check that the parameter already exists)
[15]:
defcubed(x):returnx**3parameter_values.update({"cube function":cubed},check_already_exists=False)print(f"parameter values are {parameter_values}")
method self.evaluate() not implemented for symbol a of type <class 'pybamm.expression_tree.parameter.Parameter'>
However, the ParameterValues class can walk through an expression, changing an Parameter objects it sees to the appropriate Scalar and any FunctionParameter object to the appropriate Function, and the resulting expression can be evaluated
If a parameter needs to be changed often (for example, for convergence studies or parameter estimation), the InputParameter class should be used. This is not fixed by parameter values, and its value can be set on evaluation (or on solve):
[19]:
d=pybamm.InputParameter("d")expr=2+dexpr_eval=parameter_values.process_symbol(expr)print("with d = {}, {} = {}".format(3,expr_eval,expr_eval.evaluate(inputs={"d":3})))print("with d = {}, {} = {}".format(5,expr_eval,expr_eval.evaluate(inputs={"d":5})))
with d = 3, 2.0 + d = 5.0
with d = 5, 2.0 + d = 7.0
Discretising and solving it first with one set of parameters,
then updating the parameters and solving again
The toy model used is:
\[\frac{\mathrm{d} u}{\mathrm{d} t} = -a u\]
with initial conditions \(u(0) = b\). The model is first solved with \(a = 3, b = 2\), then with \(a = -1, b = 2\)
[20]:
# Create modelmodel=pybamm.BaseModel()u=pybamm.Variable("u")a=pybamm.Parameter("a")b=pybamm.Parameter("b")model.rhs={u:-a*u}model.initial_conditions={u:b}model.variables={"u":u,"a":a,"b":b}# Set parameters, with a as an input ########################parameter_values=pybamm.ParameterValues({"a":"[input]","b":2})parameter_values.process_model(model)############################################################## Discretise using default discretisationdisc=pybamm.Discretisation()disc.process_model(model)# Solvet_eval=np.linspace(0,2,30)ode_solver=pybamm.ScipySolver()solution=ode_solver.solve(model,t_eval,inputs={"a":3})# Post-process, so that u1 can be called at any time t (using interpolation)t_sol1=solution.tu1=solution["u"]# Solve again with different inputs ###############################solution=ode_solver.solve(model,t_eval,inputs={"a":-1})t_sol2=solution.tu2=solution["u"]#################################################################### Plott_fine=np.linspace(0,t_eval[-1],1000)fig,(ax1,ax2)=plt.subplots(1,2,figsize=(13,4))ax1.plot(t_fine,2*np.exp(-3*t_fine),t_sol1,u1(t_sol1),"o")ax1.set_xlabel("t")ax1.legend(["2 * exp(-3 * t)","u1"],loc="best")ax1.set_title("a = 3, b = 2")ax2.plot(t_fine,2*np.exp(t_fine),t_sol2,u2(t_sol2),"o")ax2.set_xlabel("t")ax2.legend(["2 * exp(t)","u2"],loc="best")ax2.set_title("a = -1, b = 2")plt.tight_layout()plt.show()
[21]:
model.rhs
[21]:
{Variable(0x5f4a102fc03b7b39, u, children=[], domains={}): Multiplication(-0x32ae6bc94fa07109, *, children=['-a', 'y[0:1]'], domains={})}
Since parameters objects must be processed by the ParameterValues class before they can be evaluated, it can be difficult to quickly check the value of a parameter that is a combination of other parameters. You can print all of the parameters (including combinations) in a model by using the print_parameters function.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[4] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[5] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
Tip
An interactive online version of this notebook is available, which can be
accessed via
Setting parameter values can be found at pybamm/docs/source/examples/notebooks/getting_started/tutorial-4-setting-parameter-values.ipynb. This explains the basics of how to set the parameters of a model (in less detail than here).
parameter-values.ipynb can be found at pybamm/examples/notebooks/parameterization/parameter-values.ipynb. This explains the basics of the ParameterValues class.
Adding your own parameter sets (using a dictionary)#
We will be using the model defined and explained in more detail in 3-negative-particle-problem.ipynb example notebook. We begin by importing the required libraries
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpimportmatplotlib.pyplotasplt
/bin/bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8)
Note: you may need to restart the kernel to use updated packages.
| Parameter | Type of parameter |
| =================================== | ========================================================== |
| Initial concentration [mol.m-3] | Parameter |
| Interfacial current density [A.m-2] | InputParameter |
| Diffusion coefficient [m2.s-1] | FunctionParameter with inputs(s) 'Concentration [mol.m-3]' |
Particle radius [m] (Parameter)
This tells us that we need to provide parameter values for the initial concentration and Faraday constant, an InputParameter at solve time for the interfacial current density, and diffusivity as a function of concentration. Since the electrolyte concentration does not appear anywhere in the model, there is no need to provide a value for it.
Now we can proceed to the step where we add the parameter values using a dictionary. We set up a dictionary with parameter names as the dictionary keys and their respective values as the dictionary values.
Now we can pass this dictionary in pybamm.ParameterValues class which accepts a dictionary of parameter names and values. We can then print param to check if it was initialised.
The parameter values or param can be further updated by using the update function of ParameterValues class. The update function takes a dictionary with keys being the parameters to be updated and their respective values being the updated values. Here we update the "Particleradius[m]" parameter’s value. Additionally, a function can also be passed as a parameter’s value which we will see ahead, and a new parameter can also be added by passing
check_already_exists=False in the update function.
We choose a solver and times at which we want the solution returned, and solve the model. Here we give a value for the current density j.
[12]:
# solvesolver=pybamm.ScipySolver()t=np.linspace(0,3600,600)solution=solver.solve(model,t,inputs={"Interfacial current density [A.m-2]":1.4})# post-process, so that the solution can be called at any time t or space r# (using interpolation)c=solution["Concentration [mol.m-3]"]c_surf=solution["Surface concentration [mol.m-3]"]# plotfig,(ax1,ax2)=plt.subplots(1,2,figsize=(13,4))ax1.plot(solution.t,c_surf(solution.t))ax1.set_xlabel("Time [s]")ax1.set_ylabel("Surface concentration [mol.m-3]")rsol=mesh["negative particle"].nodes# radial positiontime=1000# time in secondsax2.plot(rsol*1e6,c(t=time,r=rsol),label=f"t={time}[s]")ax2.set_xlabel("Particle radius [microns]")ax2.set_ylabel("Concentration [mol.m-3]")ax2.legend()plt.tight_layout()plt.show()
In the next few steps, we will be showing the same workflow with the Single Particle Model (SPM). We will also see how you can pass a function as a parameter’s value and how to plot such parameterfunctions.
We can print the parameters of a model by using the print_parameter_info function.
[14]:
spm.print_parameter_info()
| Parameter | Type of parameter |
| ========================================================= | =========================================================================================================================================================================================================== |
| Positive electrode Bruggeman coefficient (electrolyte) | Parameter |
| Electrode width [m] | Parameter |
| Positive electrode thickness [m] | Parameter |
| Negative electrode Bruggeman coefficient (electrolyte) | Parameter |
| Negative electrode Bruggeman coefficient (electrode) | Parameter |
| Initial concentration in electrolyte [mol.m-3] | Parameter |
| Number of cells connected in series to make a battery | Parameter |
| Lower voltage cut-off [V] | Parameter |
| Ideal gas constant [J.K-1.mol-1] | Parameter |
| Separator Bruggeman coefficient (electrolyte) | Parameter |
| Upper voltage cut-off [V] | Parameter |
| Positive electrode Bruggeman coefficient (electrode) | Parameter |
| Separator thickness [m] | Parameter |
| Maximum concentration in negative electrode [mol.m-3] | Parameter |
| Faraday constant [C.mol-1] | Parameter |
| Reference temperature [K] | Parameter |
| Electrode height [m] | Parameter |
| Nominal cell capacity [A.h] | Parameter |
| Maximum concentration in positive electrode [mol.m-3] | Parameter |
| Number of electrodes connected in parallel to make a cell | Parameter |
| Negative electrode thickness [m] | Parameter |
| Separator porosity | FunctionParameter with inputs(s) 'Through-cell distance (x) [m]' |
| Negative electrode active material volume fraction | FunctionParameter with inputs(s) 'Through-cell distance (x) [m]' |
| Negative electrode OCP [V] | FunctionParameter with inputs(s) 'Negative particle stoichiometry' |
| Positive electrode porosity | FunctionParameter with inputs(s) 'Through-cell distance (x) [m]' |
| Negative particle radius [m] | FunctionParameter with inputs(s) 'Through-cell distance (x) [m]' |
| Positive electrode exchange-current density [A.m-2] | FunctionParameter with inputs(s) 'Electrolyte concentration [mol.m-3]', 'Positive particle surface concentration [mol.m-3]', 'Maximum positive particle surface concentration [mol.m-3]', 'Temperature [K]' |
| Positive particle radius [m] | FunctionParameter with inputs(s) 'Through-cell distance (x) [m]' |
| Positive electrode OCP [V] | FunctionParameter with inputs(s) 'Positive particle stoichiometry' |
| Negative electrode exchange-current density [A.m-2] | FunctionParameter with inputs(s) 'Electrolyte concentration [mol.m-3]', 'Negative particle surface concentration [mol.m-3]', 'Maximum negative particle surface concentration [mol.m-3]', 'Temperature [K]' |
| Negative electrode OCP entropic change [V.K-1] | FunctionParameter with inputs(s) 'Negative particle stoichiometry', 'Maximum negative particle surface concentration [mol.m-3]' |
| Current function [A] | FunctionParameter with inputs(s) 'Time[s]' |
| Initial concentration in positive electrode [mol.m-3] | FunctionParameter with inputs(s) 'Radial distance (r) [m]', 'Through-cell distance (x) [m]' |
| Initial concentration in negative electrode [mol.m-3] | FunctionParameter with inputs(s) 'Radial distance (r) [m]', 'Through-cell distance (x) [m]' |
| Positive electrode OCP entropic change [V.K-1] | FunctionParameter with inputs(s) 'Positive particle stoichiometry', 'Maximum positive particle surface concentration [mol.m-3]' |
| Positive electrode diffusivity [m2.s-1] | FunctionParameter with inputs(s) 'Positive particle stoichiometry', 'Temperature [K]' |
| Negative electrode porosity | FunctionParameter with inputs(s) 'Through-cell distance (x) [m]' |
| Positive electrode active material volume fraction | FunctionParameter with inputs(s) 'Through-cell distance (x) [m]' |
| Negative electrode diffusivity [m2.s-1] | FunctionParameter with inputs(s) 'Negative particle stoichiometry', 'Temperature [K]' |
| Ambient temperature [K] | FunctionParameter with inputs(s) 'Distance across electrode width [m]', 'Distance across electrode height [m]', 'Time [s]' |
Note that there are no InputParameter objects in the default SPM. Also, note that if a FunctionParameter is expected, it is ok to provide a scalar (parameter) instead. However, if a Parameter is expected, you cannot provide a function instead.
Another way to view what parameters are needed is to print the default parameter values. This can also be used to get some good defaults (but care must be taken when combining parameters across datasets and chemistries)
{'Ideal gas constant [J.K-1.mol-1]': 8.314462618,
'Faraday constant [C.mol-1]': 96485.33212,
'Negative electrode thickness [m]': 0.0001,
'Separator thickness [m]': 2.5e-05,
'Positive electrode thickness [m]': 0.0001,
'Electrode height [m]': 0.137,
'Electrode width [m]': 0.207,
'Nominal cell capacity [A.h]': 0.680616,
'Current function [A]': 0.680616,
'Maximum concentration in negative electrode [mol.m-3]': 24983.2619938437,
'Negative electrode diffusivity [m2.s-1]': <function pybamm.input.parameters.lithium_ion.Marquis2019.graphite_mcmb2528_diffusivity_Dualfoil1998(sto, T)>,
'Negative electrode OCP [V]': <function pybamm.input.parameters.lithium_ion.Marquis2019.graphite_mcmb2528_ocp_Dualfoil1998(sto)>,
'Negative electrode porosity': 0.3,
'Negative electrode active material volume fraction': 0.6,
'Negative particle radius [m]': 1e-05,
'Negative electrode Bruggeman coefficient (electrolyte)': 1.5,
'Negative electrode Bruggeman coefficient (electrode)': 1.5,
'Negative electrode exchange-current density [A.m-2]': <function pybamm.input.parameters.lithium_ion.Marquis2019.graphite_electrolyte_exchange_current_density_Dualfoil1998(c_e, c_s_surf, c_s_max, T)>,
'Negative electrode OCP entropic change [V.K-1]': <function pybamm.input.parameters.lithium_ion.Marquis2019.graphite_entropic_change_Moura2016(sto, c_s_max)>,
'Maximum concentration in positive electrode [mol.m-3]': 51217.9257309275,
'Positive electrode diffusivity [m2.s-1]': <function pybamm.input.parameters.lithium_ion.Marquis2019.lico2_diffusivity_Dualfoil1998(sto, T)>,
'Positive electrode OCP [V]': <function pybamm.input.parameters.lithium_ion.Marquis2019.lico2_ocp_Dualfoil1998(sto)>,
'Positive electrode porosity': 0.3,
'Positive electrode active material volume fraction': 0.5,
'Positive particle radius [m]': 1e-05,
'Positive electrode Bruggeman coefficient (electrolyte)': 1.5,
'Positive electrode Bruggeman coefficient (electrode)': 1.5,
'Positive electrode exchange-current density [A.m-2]': <function pybamm.input.parameters.lithium_ion.Marquis2019.lico2_electrolyte_exchange_current_density_Dualfoil1998(c_e, c_s_surf, c_s_max, T)>,
'Positive electrode OCP entropic change [V.K-1]': <function pybamm.input.parameters.lithium_ion.Marquis2019.lico2_entropic_change_Moura2016(sto, c_s_max)>,
'Separator porosity': 1.0,
'Separator Bruggeman coefficient (electrolyte)': 1.5,
'Initial concentration in electrolyte [mol.m-3]': 1000.0,
'Reference temperature [K]': 298.15,
'Ambient temperature [K]': 298.15,
'Number of electrodes connected in parallel to make a cell': 1.0,
'Number of cells connected in series to make a battery': 1.0,
'Lower voltage cut-off [V]': 3.105,
'Upper voltage cut-off [V]': 4.1,
'Initial concentration in negative electrode [mol.m-3]': 19986.609595075,
'Initial concentration in positive electrode [mol.m-3]': 30730.7554385565}
We now define a dictionary of values for ParameterValues as before (here, a subset of the Chen2020 parameters)
[16]:
defgraphite_mcmb2528_diffusivity_Dualfoil1998(sto,T):D_ref=3.9*10**(-14)E_D_s=42770arrhenius=exp(E_D_s/constants.R*(1/298.15-1/T))returnD_ref*arrheniusneg_ocp=np.array([[0.0,1.81772748],[0.03129623,1.0828807],[0.03499902,0.99593794],[0.0387018,0.90023398],[0.04240458,0.79649431],[0.04610736,0.73354429],[0.04981015,0.66664314],[0.05351292,0.64137149],[0.05721568,0.59813869],[0.06091845,0.5670836],[0.06462122,0.54746181],[0.06832399,0.53068399],[0.07202675,0.51304734],[0.07572951,0.49394092],[0.07943227,0.47926274],[0.08313503,0.46065259],[0.08683779,0.45992726],[0.09054054,0.43801501],[0.09424331,0.42438665],[0.09794607,0.41150269],[0.10164883,0.40033659],[0.10535158,0.38957134],[0.10905434,0.37756538],[0.1127571,0.36292541],[0.11645985,0.34357086],[0.12016261,0.3406314],[0.12386536,0.32299468],[0.12756811,0.31379458],[0.13127086,0.30795386],[0.13497362,0.29207319],[0.13867638,0.28697687],[0.14237913,0.27405477],[0.14608189,0.2670497],[0.14978465,0.25857493],[0.15348741,0.25265783],[0.15719018,0.24826777],[0.16089294,0.2414345],[0.1645957,0.23362778],[0.16829847,0.22956218],[0.17200122,0.22370236],[0.17570399,0.22181271],[0.17940674,0.22089651],[0.1831095,0.2194268],[0.18681229,0.21830064],[0.19051504,0.21845333],[0.1942178,0.21753715],[0.19792056,0.21719357],[0.20162334,0.21635373],[0.2053261,0.21667822],[0.20902886,0.21738444],[0.21273164,0.21469313],[0.2164344,0.21541846],[0.22013716,0.21465495],[0.22383993,0.2135479],[0.2275427,0.21392964],[0.23124547,0.21074206],[0.23494825,0.20873788],[0.23865101,0.20465319],[0.24235377,0.20205732],[0.24605653,0.19774358],[0.2497593,0.19444147],[0.25346208,0.19190285],[0.25716486,0.18850531],[0.26086762,0.18581399],[0.26457039,0.18327537],[0.26827314,0.18157659],[0.2719759,0.17814088],[0.27567867,0.17529686],[0.27938144,0.1719375],[0.28308421,0.16934161],[0.28678698,0.16756649],[0.29048974,0.16609676],[0.29419251,0.16414985],[0.29789529,0.16260378],[0.30159806,0.16224113],[0.30530083,0.160027],[0.30900361,0.15827096],[0.31270637,0.1588054],[0.31640913,0.15552238],[0.32011189,0.15580869],[0.32381466,0.15220118],[0.32751744,0.1511132],[0.33122021,0.14987253],[0.33492297,0.14874637],[0.33862575,0.14678037],[0.34232853,0.14620776],[0.34603131,0.14555879],[0.34973408,0.14389819],[0.35343685,0.14359279],[0.35713963,0.14242846],[0.36084241,0.14038612],[0.36454517,0.13882096],[0.36824795,0.13954628],[0.37195071,0.13946992],[0.37565348,0.13780934],[0.37935626,0.13973714],[0.38305904,0.13698858],[0.38676182,0.13523254],[0.3904646,0.13441178],[0.39416737,0.1352898],[0.39787015,0.13507985],[0.40157291,0.13647321],[0.40527567,0.13601512],[0.40897844,0.13435452],[0.41268121,0.1334765],[0.41638398,0.1348317],[0.42008676,0.13275118],[0.42378953,0.13286571],[0.4274923,0.13263667],[0.43119506,0.13456447],[0.43489784,0.13471718],[0.43860061,0.13395369],[0.44230338,0.13448814],[0.44600615,0.1334765],[0.44970893,0.13298023],[0.45341168,0.13259849],[0.45711444,0.13338107],[0.46081719,0.13309476],[0.46451994,0.13275118],[0.46822269,0.13443087],[0.47192545,0.13315202],[0.47562821,0.132713],[0.47933098,0.1330184],[0.48303375,0.13278936],[0.48673651,0.13225491],[0.49043926,0.13317111],[0.49414203,0.13263667],[0.49784482,0.13187316],[0.50154759,0.13265574],[0.50525036,0.13250305],[0.50895311,0.13324745],[0.51265586,0.13204496],[0.51635861,0.13242669],[0.52006139,0.13233127],[0.52376415,0.13198769],[0.52746692,0.13254122],[0.53116969,0.13145325],[0.53487245,0.13298023],[0.53857521,0.13168229],[0.54227797,0.1313578],[0.54598074,0.13235036],[0.5496835,0.13120511],[0.55338627,0.13089971],[0.55708902,0.13109058],[0.56079178,0.13082336],[0.56449454,0.13011713],[0.5681973,0.129869],[0.57190006,0.12992626],[0.57560282,0.12942998],[0.57930558,0.12796026],[0.58300835,0.12862831],[0.58671112,0.12656689],[0.59041389,0.12734947],[0.59411664,0.12509716],[0.59781941,0.12110791],[0.60152218,0.11839751],[0.60522496,0.11244226],[0.60892772,0.11307214],[0.61263048,0.1092165],[0.61633325,0.10683058],[0.62003603,0.10433014],[0.6237388,0.10530359],[0.62744156,0.10056993],[0.63114433,0.09950104],[0.63484711,0.09854668],[0.63854988,0.09921473],[0.64225265,0.09541635],[0.64595543,0.09980643],[0.64965823,0.0986612],[0.653361,0.09560722],[0.65706377,0.09755413],[0.66076656,0.09612258],[0.66446934,0.09430929],[0.66817212,0.09661885],[0.67187489,0.09366032],[0.67557767,0.09522548],[0.67928044,0.09535909],[0.68298322,0.09316404],[0.686686,0.09450016],[0.69038878,0.0930877],[0.69409156,0.09343126],[0.69779433,0.0932404],[0.70149709,0.09350762],[0.70519988,0.09339309],[0.70890264,0.09291591],[0.7126054,0.09303043],[0.71630818,0.0926296],[0.72001095,0.0932404],[0.72371371,0.09261052],[0.72741648,0.09249599],[0.73111925,0.09240055],[0.73482204,0.09253416],[0.7385248,0.09209515],[0.74222757,0.09234329],[0.74593034,0.09366032],[0.74963312,0.09333583],[0.75333589,0.09322131],[0.75703868,0.09264868],[0.76074146,0.09253416],[0.76444422,0.09243873],[0.76814698,0.09230512],[0.77184976,0.09310678],[0.77555253,0.09165615],[0.77925531,0.09159888],[0.78295807,0.09207606],[0.78666085,0.09175158],[0.79036364,0.09177067],[0.79406641,0.09236237],[0.79776918,0.09241964],[0.80147197,0.09320222],[0.80517474,0.09199972],[0.80887751,0.09167523],[0.81258028,0.09322131],[0.81628304,0.09190428],[0.81998581,0.09167523],[0.82368858,0.09285865],[0.82739136,0.09180884],[0.83109411,0.09150345],[0.83479688,0.09186611],[0.83849965,0.0920188],[0.84220242,0.09320222],[0.84590519,0.09131257],[0.84960797,0.09117896],[0.85331075,0.09133166],[0.85701353,0.09089265],[0.86071631,0.09058725],[0.86441907,0.09051091],[0.86812186,0.09033912],[0.87182464,0.09041547],[0.87552742,0.0911217],[0.87923019,0.0894611],[0.88293296,0.08999555],[0.88663573,0.08921297],[0.89033849,0.08881213],[0.89404126,0.08797229],[0.89774404,0.08709427],[0.9014468,0.08503284],[1.0,0.07601531],])pos_ocp=np.array([[0.24879728,4.4],[0.26614516,4.2935653],[0.26886763,4.2768621],[0.27159011,4.2647018],[0.27431258,4.2540312],[0.27703505,4.2449446],[0.27975753,4.2364879],[0.28248,4.2302647],[0.28520247,4.2225528],[0.28792495,4.2182574],[0.29064743,4.213294],[0.29336992,4.2090373],[0.29609239,4.2051239],[0.29881487,4.2012677],[0.30153735,4.1981564],[0.30425983,4.1955218],[0.30698231,4.1931167],[0.30970478,4.1889744],[0.31242725,4.1881533],[0.31514973,4.1865883],[0.3178722,4.1850228],[0.32059466,4.1832285],[0.32331714,4.1808805],[0.32603962,4.1805749],[0.32876209,4.1789522],[0.33148456,4.1768146],[0.33420703,4.1768146],[0.3369295,4.1752872],[0.33965197,4.173111],[0.34237446,4.1726718],[0.34509694,4.1710877],[0.34781941,4.1702285],[0.3505419,4.168797],[0.35326438,4.1669831],[0.35598685,4.1655135],[0.35870932,4.1634517],[0.3614318,4.1598248],[0.36415428,4.1571712],[0.36687674,4.154079],[0.36959921,4.1504135],[0.37232169,4.1466532],[0.37504418,4.1423388],[0.37776665,4.1382346],[0.38048913,4.1338248],[0.38321161,4.1305799],[0.38593408,4.1272392],[0.38865655,4.1228104],[0.39137903,4.1186109],[0.39410151,4.114182],[0.39682398,4.1096005],[0.39954645,4.1046948],[0.40226892,4.1004758],[0.4049914,4.0956464],[0.40771387,4.0909696],[0.41043634,4.0864644],[0.41315882,4.0818448],[0.41588129,4.077683],[0.41860377,4.0733309],[0.42132624,4.0690737],[0.42404872,4.0647216],[0.4267712,4.0608654],[0.42949368,4.0564747],[0.43221616,4.0527525],[0.43493864,4.0492401],[0.43766111,4.0450211],[0.44038359,4.041986],[0.44310607,4.0384736],[0.44582856,4.035171],[0.44855103,4.0320406],[0.45127351,4.0289288],[0.453996,4.02597],[0.45671848,4.0227437],[0.45944095,4.0199757],[0.46216343,4.0175133],[0.46488592,4.0149746],[0.46760838,4.0122066],[0.47033085,4.009954],[0.47305333,4.0075679],[0.47577581,4.0050669],[0.47849828,4.0023184],[0.48122074,3.9995501],[0.48394321,3.9969349],[0.48666569,3.9926589],[0.48938816,3.9889555],[0.49211064,3.9834003],[0.4948331,3.9783037],[0.49755557,3.9755929],[0.50027804,3.9707632],[0.50300052,3.9681098],[0.50572298,3.9635665],[0.50844545,3.9594433],[0.51116792,3.9556634],[0.51389038,3.9521511],[0.51661284,3.9479132],[0.51933531,3.9438281],[0.52205777,3.9400866],[0.52478024,3.9362304],[0.52750271,3.9314201],[0.53022518,3.9283848],[0.53294765,3.9242232],[0.53567012,3.9192028],[0.53839258,3.9166257],[0.54111506,3.9117961],[0.54383753,3.90815],[0.54656,3.9038739],[0.54928247,3.8995597],[0.55200494,3.8959136],[0.5547274,3.8909314],[0.55744986,3.8872662],[0.56017233,3.8831048],[0.5628948,3.8793442],[0.56561729,3.8747628],[0.56833976,3.8702576],[0.57106222,3.8666878],[0.57378469,3.8623927],[0.57650716,3.8581741],[0.57922963,3.854146],[0.5819521,3.8499846],[0.58467456,3.8450022],[0.58739702,3.8422534],[0.59011948,3.8380919],[0.59284194,3.8341596],[0.5955644,3.8309333],[0.59828687,3.8272109],[0.60100935,3.823164],[0.60373182,3.8192315],[0.60645429,3.8159864],[0.60917677,3.8123021],[0.61189925,3.8090379],[0.61462172,3.8071671],[0.61734419,3.8040555],[0.62006666,3.8013639],[0.62278914,3.7970879],[0.62551162,3.7953317],[0.62823408,3.7920673],[0.63095656,3.788383],[0.63367903,3.7855389],[0.6364015,3.7838206],[0.63912397,3.78111],[0.64184645,3.7794874],[0.64456893,3.7769294],[0.6472914,3.773608],[0.65001389,3.7695992],[0.65273637,3.7690265],[0.65545884,3.7662776],[0.65818131,3.7642922],[0.66090379,3.7626889],[0.66362625,3.7603791],[0.66634874,3.7575538],[0.66907121,3.7552056],[0.67179369,3.7533159],[0.67451616,3.7507198],[0.67723865,3.7487535],[0.67996113,3.7471499],[0.68268361,3.7442865],[0.68540608,3.7423012],[0.68812855,3.7400677],[0.69085103,3.7385788],[0.6935735,3.7345319],[0.69629597,3.7339211],[0.69901843,3.7301605],[0.7017409,3.7301033],[0.70446338,3.7278316],[0.70718585,3.7251589],[0.70990833,3.723861],[0.71263081,3.7215703],[0.71535328,3.7191267],[0.71807574,3.7172751],[0.72079822,3.7157097],[0.72352069,3.7130945],[0.72624317,3.7099447],[0.72896564,3.7071004],[0.7316881,3.7045615],[0.73441057,3.703588],[0.73713303,3.70208],[0.73985551,3.7002664],[0.74257799,3.6972122],[0.74530047,3.6952841],[0.74802293,3.6929362],[0.7507454,3.6898055],[0.75346787,3.6890991],[0.75619034,3.686522],[0.75891281,3.6849759],[0.76163529,3.6821697],[0.76435776,3.6808143],[0.76708024,3.6786573],[0.7698027,3.6761947],[0.77252517,3.674763],[0.77524765,3.6712887],[0.77797012,3.6697233],[0.78069258,3.6678908],[0.78341506,3.6652565],[0.78613753,3.6630611],[0.78885999,3.660274],[0.79158246,3.6583652],[0.79430494,3.6554828],[0.79702741,3.6522949],[0.79974987,3.6499848],[0.80247234,3.6470451],[0.8051948,3.6405547],[0.80791727,3.6383405],[0.81063974,3.635076],[0.81336221,3.633549],[0.81608468,3.6322317],[0.81880714,3.6306856],[0.82152961,3.6283948],[0.82425208,3.6268487],[0.82697453,3.6243098],[0.829697,3.6223626],[0.83241946,3.6193655],[0.83514192,3.6177621],[0.83786439,3.6158531],[0.84058684,3.6128371],[0.84330931,3.6118062],[0.84603177,3.6094582],[0.84875424,3.6072438],[0.8514767,3.6049912],[0.85419916,3.6030822],[0.85692162,3.6012688],[0.85964409,3.5995889],[0.86236656,3.5976417],[0.86508902,3.5951984],[0.86781149,3.593843],[0.87053395,3.5916286],[0.87325642,3.5894907],[0.87597888,3.587429],[0.87870135,3.5852909],[0.88142383,3.5834775],[0.8841463,3.5817785],[0.88686877,3.5801177],[0.88959124,3.5778842],[0.89231371,3.5763381],[0.8950362,3.5737801],[0.89775868,3.5721002],[0.90048116,3.5702102],[0.90320364,3.5684922],[0.90592613,3.5672133],[1.0,3.52302167],])frompybammimportexp,constantsdefgraphite_LGM50_electrolyte_exchange_current_density_Chen2020(c_e,c_s_surf,c_n_max,T):m_ref=6.48e-7# (A/m2)(m3/mol)**1.5 - includes ref concentrationsE_r=35000arrhenius=exp(E_r/constants.R*(1/298.15-1/T))returnm_ref*arrhenius*c_e**0.5*c_s_surf**0.5*(c_n_max-c_s_surf)**0.5defnmc_LGM50_electrolyte_exchange_current_density_Chen2020(c_e,c_s_surf,c_p_max,T):m_ref=3.42e-6# (A/m2)(m3/mol)**1.5 - includes ref concentrationsE_r=17800arrhenius=exp(E_r/constants.R*(1/298.15-1/T))returnm_ref*arrhenius*c_e**0.5*c_s_surf**0.5*(c_p_max-c_s_surf)**0.5values={"Negative electrode thickness [m]":8.52e-05,"Separator thickness [m]":1.2e-05,"Positive electrode thickness [m]":7.56e-05,"Electrode height [m]":0.065,"Electrode width [m]":1.58,"Nominal cell capacity [A.h]":5.0,"Typical current [A]":5.0,"Current function [A]":5.0,"Maximum concentration in negative electrode [mol.m-3]":33133.0,"Negative electrode diffusivity [m2.s-1]":3.3e-14,"Negative electrode OCP [V]":("graphite_LGM50_ocp_Chen2020",neg_ocp),"Negative electrode porosity":0.25,"Negative electrode active material volume fraction":0.75,"Negative particle radius [m]":5.86e-06,"Negative electrode Bruggeman coefficient (electrolyte)":1.5,"Negative electrode Bruggeman coefficient (electrode)":1.5,"Negative electrode electrons in reaction":1.0,"Negative electrode exchange-current density [A.m-2]":graphite_LGM50_electrolyte_exchange_current_density_Chen2020,"Negative electrode OCP entropic change [V.K-1]":0.0,"Maximum concentration in positive electrode [mol.m-3]":63104.0,"Positive electrode diffusivity [m2.s-1]":4e-15,"Positive electrode OCP [V]":("nmc_LGM50_ocp_Chen2020",pos_ocp),"Positive electrode porosity":0.335,"Positive electrode active material volume fraction":0.665,"Positive particle radius [m]":5.22e-06,"Positive electrode Bruggeman coefficient (electrolyte)":1.5,"Positive electrode Bruggeman coefficient (electrode)":1.5,"Positive electrode electrons in reaction":1.0,"Positive electrode exchange-current density [A.m-2]":nmc_LGM50_electrolyte_exchange_current_density_Chen2020,"Positive electrode OCP entropic change [V.K-1]":0.0,"Separator porosity":0.47,"Separator Bruggeman coefficient (electrolyte)":1.5,"Typical electrolyte concentration [mol.m-3]":1000.0,"Reference temperature [K]":298.15,"Ambient temperature [K]":298.15,"Number of electrodes connected in parallel to make a cell":1.0,"Number of cells connected in series to make a battery":1.0,"Lower voltage cut-off [V]":2.5,"Upper voltage cut-off [V]":4.4,"Initial concentration in electrolyte [mol.m-3]":1000,"Initial concentration in negative electrode [mol.m-3]":29866.0,"Initial concentration in positive electrode [mol.m-3]":17038.0,"Initial temperature [K]":298.15,}param=pybamm.ParameterValues(values)param
currentfunc=param["Current function [A]"]time=pybamm.linspace(0,120,60)evaluated=param.evaluate(currentfunc(time))evaluated=pybamm.Array(evaluated)pybamm.plot(time,evaluated)
[20]:
<Axes: >
Taking another such example:
Plotting “Negative electrode exchange-current density [A.m-2]”#
[21]:
negative_electrode_exchange_current_density=param["Negative electrode exchange-current density [A.m-2]"]x=pybamm.linspace(3000,6000,100)c_n_max=param["Maximum concentration in negative electrode [mol.m-3]"]evaluated=param.evaluate(negative_electrode_exchange_current_density(1000,x,c_n_max,300))evaluated=pybamm.Array(evaluated)pybamm.plot(x,evaluated)
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Chang-Hui Chen, Ferran Brosa Planella, Kieran O'Regan, Dominika Gastol, W. Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[5] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[6] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
Tip
An interactive online version of this notebook is available, which can be
accessed via
WARNING: This is a new, experimental feature, and the API may change in future.
Callbacks provide hooks for users to interact with the different parts of the PyBaMM pipeline, for example to log, save, or visualize outputs of intermediate functions.
A list of available callbacks can be found in the API docs. Any number of callbacks can be provided as a list, and they will each be called in turn in the order provided.
%pip install "pybamm[plot,cite]" -q
importpybammmodel=pybamm.lithium_ion.DFN()experiment=pybamm.Experiment([("Discharge at C/5 for 10 hours or until 3.3 V","Charge at 1 A until 4.1 V","Hold at 4.1 V until 10 mA",),]*3)sim=pybamm.Simulation(model,experiment=experiment)
Note: you may need to restart the kernel to use updated packages.
The LoggingCallback can be used to log the progress of the simulation. The default is to print to stdout (this callback is automatically created if no other LoggingCallback exists)
[2]:
pybamm.set_logging_level("NOTICE")sim.solve();
2022-03-10 10:17:57.074 - [NOTICE] callbacks.on_cycle_start(174): Cycle 1/3 (18.917 ms elapsed) --------------------
2022-03-10 10:17:57.074 - [NOTICE] callbacks.on_step_start(182): Cycle 1/3, step 1/3: Discharge at C/5 for 10 hours or until 3.3 V
2022-03-10 10:17:58.139 - [NOTICE] callbacks.on_step_start(182): Cycle 1/3, step 2/3: Charge at 1 A until 4.1 V
2022-03-10 10:17:58.505 - [NOTICE] callbacks.on_step_start(182): Cycle 1/3, step 3/3: Hold at 4.1 V until 10 mA
2022-03-10 10:17:58.952 - [NOTICE] callbacks.on_cycle_start(174): Cycle 2/3 (1.898 s elapsed) --------------------
2022-03-10 10:17:58.953 - [NOTICE] callbacks.on_step_start(182): Cycle 2/3, step 1/3: Discharge at C/5 for 10 hours or until 3.3 V
2022-03-10 10:18:00.203 - [NOTICE] callbacks.on_step_start(182): Cycle 2/3, step 2/3: Charge at 1 A until 4.1 V
2022-03-10 10:18:00.464 - [NOTICE] callbacks.on_step_start(182): Cycle 2/3, step 3/3: Hold at 4.1 V until 10 mA
2022-03-10 10:18:00.675 - [NOTICE] callbacks.on_cycle_start(174): Cycle 3/3 (3.620 s elapsed) --------------------
2022-03-10 10:18:00.675 - [NOTICE] callbacks.on_step_start(182): Cycle 3/3, step 1/3: Discharge at C/5 for 10 hours or until 3.3 V
2022-03-10 10:18:01.963 - [NOTICE] callbacks.on_step_start(182): Cycle 3/3, step 2/3: Charge at 1 A until 4.1 V
2022-03-10 10:18:02.232 - [NOTICE] callbacks.on_step_start(182): Cycle 3/3, step 3/3: Hold at 4.1 V until 10 mA
2022-03-10 10:18:02.452 - [NOTICE] callbacks.on_experiment_end(222): Finish experiment simulation, took 3.620 s
or to a separate file
[3]:
callback=pybamm.callbacks.LoggingCallback("output.log")sim.solve(callbacks=callback)# Read the file that has been written, which was saved to callback.logfilewithopen(callback.logfile)asf:print(f.read())# Remove the log fileimportosos.remove(callback.logfile)
2022-03-10 10:18:02.483 - [NOTICE] callbacks.on_cycle_start(174): Cycle 1/3 (24.357 ms elapsed) --------------------
2022-03-10 10:18:02.483 - [NOTICE] callbacks.on_step_start(182): Cycle 1/3, step 1/3: Discharge at C/5 for 10 hours or until 3.3 V
2022-03-10 10:18:03.799 - [NOTICE] callbacks.on_step_start(182): Cycle 1/3, step 2/3: Charge at 1 A until 4.1 V
2022-03-10 10:18:04.065 - [NOTICE] callbacks.on_step_start(182): Cycle 1/3, step 3/3: Hold at 4.1 V until 10 mA
2022-03-10 10:18:04.394 - [NOTICE] callbacks.on_cycle_start(174): Cycle 2/3 (1.935 s elapsed) --------------------
2022-03-10 10:18:04.394 - [NOTICE] callbacks.on_step_start(182): Cycle 2/3, step 1/3: Discharge at C/5 for 10 hours or until 3.3 V
2022-03-10 10:18:05.607 - [NOTICE] callbacks.on_step_start(182): Cycle 2/3, step 2/3: Charge at 1 A until 4.1 V
2022-03-10 10:18:05.858 - [NOTICE] callbacks.on_step_start(182): Cycle 2/3, step 3/3: Hold at 4.1 V until 10 mA
2022-03-10 10:18:06.059 - [NOTICE] callbacks.on_cycle_start(174): Cycle 3/3 (3.600 s elapsed) --------------------
2022-03-10 10:18:06.059 - [NOTICE] callbacks.on_step_start(182): Cycle 3/3, step 1/3: Discharge at C/5 for 10 hours or until 3.3 V
2022-03-10 10:18:07.280 - [NOTICE] callbacks.on_step_start(182): Cycle 3/3, step 2/3: Charge at 1 A until 4.1 V
2022-03-10 10:18:07.539 - [NOTICE] callbacks.on_step_start(182): Cycle 3/3, step 3/3: Hold at 4.1 V until 10 mA
2022-03-10 10:18:07.743 - [NOTICE] callbacks.on_experiment_end(222): Finish experiment simulation, took 3.600 s
Custom callbacks should subclass the class pybamm.callbacks.Callback class, and implement a subset of the callback methods on_<event>, which all take as input the dictionary logs.
[4]:
classCustomCallback(pybamm.callbacks.Callback):defon_experiment_end(self,logs):print(f"We are at the end of the simulation. Logs are {logs}")
[5]:
# Note the default `LoggingCallback` is also calledsim.solve(callbacks=CustomCallback());
2022-03-10 10:18:07.776 - [NOTICE] callbacks.on_cycle_start(174): Cycle 1/3 (24.415 ms elapsed) --------------------
2022-03-10 10:18:07.776 - [NOTICE] callbacks.on_step_start(182): Cycle 1/3, step 1/3: Discharge at C/5 for 10 hours or until 3.3 V
2022-03-10 10:18:09.125 - [NOTICE] callbacks.on_step_start(182): Cycle 1/3, step 2/3: Charge at 1 A until 4.1 V
2022-03-10 10:18:09.390 - [NOTICE] callbacks.on_step_start(182): Cycle 1/3, step 3/3: Hold at 4.1 V until 10 mA
2022-03-10 10:18:09.714 - [NOTICE] callbacks.on_cycle_start(174): Cycle 2/3 (1.963 s elapsed) --------------------
2022-03-10 10:18:09.714 - [NOTICE] callbacks.on_step_start(182): Cycle 2/3, step 1/3: Discharge at C/5 for 10 hours or until 3.3 V
2022-03-10 10:18:11.025 - [NOTICE] callbacks.on_step_start(182): Cycle 2/3, step 2/3: Charge at 1 A until 4.1 V
2022-03-10 10:18:11.288 - [NOTICE] callbacks.on_step_start(182): Cycle 2/3, step 3/3: Hold at 4.1 V until 10 mA
2022-03-10 10:18:11.498 - [NOTICE] callbacks.on_cycle_start(174): Cycle 3/3 (3.747 s elapsed) --------------------
2022-03-10 10:18:11.499 - [NOTICE] callbacks.on_step_start(182): Cycle 3/3, step 1/3: Discharge at C/5 for 10 hours or until 3.3 V
2022-03-10 10:18:12.748 - [NOTICE] callbacks.on_step_start(182): Cycle 3/3, step 2/3: Charge at 1 A until 4.1 V
2022-03-10 10:18:13.007 - [NOTICE] callbacks.on_step_start(182): Cycle 3/3, step 3/3: Hold at 4.1 V until 10 mA
2022-03-10 10:18:13.230 - [NOTICE] callbacks.on_experiment_end(222): Finish experiment simulation, took 3.747 s
We are at the end of the simulation of the simulation. Logs are {'stopping conditions': {'voltage': None, 'capacity': None}, 'cycle number': (3, 3), 'elapsed time': pybamm.TimerTime(3.7469801669999967), 'step number': (3, 3), 'step operating conditions': 'Hold at 4.1 V until 10 mA', 'termination': 'event: Current cut-off (negative) [A] [experiment]', 'summary variables': {'Minimum measured discharge capacity [A.h]': -0.16806782223941116, 'Maximum measured discharge capacity [A.h]': 0.6889979156579616, 'Measured capacity [A.h]': 0.8570657378973728, 'Minimum voltage [V]': 3.300010000000005, 'Maximum voltage [V]': 4.100000000000016, 'Positive electrode capacity [A.h]': 1.9464430360887066, 'Change in positive electrode capacity [A.h]': 0.0, 'Loss of active material in positive electrode [%]': 1.1102230246251565e-14, 'Change in loss of active material in positive electrode [%]': 0.0, 'Loss of lithium inventory [%]': -2.220446049250313e-14, 'Change in loss of lithium inventory [%]': 0.0, 'Loss of lithium inventory, including electrolyte [%]': 0.0, 'Change in loss of lithium inventory, including electrolyte [%]': 0.0, 'Total lithium [mol]': 0.0799932053645051, 'Change in total lithium [mol]': 0.0, 'Total lithium in electrolyte [mol]': 0.002410514999999987, 'Change in total lithium in electrolyte [mol]': -2.168404344971009e-18, 'Total lithium in positive electrode [mol]': 0.037303833837759315, 'Change in total lithium in positive electrode [mol]': 6.938893903907228e-18, 'Total lithium in particles [mol]': 0.07758269036450512, 'Change in total lithium in particles [mol]': 0.0, 'Total lithium lost [mol]': 0.0, 'Change in total lithium lost [mol]': 0.0, 'Total lithium lost from particles [mol]': -1.3877787807814457e-17, 'Change in total lithium lost from particles [mol]': 0.0, 'Total lithium lost from electrolyte [mol]': 1.2576745200831851e-17, 'Change in total lithium lost from electrolyte [mol]': 2.168404344971009e-18, 'Loss of lithium to SEI [mol]': 0.0, 'Change in loss of lithium to SEI [mol]': 0.0, 'Loss of capacity to SEI [A.h]': 0.0, 'Change in loss of capacity to SEI [A.h]': 0.0, 'Total lithium lost to side reactions [mol]': 0.0, 'Change in total lithium lost to side reactions [mol]': 0.0, 'Total capacity lost to side reactions [A.h]': 0.0, 'Change in total capacity lost to side reactions [A.h]': 0.0, 'Local ECM resistance [Ohm]': -0.1921801399643974, 'Change in local ECM resistance [Ohm]': -0.3385111526792064, 'Negative electrode capacity [A.h]': 1.139331489107912, 'Change in negative electrode capacity [A.h]': 0.0, 'Loss of active material in negative electrode [%]': -2.220446049250313e-14, 'Change in loss of active material in negative electrode [%]': 0.0, 'Total lithium in negative electrode [mol]': 0.0402788565267458, 'Change in total lithium in negative electrode [mol]': -6.938893903907228e-18, 'Loss of lithium to lithium plating [mol]': 0.0, 'Change in loss of lithium to lithium plating [mol]': 0.0, 'Loss of capacity to lithium plating [A.h]': 0.0, 'Change in loss of capacity to lithium plating [A.h]': 0.0, 'x_100': 0.9493038520218161, 'y_100': 0.5126064431891194, 'C': 0.8728195070455337, 'x_0': 0.18322346594476246, 'y_0': 0.9610241419672079, 'Un(x_100)': 0.07517904666112207, 'Un(x_0)': 0.2527477655253842, 'Up(y_100)': 4.175179046661121, 'Up(y_0)': 3.357747765525383, 'Up(y_100) - Un(x_100)': 4.099999999999999, 'Up(y_0) - Un(x_0)': 3.1049999999999986, 'n_Li_100': 0.07758269036450512, 'n_Li_0': 0.07758269036450512, 'n_Li': 0.07758269036450512, 'C_n': 1.139331489107912, 'C_p': 1.9464430360887066, 'C_n * (x_100 - x_0)': 0.8728195070455337, 'C_p * (y_100 - y_0)': 0.8728195070455337, 'Capacity [A.h]': 0.8728195070455337}}
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[5] Peyman Mohtat, Suhak Lee, Jason B Siegel, and Anna G Stefanopoulou. Towards better estimability of electrode-specific state of health: decoding the cell expansion. Journal of Power Sources, 427:101–111, 2019.
[6] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[7] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
Tip
An interactive online version of this notebook is available, which can be
accessed via
Termination of a step can be specified using a few standard strings (e.g. “4.2V” for voltage, “1 A” for current, “C/2” for C-rate), or via a custom termination step. The custom termination step can be specified based on any variable in the model. Below, we show an example where we specify a custom termination step based on keeping the anode potential above 0V, which is a common limit used to avoid lithium plating,
[2]:
# Set up model and parametersmodel=pybamm.lithium_ion.DFN()# add anode potential as a variable# we use the potential at the separator interface since that is the minimum potential# during charging (plating is most likely to occur first at the separator interface)model.variables["Anode potential [V]"]=model.variables["Negative electrode surface potential difference at separator interface [V]"]parameter_values=pybamm.ParameterValues("Chen2020")# Create a custom termination event for the anode potential cut-off at 0.02V# We use 0.02V instead of 0V to give a safety factordefanode_potential_cutoff(variables):returnvariables["Anode potential [V]"]-0.02# The CustomTermination class takes a name and functionanode_potential_termination=pybamm.step.CustomTermination(name="Anode potential cut-off [V]",event_function=anode_potential_cutoff)# Provide a list of termination events, each step will stop whenever the first# termination event is reachedterminations=[anode_potential_termination,"4.2V"]# Set up multi-step CC experiment with the customer terminations followed# by a voltage holdexperiment=pybamm.Experiment([(pybamm.step.c_rate(-1,termination=terminations),pybamm.step.c_rate(-0.5,termination=terminations),pybamm.step.c_rate(-0.25,termination=terminations),"Hold at 4.2V until C/50",)])# Set up simulationsim=pybamm.Simulation(model,parameter_values=parameter_values,experiment=experiment)# for a charge we start as SOC 0sim.solve(initial_soc=0)
[2]:
<pybamm.solvers.solution.Solution at 0x11fcd1e10>
[3]:
# Plotplot=pybamm.QuickPlot(sim.solution,["Current [A]","Voltage [V]","Anode potential [V]",],)plot.plot(0)# Plot the limits used in the termination events to check they are not surpassedplot.axes.by_variable("Voltage [V]").axhline(4.2,color="k",linestyle=":")plot.axes.by_variable("Anode potential [V]").axhline(0.02,color="k",linestyle=":")
[3]:
<matplotlib.lines.Line2D at 0x16e37f9d0>
We can check which events were reached by each step
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Chang-Hui Chen, Ferran Brosa Planella, Kieran O'Regan, Dominika Gastol, W. Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.
[3] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[4] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[5] Peyman Mohtat, Suhak Lee, Jason B Siegel, and Anna G Stefanopoulou. Towards better estimability of electrode-specific state of health: decoding the cell expansion. Journal of Power Sources, 427:101–111, 2019.
[6] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[7] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
[8] Andrew Weng, Jason B Siegel, and Anna Stefanopoulou. Differential voltage analysis for battery manufacturing process control. arXiv preprint arXiv:2303.07088, 2023.
[ ]:
Tip
An interactive online version of this notebook is available, which can be
accessed via
This notebook introduces functionality for simulating user case in which the experiment steps are triggered at a certain point in time.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammfromdatetimeimportdatetime
[notice] A new release of pip is available: 23.0.1 -> 23.1.2[notice] To update, run: pip install --upgrade pip
Note: you may need to restart the kernel to use updated packages.
Let’s start defining a model to illustrate this functionality, in this case we choose the SPM
[2]:
model=pybamm.lithium_ion.SPM()
Usually we define an experiment such that each step is triggered when the previous step is completed. For example, in this case we do a 1C discharge for 20 minutes and then a C/3 charge for 10 minutes. The charge step starts after 20 minutes, i.e. once the discharge step is finished.
[3]:
experiment=pybamm.Experiment(["Discharge at 1C for 20 minutes","Charge at C/3 for 10 minutes"])sim=pybamm.Simulation(model,experiment=experiment)sim.solve()sim.plot()
[3]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7f8910adb4f0>
However, if we want to represent a realistic user case we might certain experiments to be run at a certain time instead, even if that means cutting short the previous step. In this case we can pass a starting time as a keyword argument in the pybamm.step.string method. The start_time should be passed as a datetime.datetime object.
[4]:
s=pybamm.step.stringexperiment=pybamm.Experiment([s("Discharge at 1C for 1 hour",start_time=datetime(1,1,1,8,0,0)),s("Charge at C/3 for 10 minutes",start_time=datetime(1,1,1,8,30,0)),s("Discharge at C/2 for 30 minutes",start_time=datetime(1,1,1,9,0,0)),s("Rest for 1 hour"),])sim=pybamm.Simulation(model,experiment=experiment)sim.solve()sim.plot()
[4]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7f8910b8e250>
In the example above, we note that the first step (1C discharge) is cut short as the second step (C/3 charge) start time occurs before the end of the first step. On the other hand, an additional resting period is added after the second step as the third step (C/2 discharge) start time is 20 minutes later than the end of the second step. The final step does not have a start time so it is triggered immediately after the previous step. Note that if the argument start_time is used in an
experiment, the first step should always have a start_time, otherwise the solver will throw an error.
Note that you can use the datetime.strptime (see the docs for more info) function to convert a string to a datetime object. For example, to start the experiment at 8:30 on the 2nd of January 2023, you can use
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[4] Peyman Mohtat, Suhak Lee, Jason B Siegel, and Anna G Stefanopoulou. Towards better estimability of electrode-specific state of health: decoding the cell expansion. Journal of Power Sources, 427:101–111, 2019.
[5] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[6] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
[7] Andrew Weng, Jason B Siegel, and Anna Stefanopoulou. Differential voltage analysis for battery manufacturing process control. arXiv preprint arXiv:2303.07088, 2023.
[ ]:
Tip
An interactive online version of this notebook is available, which can be
accessed via
Degradation experiments with reference performance tests#
When running degradation experiments in the lab, it is important to use reference performance tests (RPTs) to measure capacity and other key metrics, otherwise the experimental contitions will interfere with the measurement! In PyBaMM, you can run simulations with RPTs using the Experiment class.
[1]:
%pip install pybamm[plot,cite] -q # install PyBaMM if it is not installed
importpybammimportmatplotlib.pyplotaspltimportosos.chdir(pybamm.__path__[0]+"/..")
Note: you may need to restart the kernel to use updated packages.
Define three different experiments using the Experiment class: * cccv_experiment is a cycle ageing experiment * charge_experiment charges to full after N ageing cycles * rpt_experiment is a C/3 discharge in this case, but can also contain a charge, GITT, EIS and other procedures
[3]:
N=10cccv_experiment=pybamm.Experiment([("Charge at 1C until 4.2V","Hold at 4.2V until C/50","Discharge at 1C until 3V","Rest for 1 hour",)]*N)charge_experiment=pybamm.Experiment([("Charge at 1C until 4.2V","Hold at 4.2V until C/50",)])rpt_experiment=pybamm.Experiment([("Discharge at C/3 until 3V",)])
Run the ageing, charge and RPT experiments in order by feeding the previous solution into the solve command:
<pybamm.plotting.quick_plot.QuickPlot at 0x7ff13da91760>
PyBaMM’s summary variables track important cell-level degradation veriables at the end of each cycle. The charge and RPT cycles are also counted, making a total of 12 cycles:
[6]:
pybamm.plot_summary_variables(rpt_sol);
Repeat the procedure four times:
[7]:
cccv_sols=[]charge_sols=[]rpt_sols=[]M=5foriinrange(M):ifi!=0:# skip the first set of ageing cycles because it's already been donesim=pybamm.Simulation(model,experiment=cccv_experiment,parameter_values=parameter_values)cccv_sol=sim.solve(starting_solution=rpt_sol)sim=pybamm.Simulation(model,experiment=charge_experiment,parameter_values=parameter_values)charge_sol=sim.solve(starting_solution=cccv_sol)sim=pybamm.Simulation(model,experiment=rpt_experiment,parameter_values=parameter_values)rpt_sol=sim.solve(starting_solution=charge_sol)cccv_sols.append(cccv_sol)charge_sols.append(charge_sol)rpt_sols.append(rpt_sol)
You can plot any RPT cycle. The last one is chosen here.
<pybamm.plotting.quick_plot.QuickPlot at 0x7ff12d79b610>
One way of demonstrating how useful RPTs are is to plot the discharge capacity for each cycle. It is convenient to use the final rpt_sol because it also contains all previous simulations.
The ageing cycles have a higher discharge rate than the RPT cycles and therefore have a slightly lower discharge capacity. (The charge cycles are not included because they have no discharge capacity.)
Finally, plot the summary variables for the entire experiment run.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Ferran Brosa Planella and W. Dhammika Widanage. Systematic derivation of a Single Particle Model with Electrolyte and Side Reactions (SPMe+SR) for degradation of lithium-ion batteries. Submitted for publication, ():, 2022. doi:.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[5] Peyman Mohtat, Suhak Lee, Jason B Siegel, and Anna G Stefanopoulou. Towards better estimability of electrode-specific state of health: decoding the cell expansion. Journal of Power Sources, 427:101–111, 2019.
[6] Peyman Mohtat, Suhak Lee, Valentin Sulzer, Jason B. Siegel, and Anna G. Stefanopoulou. Differential Expansion and Voltage Model for Li-ion Batteries at Practical Charging Rates. Journal of The Electrochemical Society, 167(11):110561, 2020. doi:10.1149/1945-7111/aba5d1.
[7] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[8] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
[9] Andrew Weng, Jason B Siegel, and Anna Stefanopoulou. Differential voltage analysis for battery manufacturing process control. arXiv preprint arXiv:2303.07088, 2023.
[10] Xiao Guang Yang, Yongjun Leng, Guangsheng Zhang, Shanhai Ge, and Chao Yang Wang. Modeling of lithium plating induced aging of lithium-ion batteries: transition from linear to nonlinear aging. Journal of Power Sources, 360:28–40, 2017. doi:10.1016/j.jpowsour.2017.05.110.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In the interest of simplicity and running time, we consider a SPM with SEI effects leading to linear degradation, with parameter values chosen so that the capacity fades by 20% in just a few cycles
We initialize the concentration in each electrode at 100% State of Charge
[3]:
# Calculate stoichiometries at 100% SOCparameter_values.set_initial_stoichiometries(1);
We can now simulate a single CCCV cycle using the Experiment class (see this notebook for more details)
[4]:
pybamm.set_logging_level("NOTICE")experiment=pybamm.Experiment([("Discharge at 1C until 3V","Rest for 1 hour","Charge at 1C until 4.2V","Hold at 4.2V until C/50",)])sim=pybamm.Simulation(spm,experiment=experiment,parameter_values=parameter_values)sol=sim.solve()
2022-12-04 21:24:29.130 - [NOTICE] callbacks.on_cycle_start(174): Cycle 1/1 (24.500 us elapsed) --------------------
2022-12-04 21:24:29.131 - [NOTICE] callbacks.on_step_start(182): Cycle 1/1, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:29.172 - [NOTICE] callbacks.on_step_start(182): Cycle 1/1, step 2/4: Rest for 1 hour
2022-12-04 21:24:29.199 - [NOTICE] callbacks.on_step_start(182): Cycle 1/1, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:29.237 - [NOTICE] callbacks.on_step_start(182): Cycle 1/1, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:29.372 - [NOTICE] callbacks.on_experiment_end(222): Finish experiment simulation, took 241.861 ms
Alternatively, we can simulate many CCCV cycles. Here we simulate either 100 cycles or until the capacity is 80% of the initial capacity, whichever is first. The capacity is calculated by the eSOH model
[5]:
experiment=pybamm.Experiment([("Discharge at 1C until 3V","Rest for 1 hour","Charge at 1C until 4.2V","Hold at 4.2V until C/50",)]*500,termination="80% capacity",)sim=pybamm.Simulation(spm,experiment=experiment,parameter_values=parameter_values)sol=sim.solve()
2022-12-04 21:24:30.209 - [NOTICE] callbacks.on_cycle_start(174): Cycle 1/500 (25.250 us elapsed) --------------------
2022-12-04 21:24:30.209 - [NOTICE] callbacks.on_step_start(182): Cycle 1/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:30.248 - [NOTICE] callbacks.on_step_start(182): Cycle 1/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:30.275 - [NOTICE] callbacks.on_step_start(182): Cycle 1/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:30.315 - [NOTICE] callbacks.on_step_start(182): Cycle 1/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:30.460 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.941 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:30.460 - [NOTICE] callbacks.on_cycle_start(174): Cycle 2/500 (251.525 ms elapsed) --------------------
2022-12-04 21:24:30.461 - [NOTICE] callbacks.on_step_start(182): Cycle 2/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:30.486 - [NOTICE] callbacks.on_step_start(182): Cycle 2/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:30.501 - [NOTICE] callbacks.on_step_start(182): Cycle 2/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:30.523 - [NOTICE] callbacks.on_step_start(182): Cycle 2/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:30.566 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.913 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:30.567 - [NOTICE] callbacks.on_cycle_start(174): Cycle 3/500 (357.953 ms elapsed) --------------------
2022-12-04 21:24:30.567 - [NOTICE] callbacks.on_step_start(182): Cycle 3/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:30.593 - [NOTICE] callbacks.on_step_start(182): Cycle 3/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:30.607 - [NOTICE] callbacks.on_step_start(182): Cycle 3/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:30.630 - [NOTICE] callbacks.on_step_start(182): Cycle 3/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:30.674 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.886 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:30.675 - [NOTICE] callbacks.on_cycle_start(174): Cycle 4/500 (465.886 ms elapsed) --------------------
2022-12-04 21:24:30.675 - [NOTICE] callbacks.on_step_start(182): Cycle 4/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:30.700 - [NOTICE] callbacks.on_step_start(182): Cycle 4/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:30.713 - [NOTICE] callbacks.on_step_start(182): Cycle 4/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:30.737 - [NOTICE] callbacks.on_step_start(182): Cycle 4/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:30.781 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.859 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:30.782 - [NOTICE] callbacks.on_cycle_start(174): Cycle 5/500 (573.195 ms elapsed) --------------------
2022-12-04 21:24:30.782 - [NOTICE] callbacks.on_step_start(182): Cycle 5/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:30.807 - [NOTICE] callbacks.on_step_start(182): Cycle 5/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:30.821 - [NOTICE] callbacks.on_step_start(182): Cycle 5/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:30.845 - [NOTICE] callbacks.on_step_start(182): Cycle 5/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:30.894 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.833 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:30.895 - [NOTICE] callbacks.on_cycle_start(174): Cycle 6/500 (686.094 ms elapsed) --------------------
2022-12-04 21:24:30.895 - [NOTICE] callbacks.on_step_start(182): Cycle 6/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:30.916 - [NOTICE] callbacks.on_step_start(182): Cycle 6/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:30.930 - [NOTICE] callbacks.on_step_start(182): Cycle 6/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:30.949 - [NOTICE] callbacks.on_step_start(182): Cycle 6/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:30.996 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.807 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:30.996 - [NOTICE] callbacks.on_cycle_start(174): Cycle 7/500 (787.595 ms elapsed) --------------------
2022-12-04 21:24:30.997 - [NOTICE] callbacks.on_step_start(182): Cycle 7/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:31.018 - [NOTICE] callbacks.on_step_start(182): Cycle 7/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:31.032 - [NOTICE] callbacks.on_step_start(182): Cycle 7/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:31.051 - [NOTICE] callbacks.on_step_start(182): Cycle 7/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:31.099 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.781 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:31.100 - [NOTICE] callbacks.on_cycle_start(174): Cycle 8/500 (891.219 ms elapsed) --------------------
2022-12-04 21:24:31.100 - [NOTICE] callbacks.on_step_start(182): Cycle 8/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:31.120 - [NOTICE] callbacks.on_step_start(182): Cycle 8/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:31.134 - [NOTICE] callbacks.on_step_start(182): Cycle 8/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:31.153 - [NOTICE] callbacks.on_step_start(182): Cycle 8/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:31.200 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.756 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:31.201 - [NOTICE] callbacks.on_cycle_start(174): Cycle 9/500 (992.032 ms elapsed) --------------------
2022-12-04 21:24:31.201 - [NOTICE] callbacks.on_step_start(182): Cycle 9/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:31.225 - [NOTICE] callbacks.on_step_start(182): Cycle 9/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:31.240 - [NOTICE] callbacks.on_step_start(182): Cycle 9/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:31.263 - [NOTICE] callbacks.on_step_start(182): Cycle 9/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:31.313 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.732 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:31.313 - [NOTICE] callbacks.on_cycle_start(174): Cycle 10/500 (1.104 s elapsed) --------------------
2022-12-04 21:24:31.313 - [NOTICE] callbacks.on_step_start(182): Cycle 10/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:31.337 - [NOTICE] callbacks.on_step_start(182): Cycle 10/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:31.351 - [NOTICE] callbacks.on_step_start(182): Cycle 10/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:31.372 - [NOTICE] callbacks.on_step_start(182): Cycle 10/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:31.420 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.708 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:31.421 - [NOTICE] callbacks.on_cycle_start(174): Cycle 11/500 (1.212 s elapsed) --------------------
2022-12-04 21:24:31.421 - [NOTICE] callbacks.on_step_start(182): Cycle 11/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:31.443 - [NOTICE] callbacks.on_step_start(182): Cycle 11/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:31.458 - [NOTICE] callbacks.on_step_start(182): Cycle 11/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:31.483 - [NOTICE] callbacks.on_step_start(182): Cycle 11/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:31.539 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.684 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:31.540 - [NOTICE] callbacks.on_cycle_start(174): Cycle 12/500 (1.331 s elapsed) --------------------
2022-12-04 21:24:31.542 - [NOTICE] callbacks.on_step_start(182): Cycle 12/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:31.565 - [NOTICE] callbacks.on_step_start(182): Cycle 12/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:31.579 - [NOTICE] callbacks.on_step_start(182): Cycle 12/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:31.597 - [NOTICE] callbacks.on_step_start(182): Cycle 12/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:31.644 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.660 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:31.645 - [NOTICE] callbacks.on_cycle_start(174): Cycle 13/500 (1.436 s elapsed) --------------------
2022-12-04 21:24:31.645 - [NOTICE] callbacks.on_step_start(182): Cycle 13/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:31.665 - [NOTICE] callbacks.on_step_start(182): Cycle 13/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:31.680 - [NOTICE] callbacks.on_step_start(182): Cycle 13/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:31.700 - [NOTICE] callbacks.on_step_start(182): Cycle 13/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:31.751 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.637 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:31.751 - [NOTICE] callbacks.on_cycle_start(174): Cycle 14/500 (1.543 s elapsed) --------------------
2022-12-04 21:24:31.752 - [NOTICE] callbacks.on_step_start(182): Cycle 14/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:31.774 - [NOTICE] callbacks.on_step_start(182): Cycle 14/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:31.788 - [NOTICE] callbacks.on_step_start(182): Cycle 14/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:31.807 - [NOTICE] callbacks.on_step_start(182): Cycle 14/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:31.938 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.614 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:31.938 - [NOTICE] callbacks.on_cycle_start(174): Cycle 15/500 (1.729 s elapsed) --------------------
2022-12-04 21:24:31.939 - [NOTICE] callbacks.on_step_start(182): Cycle 15/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:31.959 - [NOTICE] callbacks.on_step_start(182): Cycle 15/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:31.972 - [NOTICE] callbacks.on_step_start(182): Cycle 15/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:31.991 - [NOTICE] callbacks.on_step_start(182): Cycle 15/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:32.045 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.592 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:32.046 - [NOTICE] callbacks.on_cycle_start(174): Cycle 16/500 (1.837 s elapsed) --------------------
2022-12-04 21:24:32.046 - [NOTICE] callbacks.on_step_start(182): Cycle 16/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:32.068 - [NOTICE] callbacks.on_step_start(182): Cycle 16/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:32.082 - [NOTICE] callbacks.on_step_start(182): Cycle 16/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:32.103 - [NOTICE] callbacks.on_step_start(182): Cycle 16/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:32.153 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.569 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:32.154 - [NOTICE] callbacks.on_cycle_start(174): Cycle 17/500 (1.945 s elapsed) --------------------
2022-12-04 21:24:32.154 - [NOTICE] callbacks.on_step_start(182): Cycle 17/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:32.179 - [NOTICE] callbacks.on_step_start(182): Cycle 17/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:32.193 - [NOTICE] callbacks.on_step_start(182): Cycle 17/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:32.215 - [NOTICE] callbacks.on_step_start(182): Cycle 17/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:32.267 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.548 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:32.268 - [NOTICE] callbacks.on_cycle_start(174): Cycle 18/500 (2.059 s elapsed) --------------------
2022-12-04 21:24:32.268 - [NOTICE] callbacks.on_step_start(182): Cycle 18/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:32.290 - [NOTICE] callbacks.on_step_start(182): Cycle 18/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:32.304 - [NOTICE] callbacks.on_step_start(182): Cycle 18/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:32.326 - [NOTICE] callbacks.on_step_start(182): Cycle 18/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:32.378 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.526 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:32.379 - [NOTICE] callbacks.on_cycle_start(174): Cycle 19/500 (2.170 s elapsed) --------------------
2022-12-04 21:24:32.380 - [NOTICE] callbacks.on_step_start(182): Cycle 19/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:32.403 - [NOTICE] callbacks.on_step_start(182): Cycle 19/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:32.417 - [NOTICE] callbacks.on_step_start(182): Cycle 19/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:32.436 - [NOTICE] callbacks.on_step_start(182): Cycle 19/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:32.489 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.505 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:32.489 - [NOTICE] callbacks.on_cycle_start(174): Cycle 20/500 (2.281 s elapsed) --------------------
2022-12-04 21:24:32.490 - [NOTICE] callbacks.on_step_start(182): Cycle 20/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:32.508 - [NOTICE] callbacks.on_step_start(182): Cycle 20/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:32.522 - [NOTICE] callbacks.on_step_start(182): Cycle 20/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:32.540 - [NOTICE] callbacks.on_step_start(182): Cycle 20/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:32.594 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.484 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:32.595 - [NOTICE] callbacks.on_cycle_start(174): Cycle 21/500 (2.386 s elapsed) --------------------
2022-12-04 21:24:32.595 - [NOTICE] callbacks.on_step_start(182): Cycle 21/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:32.615 - [NOTICE] callbacks.on_step_start(182): Cycle 21/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:32.630 - [NOTICE] callbacks.on_step_start(182): Cycle 21/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:32.649 - [NOTICE] callbacks.on_step_start(182): Cycle 21/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:32.705 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.463 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:32.706 - [NOTICE] callbacks.on_cycle_start(174): Cycle 22/500 (2.497 s elapsed) --------------------
2022-12-04 21:24:32.707 - [NOTICE] callbacks.on_step_start(182): Cycle 22/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:32.728 - [NOTICE] callbacks.on_step_start(182): Cycle 22/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:32.742 - [NOTICE] callbacks.on_step_start(182): Cycle 22/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:32.762 - [NOTICE] callbacks.on_step_start(182): Cycle 22/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:32.816 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.442 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:32.817 - [NOTICE] callbacks.on_cycle_start(174): Cycle 23/500 (2.608 s elapsed) --------------------
2022-12-04 21:24:32.817 - [NOTICE] callbacks.on_step_start(182): Cycle 23/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:32.840 - [NOTICE] callbacks.on_step_start(182): Cycle 23/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:32.855 - [NOTICE] callbacks.on_step_start(182): Cycle 23/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:32.876 - [NOTICE] callbacks.on_step_start(182): Cycle 23/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:32.932 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.422 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:32.932 - [NOTICE] callbacks.on_cycle_start(174): Cycle 24/500 (2.724 s elapsed) --------------------
2022-12-04 21:24:32.933 - [NOTICE] callbacks.on_step_start(182): Cycle 24/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:32.954 - [NOTICE] callbacks.on_step_start(182): Cycle 24/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:32.968 - [NOTICE] callbacks.on_step_start(182): Cycle 24/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:32.998 - [NOTICE] callbacks.on_step_start(182): Cycle 24/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:33.056 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.402 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:33.056 - [NOTICE] callbacks.on_cycle_start(174): Cycle 25/500 (2.848 s elapsed) --------------------
2022-12-04 21:24:33.057 - [NOTICE] callbacks.on_step_start(182): Cycle 25/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:33.078 - [NOTICE] callbacks.on_step_start(182): Cycle 25/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:33.093 - [NOTICE] callbacks.on_step_start(182): Cycle 25/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:33.114 - [NOTICE] callbacks.on_step_start(182): Cycle 25/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:33.166 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.382 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:33.167 - [NOTICE] callbacks.on_cycle_start(174): Cycle 26/500 (2.958 s elapsed) --------------------
2022-12-04 21:24:33.167 - [NOTICE] callbacks.on_step_start(182): Cycle 26/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:33.188 - [NOTICE] callbacks.on_step_start(182): Cycle 26/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:33.203 - [NOTICE] callbacks.on_step_start(182): Cycle 26/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:33.224 - [NOTICE] callbacks.on_step_start(182): Cycle 26/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:33.279 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.362 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:33.280 - [NOTICE] callbacks.on_cycle_start(174): Cycle 27/500 (3.071 s elapsed) --------------------
2022-12-04 21:24:33.280 - [NOTICE] callbacks.on_step_start(182): Cycle 27/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:33.302 - [NOTICE] callbacks.on_step_start(182): Cycle 27/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:33.316 - [NOTICE] callbacks.on_step_start(182): Cycle 27/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:33.335 - [NOTICE] callbacks.on_step_start(182): Cycle 27/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:33.391 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.343 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:33.392 - [NOTICE] callbacks.on_cycle_start(174): Cycle 28/500 (3.183 s elapsed) --------------------
2022-12-04 21:24:33.392 - [NOTICE] callbacks.on_step_start(182): Cycle 28/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:33.415 - [NOTICE] callbacks.on_step_start(182): Cycle 28/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:33.429 - [NOTICE] callbacks.on_step_start(182): Cycle 28/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:33.448 - [NOTICE] callbacks.on_step_start(182): Cycle 28/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:33.503 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.324 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:33.504 - [NOTICE] callbacks.on_cycle_start(174): Cycle 29/500 (3.295 s elapsed) --------------------
2022-12-04 21:24:33.505 - [NOTICE] callbacks.on_step_start(182): Cycle 29/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:33.525 - [NOTICE] callbacks.on_step_start(182): Cycle 29/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:33.539 - [NOTICE] callbacks.on_step_start(182): Cycle 29/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:33.557 - [NOTICE] callbacks.on_step_start(182): Cycle 29/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:33.612 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.305 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:33.613 - [NOTICE] callbacks.on_cycle_start(174): Cycle 30/500 (3.404 s elapsed) --------------------
2022-12-04 21:24:33.613 - [NOTICE] callbacks.on_step_start(182): Cycle 30/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:33.631 - [NOTICE] callbacks.on_step_start(182): Cycle 30/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:33.645 - [NOTICE] callbacks.on_step_start(182): Cycle 30/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:33.665 - [NOTICE] callbacks.on_step_start(182): Cycle 30/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:33.724 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.286 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:33.724 - [NOTICE] callbacks.on_cycle_start(174): Cycle 31/500 (3.516 s elapsed) --------------------
2022-12-04 21:24:33.725 - [NOTICE] callbacks.on_step_start(182): Cycle 31/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:33.748 - [NOTICE] callbacks.on_step_start(182): Cycle 31/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:33.766 - [NOTICE] callbacks.on_step_start(182): Cycle 31/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:33.788 - [NOTICE] callbacks.on_step_start(182): Cycle 31/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:33.847 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.267 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:33.848 - [NOTICE] callbacks.on_cycle_start(174): Cycle 32/500 (3.639 s elapsed) --------------------
2022-12-04 21:24:33.848 - [NOTICE] callbacks.on_step_start(182): Cycle 32/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:33.871 - [NOTICE] callbacks.on_step_start(182): Cycle 32/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:33.894 - [NOTICE] callbacks.on_step_start(182): Cycle 32/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:33.914 - [NOTICE] callbacks.on_step_start(182): Cycle 32/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:33.972 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.249 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:33.972 - [NOTICE] callbacks.on_cycle_start(174): Cycle 33/500 (3.763 s elapsed) --------------------
2022-12-04 21:24:33.972 - [NOTICE] callbacks.on_step_start(182): Cycle 33/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:33.995 - [NOTICE] callbacks.on_step_start(182): Cycle 33/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:34.010 - [NOTICE] callbacks.on_step_start(182): Cycle 33/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:34.031 - [NOTICE] callbacks.on_step_start(182): Cycle 33/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:34.091 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.231 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:34.092 - [NOTICE] callbacks.on_cycle_start(174): Cycle 34/500 (3.883 s elapsed) --------------------
2022-12-04 21:24:34.092 - [NOTICE] callbacks.on_step_start(182): Cycle 34/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:34.114 - [NOTICE] callbacks.on_step_start(182): Cycle 34/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:34.128 - [NOTICE] callbacks.on_step_start(182): Cycle 34/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:34.149 - [NOTICE] callbacks.on_step_start(182): Cycle 34/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:34.208 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.213 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:34.209 - [NOTICE] callbacks.on_cycle_start(174): Cycle 35/500 (4.000 s elapsed) --------------------
2022-12-04 21:24:34.209 - [NOTICE] callbacks.on_step_start(182): Cycle 35/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:34.231 - [NOTICE] callbacks.on_step_start(182): Cycle 35/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:34.246 - [NOTICE] callbacks.on_step_start(182): Cycle 35/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:34.263 - [NOTICE] callbacks.on_step_start(182): Cycle 35/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:34.420 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.195 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:34.421 - [NOTICE] callbacks.on_cycle_start(174): Cycle 36/500 (4.212 s elapsed) --------------------
2022-12-04 21:24:34.421 - [NOTICE] callbacks.on_step_start(182): Cycle 36/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:34.444 - [NOTICE] callbacks.on_step_start(182): Cycle 36/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:34.458 - [NOTICE] callbacks.on_step_start(182): Cycle 36/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:34.475 - [NOTICE] callbacks.on_step_start(182): Cycle 36/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:34.535 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.177 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:34.536 - [NOTICE] callbacks.on_cycle_start(174): Cycle 37/500 (4.327 s elapsed) --------------------
2022-12-04 21:24:34.536 - [NOTICE] callbacks.on_step_start(182): Cycle 37/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:34.558 - [NOTICE] callbacks.on_step_start(182): Cycle 37/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:34.573 - [NOTICE] callbacks.on_step_start(182): Cycle 37/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:34.590 - [NOTICE] callbacks.on_step_start(182): Cycle 37/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:34.650 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.160 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:34.650 - [NOTICE] callbacks.on_cycle_start(174): Cycle 38/500 (4.442 s elapsed) --------------------
2022-12-04 21:24:34.651 - [NOTICE] callbacks.on_step_start(182): Cycle 38/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:34.668 - [NOTICE] callbacks.on_step_start(182): Cycle 38/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:34.682 - [NOTICE] callbacks.on_step_start(182): Cycle 38/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:34.700 - [NOTICE] callbacks.on_step_start(182): Cycle 38/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:34.763 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.143 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:34.763 - [NOTICE] callbacks.on_cycle_start(174): Cycle 39/500 (4.555 s elapsed) --------------------
2022-12-04 21:24:34.764 - [NOTICE] callbacks.on_step_start(182): Cycle 39/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:34.782 - [NOTICE] callbacks.on_step_start(182): Cycle 39/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:34.797 - [NOTICE] callbacks.on_step_start(182): Cycle 39/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:34.814 - [NOTICE] callbacks.on_step_start(182): Cycle 39/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:34.875 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.126 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:34.876 - [NOTICE] callbacks.on_cycle_start(174): Cycle 40/500 (4.667 s elapsed) --------------------
2022-12-04 21:24:34.876 - [NOTICE] callbacks.on_step_start(182): Cycle 40/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:34.895 - [NOTICE] callbacks.on_step_start(182): Cycle 40/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:34.910 - [NOTICE] callbacks.on_step_start(182): Cycle 40/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:34.931 - [NOTICE] callbacks.on_step_start(182): Cycle 40/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:34.990 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.109 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:34.991 - [NOTICE] callbacks.on_cycle_start(174): Cycle 41/500 (4.782 s elapsed) --------------------
2022-12-04 21:24:34.991 - [NOTICE] callbacks.on_step_start(182): Cycle 41/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:35.009 - [NOTICE] callbacks.on_step_start(182): Cycle 41/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:35.026 - [NOTICE] callbacks.on_step_start(182): Cycle 41/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:35.046 - [NOTICE] callbacks.on_step_start(182): Cycle 41/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:35.109 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.092 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:35.110 - [NOTICE] callbacks.on_cycle_start(174): Cycle 42/500 (4.901 s elapsed) --------------------
2022-12-04 21:24:35.110 - [NOTICE] callbacks.on_step_start(182): Cycle 42/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:35.130 - [NOTICE] callbacks.on_step_start(182): Cycle 42/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:35.144 - [NOTICE] callbacks.on_step_start(182): Cycle 42/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:35.163 - [NOTICE] callbacks.on_step_start(182): Cycle 42/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:35.225 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.075 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:35.225 - [NOTICE] callbacks.on_cycle_start(174): Cycle 43/500 (5.016 s elapsed) --------------------
2022-12-04 21:24:35.225 - [NOTICE] callbacks.on_step_start(182): Cycle 43/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:35.246 - [NOTICE] callbacks.on_step_start(182): Cycle 43/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:35.260 - [NOTICE] callbacks.on_step_start(182): Cycle 43/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:35.281 - [NOTICE] callbacks.on_step_start(182): Cycle 43/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:35.341 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.059 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:35.342 - [NOTICE] callbacks.on_cycle_start(174): Cycle 44/500 (5.133 s elapsed) --------------------
2022-12-04 21:24:35.342 - [NOTICE] callbacks.on_step_start(182): Cycle 44/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:35.364 - [NOTICE] callbacks.on_step_start(182): Cycle 44/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:35.383 - [NOTICE] callbacks.on_step_start(182): Cycle 44/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:35.403 - [NOTICE] callbacks.on_step_start(182): Cycle 44/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:35.462 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.042 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:35.463 - [NOTICE] callbacks.on_cycle_start(174): Cycle 45/500 (5.254 s elapsed) --------------------
2022-12-04 21:24:35.463 - [NOTICE] callbacks.on_step_start(182): Cycle 45/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:35.484 - [NOTICE] callbacks.on_step_start(182): Cycle 45/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:35.498 - [NOTICE] callbacks.on_step_start(182): Cycle 45/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:35.514 - [NOTICE] callbacks.on_step_start(182): Cycle 45/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:35.577 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.026 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:35.584 - [NOTICE] callbacks.on_cycle_start(174): Cycle 46/500 (5.376 s elapsed) --------------------
2022-12-04 21:24:35.593 - [NOTICE] callbacks.on_step_start(182): Cycle 46/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:35.633 - [NOTICE] callbacks.on_step_start(182): Cycle 46/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:35.659 - [NOTICE] callbacks.on_step_start(182): Cycle 46/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:35.675 - [NOTICE] callbacks.on_step_start(182): Cycle 46/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:35.740 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.010 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:35.741 - [NOTICE] callbacks.on_cycle_start(174): Cycle 47/500 (5.532 s elapsed) --------------------
2022-12-04 21:24:35.741 - [NOTICE] callbacks.on_step_start(182): Cycle 47/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:35.769 - [NOTICE] callbacks.on_step_start(182): Cycle 47/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:35.784 - [NOTICE] callbacks.on_step_start(182): Cycle 47/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:35.800 - [NOTICE] callbacks.on_step_start(182): Cycle 47/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:35.864 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 3.994 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:35.864 - [NOTICE] callbacks.on_cycle_start(174): Cycle 48/500 (5.655 s elapsed) --------------------
2022-12-04 21:24:35.864 - [NOTICE] callbacks.on_step_start(182): Cycle 48/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:35.881 - [NOTICE] callbacks.on_step_start(182): Cycle 48/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:35.896 - [NOTICE] callbacks.on_step_start(182): Cycle 48/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:35.914 - [NOTICE] callbacks.on_step_start(182): Cycle 48/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:35.983 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 3.978 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:35.983 - [NOTICE] callbacks.on_cycle_start(174): Cycle 49/500 (5.774 s elapsed) --------------------
2022-12-04 21:24:35.983 - [NOTICE] callbacks.on_step_start(182): Cycle 49/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:36.000 - [NOTICE] callbacks.on_step_start(182): Cycle 49/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:36.015 - [NOTICE] callbacks.on_step_start(182): Cycle 49/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:36.039 - [NOTICE] callbacks.on_step_start(182): Cycle 49/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:36.104 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 3.962 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:36.105 - [NOTICE] callbacks.on_cycle_start(174): Cycle 50/500 (5.896 s elapsed) --------------------
2022-12-04 21:24:36.105 - [NOTICE] callbacks.on_step_start(182): Cycle 50/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:36.123 - [NOTICE] callbacks.on_step_start(182): Cycle 50/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:36.140 - [NOTICE] callbacks.on_step_start(182): Cycle 50/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:36.160 - [NOTICE] callbacks.on_step_start(182): Cycle 50/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:36.227 - [NOTICE] callbacks.on_cycle_end(201): Stopping experiment since capacity (3.947 Ah) is below stopping capacity (3.952 Ah).
2022-12-04 21:24:36.229 - [NOTICE] callbacks.on_experiment_end(222): Finish experiment simulation, took 6.019 s
We can plot standard variables like the current and voltage, but it isn’t very instructive on these timescales
[6]:
sol.plot(["Current [A]","Voltage [V]"])
[6]:
<pybamm.plotting.quick_plot.QuickPlot at 0x170561fd0>
Instead, we plot “summary variables”, which show how the battery degrades over time by various metrics. Some of the variables also have “Change in …”, which is how much that variable changes over each cycle. This can be achieved by using plot_summary_variables method of pybamm, which can also be used to compare “summary variables” extracted from 2 or more solutions.
[7]:
sorted(sol.summary_variables.keys())
[7]:
['Capacity [A.h]',
'Change in local ECM resistance [Ohm]',
'Change in loss of active material in negative electrode [%]',
'Change in loss of active material in positive electrode [%]',
'Change in loss of capacity to SEI [A.h]',
'Change in loss of capacity to SEI on cracks [A.h]',
'Change in loss of capacity to lithium plating [A.h]',
'Change in loss of lithium inventory [%]',
'Change in loss of lithium inventory, including electrolyte [%]',
'Change in loss of lithium to SEI [mol]',
'Change in loss of lithium to SEI on cracks [mol]',
'Change in loss of lithium to lithium plating [mol]',
'Change in negative electrode capacity [A.h]',
'Change in positive electrode capacity [A.h]',
'Change in throughput capacity [A.h]',
'Change in throughput energy [W.h]',
'Change in time [h]',
'Change in time [s]',
'Change in total capacity lost to side reactions [A.h]',
'Change in total lithium [mol]',
'Change in total lithium in electrolyte [mol]',
'Change in total lithium in negative electrode [mol]',
'Change in total lithium in particles [mol]',
'Change in total lithium in positive electrode [mol]',
'Change in total lithium lost [mol]',
'Change in total lithium lost from electrolyte [mol]',
'Change in total lithium lost from particles [mol]',
'Change in total lithium lost to side reactions [mol]',
'Cycle number',
'Local ECM resistance [Ohm]',
'Loss of active material in negative electrode [%]',
'Loss of active material in positive electrode [%]',
'Loss of capacity to SEI [A.h]',
'Loss of capacity to SEI on cracks [A.h]',
'Loss of capacity to lithium plating [A.h]',
'Loss of lithium inventory [%]',
'Loss of lithium inventory, including electrolyte [%]',
'Loss of lithium to SEI [mol]',
'Loss of lithium to SEI on cracks [mol]',
'Loss of lithium to lithium plating [mol]',
'Maximum measured discharge capacity [A.h]',
'Maximum voltage [V]',
'Measured capacity [A.h]',
'Minimum measured discharge capacity [A.h]',
'Minimum voltage [V]',
'Negative electrode capacity [A.h]',
'Negative electrode excess capacity ratio',
'Positive electrode capacity [A.h]',
'Positive electrode excess capacity ratio',
'Q',
'Q_Li',
'Q_n',
'Q_n * (x_100 - x_0)',
'Q_p',
'Q_p * (y_0 - y_100)',
'Throughput capacity [A.h]',
'Throughput energy [W.h]',
'Time [h]',
'Time [s]',
'Total capacity lost to side reactions [A.h]',
'Total lithium [mol]',
'Total lithium in electrolyte [mol]',
'Total lithium in negative electrode [mol]',
'Total lithium in particles [mol]',
'Total lithium in positive electrode [mol]',
'Total lithium lost [mol]',
'Total lithium lost from electrolyte [mol]',
'Total lithium lost from particles [mol]',
'Total lithium lost to side reactions [mol]',
'Un(x_0)',
'Un(x_100)',
'Up(y_0)',
'Up(y_0) - Un(x_0)',
'Up(y_100)',
'Up(y_100) - Un(x_100)',
'n_Li',
'x_0',
'x_100',
'x_100 - x_0',
'y_0',
'y_0 - y_100',
'y_100']
The “summary variables” associated with a particular model can also be accessed as a list (which can then be edited) -
[8]:
spm.summary_variables
[8]:
['Time [s]',
'Time [h]',
'Throughput capacity [A.h]',
'Throughput energy [W.h]',
'Loss of lithium inventory [%]',
'Loss of lithium inventory, including electrolyte [%]',
'Total lithium [mol]',
'Total lithium in electrolyte [mol]',
'Total lithium in particles [mol]',
'Total lithium lost [mol]',
'Total lithium lost from particles [mol]',
'Total lithium lost from electrolyte [mol]',
'Loss of lithium to SEI [mol]',
'Loss of capacity to SEI [A.h]',
'Total lithium lost to side reactions [mol]',
'Total capacity lost to side reactions [A.h]',
'Local ECM resistance [Ohm]',
'Negative electrode capacity [A.h]',
'Loss of active material in negative electrode [%]',
'Total lithium in negative electrode [mol]',
'Loss of lithium to lithium plating [mol]',
'Loss of capacity to lithium plating [A.h]',
'Loss of lithium to SEI on cracks [mol]',
'Loss of capacity to SEI on cracks [A.h]',
'Positive electrode capacity [A.h]',
'Loss of active material in positive electrode [%]',
'Total lithium in positive electrode [mol]']
Here the only degradation mechanism is one that causes loss of lithium, so we don’t see loss of active material
[9]:
pybamm.plot_summary_variables(sol)
[9]:
array([[<AxesSubplot:xlabel='Cycle number', ylabel='Capacity [A.h]'>,
<AxesSubplot:xlabel='Cycle number', ylabel='Loss of lithium inventory [%]'>,
<AxesSubplot:xlabel='Cycle number', ylabel='Loss of capacity to SEI [A.h]'>],
[<AxesSubplot:xlabel='Cycle number', ylabel='Loss of active material in negative electrode [%]'>,
<AxesSubplot:xlabel='Cycle number', ylabel='Loss of active material in positive electrode [%]'>,
<AxesSubplot:xlabel='Cycle number', ylabel='x_100'>],
[<AxesSubplot:xlabel='Cycle number', ylabel='x_0'>,
<AxesSubplot:xlabel='Cycle number', ylabel='y_100'>,
<AxesSubplot:xlabel='Cycle number', ylabel='y_0'>]], dtype=object)
To suggest additional summary variables, open an issue!
If the simulation contains thousands of cycles, saving each cycle in RAM might not be possible. To get around this, we can use save_at_cycles. If this is an integer n, every nth cycle is saved. If this is a list, all the cycles in the list are saved. The first cycle is always saved.
[10]:
# With integersol_int=sim.solve(save_at_cycles=5)# With listsol_list=sim.solve(save_at_cycles=[30,45,55])
2022-12-04 21:24:37.738 - [NOTICE] callbacks.on_cycle_start(174): Cycle 1/500 (18.250 us elapsed) --------------------
2022-12-04 21:24:37.739 - [NOTICE] callbacks.on_step_start(182): Cycle 1/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:37.779 - [NOTICE] callbacks.on_step_start(182): Cycle 1/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:37.793 - [NOTICE] callbacks.on_step_start(182): Cycle 1/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:37.813 - [NOTICE] callbacks.on_step_start(182): Cycle 1/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:37.855 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.941 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:37.855 - [NOTICE] callbacks.on_cycle_start(174): Cycle 2/500 (117.477 ms elapsed) --------------------
2022-12-04 21:24:37.856 - [NOTICE] callbacks.on_step_start(182): Cycle 2/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:37.879 - [NOTICE] callbacks.on_step_start(182): Cycle 2/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:37.892 - [NOTICE] callbacks.on_step_start(182): Cycle 2/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:37.912 - [NOTICE] callbacks.on_step_start(182): Cycle 2/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:37.951 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.913 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:37.952 - [NOTICE] callbacks.on_cycle_start(174): Cycle 3/500 (213.869 ms elapsed) --------------------
2022-12-04 21:24:37.952 - [NOTICE] callbacks.on_step_start(182): Cycle 3/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:37.977 - [NOTICE] callbacks.on_step_start(182): Cycle 3/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:37.991 - [NOTICE] callbacks.on_step_start(182): Cycle 3/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:38.013 - [NOTICE] callbacks.on_step_start(182): Cycle 3/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:38.056 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.886 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:38.057 - [NOTICE] callbacks.on_cycle_start(174): Cycle 4/500 (319.000 ms elapsed) --------------------
2022-12-04 21:24:38.058 - [NOTICE] callbacks.on_step_start(182): Cycle 4/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:38.094 - [NOTICE] callbacks.on_step_start(182): Cycle 4/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:38.115 - [NOTICE] callbacks.on_step_start(182): Cycle 4/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:38.154 - [NOTICE] callbacks.on_step_start(182): Cycle 4/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:38.204 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.859 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:38.204 - [NOTICE] callbacks.on_cycle_start(174): Cycle 5/500 (466.169 ms elapsed) --------------------
2022-12-04 21:24:38.205 - [NOTICE] callbacks.on_step_start(182): Cycle 5/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:38.232 - [NOTICE] callbacks.on_step_start(182): Cycle 5/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:38.249 - [NOTICE] callbacks.on_step_start(182): Cycle 5/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:38.273 - [NOTICE] callbacks.on_step_start(182): Cycle 5/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:38.327 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.833 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:38.328 - [NOTICE] callbacks.on_cycle_start(174): Cycle 6/500 (590.261 ms elapsed) --------------------
2022-12-04 21:24:38.329 - [NOTICE] callbacks.on_step_start(182): Cycle 6/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:38.352 - [NOTICE] callbacks.on_step_start(182): Cycle 6/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:38.369 - [NOTICE] callbacks.on_step_start(182): Cycle 6/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:38.389 - [NOTICE] callbacks.on_step_start(182): Cycle 6/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:38.442 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.807 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:38.442 - [NOTICE] callbacks.on_cycle_start(174): Cycle 7/500 (704.200 ms elapsed) --------------------
2022-12-04 21:24:38.443 - [NOTICE] callbacks.on_step_start(182): Cycle 7/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:38.464 - [NOTICE] callbacks.on_step_start(182): Cycle 7/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:38.478 - [NOTICE] callbacks.on_step_start(182): Cycle 7/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:38.498 - [NOTICE] callbacks.on_step_start(182): Cycle 7/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:38.545 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.781 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:38.546 - [NOTICE] callbacks.on_cycle_start(174): Cycle 8/500 (807.534 ms elapsed) --------------------
2022-12-04 21:24:38.546 - [NOTICE] callbacks.on_step_start(182): Cycle 8/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:38.569 - [NOTICE] callbacks.on_step_start(182): Cycle 8/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:38.584 - [NOTICE] callbacks.on_step_start(182): Cycle 8/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:38.603 - [NOTICE] callbacks.on_step_start(182): Cycle 8/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:38.650 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.756 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:38.650 - [NOTICE] callbacks.on_cycle_start(174): Cycle 9/500 (912.304 ms elapsed) --------------------
2022-12-04 21:24:38.651 - [NOTICE] callbacks.on_step_start(182): Cycle 9/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:38.673 - [NOTICE] callbacks.on_step_start(182): Cycle 9/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:38.688 - [NOTICE] callbacks.on_step_start(182): Cycle 9/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:38.709 - [NOTICE] callbacks.on_step_start(182): Cycle 9/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:38.754 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.732 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:38.755 - [NOTICE] callbacks.on_cycle_start(174): Cycle 10/500 (1.017 s elapsed) --------------------
2022-12-04 21:24:38.756 - [NOTICE] callbacks.on_step_start(182): Cycle 10/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:38.780 - [NOTICE] callbacks.on_step_start(182): Cycle 10/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:38.795 - [NOTICE] callbacks.on_step_start(182): Cycle 10/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:38.817 - [NOTICE] callbacks.on_step_start(182): Cycle 10/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:38.866 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.708 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:38.867 - [NOTICE] callbacks.on_cycle_start(174): Cycle 11/500 (1.129 s elapsed) --------------------
2022-12-04 21:24:38.867 - [NOTICE] callbacks.on_step_start(182): Cycle 11/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:38.890 - [NOTICE] callbacks.on_step_start(182): Cycle 11/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:38.905 - [NOTICE] callbacks.on_step_start(182): Cycle 11/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:38.928 - [NOTICE] callbacks.on_step_start(182): Cycle 11/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:38.972 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.684 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:38.973 - [NOTICE] callbacks.on_cycle_start(174): Cycle 12/500 (1.235 s elapsed) --------------------
2022-12-04 21:24:38.973 - [NOTICE] callbacks.on_step_start(182): Cycle 12/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:38.997 - [NOTICE] callbacks.on_step_start(182): Cycle 12/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:39.010 - [NOTICE] callbacks.on_step_start(182): Cycle 12/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:39.029 - [NOTICE] callbacks.on_step_start(182): Cycle 12/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:39.074 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.660 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:39.075 - [NOTICE] callbacks.on_cycle_start(174): Cycle 13/500 (1.337 s elapsed) --------------------
2022-12-04 21:24:39.075 - [NOTICE] callbacks.on_step_start(182): Cycle 13/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:39.094 - [NOTICE] callbacks.on_step_start(182): Cycle 13/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:39.109 - [NOTICE] callbacks.on_step_start(182): Cycle 13/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:39.128 - [NOTICE] callbacks.on_step_start(182): Cycle 13/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:39.174 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.637 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:39.175 - [NOTICE] callbacks.on_cycle_start(174): Cycle 14/500 (1.437 s elapsed) --------------------
2022-12-04 21:24:39.176 - [NOTICE] callbacks.on_step_start(182): Cycle 14/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:39.198 - [NOTICE] callbacks.on_step_start(182): Cycle 14/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:39.211 - [NOTICE] callbacks.on_step_start(182): Cycle 14/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:39.230 - [NOTICE] callbacks.on_step_start(182): Cycle 14/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:39.274 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.614 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:39.274 - [NOTICE] callbacks.on_cycle_start(174): Cycle 15/500 (1.536 s elapsed) --------------------
2022-12-04 21:24:39.275 - [NOTICE] callbacks.on_step_start(182): Cycle 15/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:39.295 - [NOTICE] callbacks.on_step_start(182): Cycle 15/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:39.309 - [NOTICE] callbacks.on_step_start(182): Cycle 15/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:39.328 - [NOTICE] callbacks.on_step_start(182): Cycle 15/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:39.377 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.592 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:39.378 - [NOTICE] callbacks.on_cycle_start(174): Cycle 16/500 (1.640 s elapsed) --------------------
2022-12-04 21:24:39.378 - [NOTICE] callbacks.on_step_start(182): Cycle 16/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:39.400 - [NOTICE] callbacks.on_step_start(182): Cycle 16/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:39.414 - [NOTICE] callbacks.on_step_start(182): Cycle 16/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:39.437 - [NOTICE] callbacks.on_step_start(182): Cycle 16/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:39.482 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.569 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:39.482 - [NOTICE] callbacks.on_cycle_start(174): Cycle 17/500 (1.744 s elapsed) --------------------
2022-12-04 21:24:39.483 - [NOTICE] callbacks.on_step_start(182): Cycle 17/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:39.504 - [NOTICE] callbacks.on_step_start(182): Cycle 17/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:39.518 - [NOTICE] callbacks.on_step_start(182): Cycle 17/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:39.542 - [NOTICE] callbacks.on_step_start(182): Cycle 17/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:39.588 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.548 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:39.589 - [NOTICE] callbacks.on_cycle_start(174): Cycle 18/500 (1.851 s elapsed) --------------------
2022-12-04 21:24:39.589 - [NOTICE] callbacks.on_step_start(182): Cycle 18/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:39.613 - [NOTICE] callbacks.on_step_start(182): Cycle 18/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:39.627 - [NOTICE] callbacks.on_step_start(182): Cycle 18/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:39.654 - [NOTICE] callbacks.on_step_start(182): Cycle 18/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:39.700 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.526 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:39.700 - [NOTICE] callbacks.on_cycle_start(174): Cycle 19/500 (1.962 s elapsed) --------------------
2022-12-04 21:24:39.701 - [NOTICE] callbacks.on_step_start(182): Cycle 19/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:39.723 - [NOTICE] callbacks.on_step_start(182): Cycle 19/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:39.736 - [NOTICE] callbacks.on_step_start(182): Cycle 19/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:39.755 - [NOTICE] callbacks.on_step_start(182): Cycle 19/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:39.799 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.505 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:39.799 - [NOTICE] callbacks.on_cycle_start(174): Cycle 20/500 (2.061 s elapsed) --------------------
2022-12-04 21:24:39.800 - [NOTICE] callbacks.on_step_start(182): Cycle 20/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:39.820 - [NOTICE] callbacks.on_step_start(182): Cycle 20/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:39.833 - [NOTICE] callbacks.on_step_start(182): Cycle 20/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:39.852 - [NOTICE] callbacks.on_step_start(182): Cycle 20/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:39.899 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.484 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:39.899 - [NOTICE] callbacks.on_cycle_start(174): Cycle 21/500 (2.161 s elapsed) --------------------
2022-12-04 21:24:39.900 - [NOTICE] callbacks.on_step_start(182): Cycle 21/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:39.919 - [NOTICE] callbacks.on_step_start(182): Cycle 21/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:39.933 - [NOTICE] callbacks.on_step_start(182): Cycle 21/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:39.952 - [NOTICE] callbacks.on_step_start(182): Cycle 21/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:39.997 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.463 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:39.997 - [NOTICE] callbacks.on_cycle_start(174): Cycle 22/500 (2.259 s elapsed) --------------------
2022-12-04 21:24:39.998 - [NOTICE] callbacks.on_step_start(182): Cycle 22/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:40.017 - [NOTICE] callbacks.on_step_start(182): Cycle 22/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:40.031 - [NOTICE] callbacks.on_step_start(182): Cycle 22/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:40.050 - [NOTICE] callbacks.on_step_start(182): Cycle 22/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:40.092 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.442 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:40.093 - [NOTICE] callbacks.on_cycle_start(174): Cycle 23/500 (2.355 s elapsed) --------------------
2022-12-04 21:24:40.093 - [NOTICE] callbacks.on_step_start(182): Cycle 23/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:40.114 - [NOTICE] callbacks.on_step_start(182): Cycle 23/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:40.127 - [NOTICE] callbacks.on_step_start(182): Cycle 23/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:40.148 - [NOTICE] callbacks.on_step_start(182): Cycle 23/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:40.194 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.422 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:40.195 - [NOTICE] callbacks.on_cycle_start(174): Cycle 24/500 (2.457 s elapsed) --------------------
2022-12-04 21:24:40.195 - [NOTICE] callbacks.on_step_start(182): Cycle 24/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:40.217 - [NOTICE] callbacks.on_step_start(182): Cycle 24/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:40.231 - [NOTICE] callbacks.on_step_start(182): Cycle 24/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:40.253 - [NOTICE] callbacks.on_step_start(182): Cycle 24/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:40.299 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.402 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:40.299 - [NOTICE] callbacks.on_cycle_start(174): Cycle 25/500 (2.561 s elapsed) --------------------
2022-12-04 21:24:40.300 - [NOTICE] callbacks.on_step_start(182): Cycle 25/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:40.322 - [NOTICE] callbacks.on_step_start(182): Cycle 25/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:40.337 - [NOTICE] callbacks.on_step_start(182): Cycle 25/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:40.358 - [NOTICE] callbacks.on_step_start(182): Cycle 25/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:40.413 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.382 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:40.413 - [NOTICE] callbacks.on_cycle_start(174): Cycle 26/500 (2.675 s elapsed) --------------------
2022-12-04 21:24:40.414 - [NOTICE] callbacks.on_step_start(182): Cycle 26/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:40.439 - [NOTICE] callbacks.on_step_start(182): Cycle 26/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:40.456 - [NOTICE] callbacks.on_step_start(182): Cycle 26/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:40.480 - [NOTICE] callbacks.on_step_start(182): Cycle 26/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:40.526 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.362 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:40.527 - [NOTICE] callbacks.on_cycle_start(174): Cycle 27/500 (2.789 s elapsed) --------------------
2022-12-04 21:24:40.529 - [NOTICE] callbacks.on_step_start(182): Cycle 27/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:40.553 - [NOTICE] callbacks.on_step_start(182): Cycle 27/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:40.570 - [NOTICE] callbacks.on_step_start(182): Cycle 27/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:40.589 - [NOTICE] callbacks.on_step_start(182): Cycle 27/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:40.635 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.343 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:40.635 - [NOTICE] callbacks.on_cycle_start(174): Cycle 28/500 (2.897 s elapsed) --------------------
2022-12-04 21:24:40.635 - [NOTICE] callbacks.on_step_start(182): Cycle 28/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:40.658 - [NOTICE] callbacks.on_step_start(182): Cycle 28/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:40.673 - [NOTICE] callbacks.on_step_start(182): Cycle 28/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:40.693 - [NOTICE] callbacks.on_step_start(182): Cycle 28/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:40.737 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.324 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:40.738 - [NOTICE] callbacks.on_cycle_start(174): Cycle 29/500 (3.000 s elapsed) --------------------
2022-12-04 21:24:40.738 - [NOTICE] callbacks.on_step_start(182): Cycle 29/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:40.757 - [NOTICE] callbacks.on_step_start(182): Cycle 29/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:40.772 - [NOTICE] callbacks.on_step_start(182): Cycle 29/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:40.790 - [NOTICE] callbacks.on_step_start(182): Cycle 29/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:40.831 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.305 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:40.832 - [NOTICE] callbacks.on_cycle_start(174): Cycle 30/500 (3.094 s elapsed) --------------------
2022-12-04 21:24:40.833 - [NOTICE] callbacks.on_step_start(182): Cycle 30/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:40.851 - [NOTICE] callbacks.on_step_start(182): Cycle 30/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:40.864 - [NOTICE] callbacks.on_step_start(182): Cycle 30/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:40.882 - [NOTICE] callbacks.on_step_start(182): Cycle 30/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:40.926 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.286 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:40.927 - [NOTICE] callbacks.on_cycle_start(174): Cycle 31/500 (3.189 s elapsed) --------------------
2022-12-04 21:24:40.927 - [NOTICE] callbacks.on_step_start(182): Cycle 31/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:40.946 - [NOTICE] callbacks.on_step_start(182): Cycle 31/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:40.960 - [NOTICE] callbacks.on_step_start(182): Cycle 31/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:40.980 - [NOTICE] callbacks.on_step_start(182): Cycle 31/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:41.022 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.267 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:41.023 - [NOTICE] callbacks.on_cycle_start(174): Cycle 32/500 (3.285 s elapsed) --------------------
2022-12-04 21:24:41.023 - [NOTICE] callbacks.on_step_start(182): Cycle 32/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:41.042 - [NOTICE] callbacks.on_step_start(182): Cycle 32/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:41.056 - [NOTICE] callbacks.on_step_start(182): Cycle 32/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:41.078 - [NOTICE] callbacks.on_step_start(182): Cycle 32/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:41.122 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.249 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:41.123 - [NOTICE] callbacks.on_cycle_start(174): Cycle 33/500 (3.385 s elapsed) --------------------
2022-12-04 21:24:41.123 - [NOTICE] callbacks.on_step_start(182): Cycle 33/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:41.146 - [NOTICE] callbacks.on_step_start(182): Cycle 33/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:41.159 - [NOTICE] callbacks.on_step_start(182): Cycle 33/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:41.179 - [NOTICE] callbacks.on_step_start(182): Cycle 33/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:41.226 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.231 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:41.226 - [NOTICE] callbacks.on_cycle_start(174): Cycle 34/500 (3.488 s elapsed) --------------------
2022-12-04 21:24:41.227 - [NOTICE] callbacks.on_step_start(182): Cycle 34/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:41.252 - [NOTICE] callbacks.on_step_start(182): Cycle 34/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:41.267 - [NOTICE] callbacks.on_step_start(182): Cycle 34/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:41.288 - [NOTICE] callbacks.on_step_start(182): Cycle 34/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:41.333 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.213 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:41.334 - [NOTICE] callbacks.on_cycle_start(174): Cycle 35/500 (3.596 s elapsed) --------------------
2022-12-04 21:24:41.334 - [NOTICE] callbacks.on_step_start(182): Cycle 35/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:41.356 - [NOTICE] callbacks.on_step_start(182): Cycle 35/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:41.371 - [NOTICE] callbacks.on_step_start(182): Cycle 35/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:41.389 - [NOTICE] callbacks.on_step_start(182): Cycle 35/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:41.435 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.195 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:41.436 - [NOTICE] callbacks.on_cycle_start(174): Cycle 36/500 (3.698 s elapsed) --------------------
2022-12-04 21:24:41.436 - [NOTICE] callbacks.on_step_start(182): Cycle 36/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:41.461 - [NOTICE] callbacks.on_step_start(182): Cycle 36/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:41.474 - [NOTICE] callbacks.on_step_start(182): Cycle 36/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:41.492 - [NOTICE] callbacks.on_step_start(182): Cycle 36/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:41.539 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.177 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:41.540 - [NOTICE] callbacks.on_cycle_start(174): Cycle 37/500 (3.802 s elapsed) --------------------
2022-12-04 21:24:41.540 - [NOTICE] callbacks.on_step_start(182): Cycle 37/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:41.562 - [NOTICE] callbacks.on_step_start(182): Cycle 37/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:41.575 - [NOTICE] callbacks.on_step_start(182): Cycle 37/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:41.594 - [NOTICE] callbacks.on_step_start(182): Cycle 37/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:41.640 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.160 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:41.641 - [NOTICE] callbacks.on_cycle_start(174): Cycle 38/500 (3.903 s elapsed) --------------------
2022-12-04 21:24:41.641 - [NOTICE] callbacks.on_step_start(182): Cycle 38/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:41.659 - [NOTICE] callbacks.on_step_start(182): Cycle 38/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:41.673 - [NOTICE] callbacks.on_step_start(182): Cycle 38/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:41.692 - [NOTICE] callbacks.on_step_start(182): Cycle 38/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:41.738 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.143 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:41.738 - [NOTICE] callbacks.on_cycle_start(174): Cycle 39/500 (4.000 s elapsed) --------------------
2022-12-04 21:24:41.739 - [NOTICE] callbacks.on_step_start(182): Cycle 39/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:41.758 - [NOTICE] callbacks.on_step_start(182): Cycle 39/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:41.773 - [NOTICE] callbacks.on_step_start(182): Cycle 39/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:41.791 - [NOTICE] callbacks.on_step_start(182): Cycle 39/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:41.840 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.126 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:41.843 - [NOTICE] callbacks.on_cycle_start(174): Cycle 40/500 (4.105 s elapsed) --------------------
2022-12-04 21:24:41.848 - [NOTICE] callbacks.on_step_start(182): Cycle 40/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:41.869 - [NOTICE] callbacks.on_step_start(182): Cycle 40/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:41.888 - [NOTICE] callbacks.on_step_start(182): Cycle 40/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:41.916 - [NOTICE] callbacks.on_step_start(182): Cycle 40/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:41.973 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.109 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:41.974 - [NOTICE] callbacks.on_cycle_start(174): Cycle 41/500 (4.236 s elapsed) --------------------
2022-12-04 21:24:41.975 - [NOTICE] callbacks.on_step_start(182): Cycle 41/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:41.994 - [NOTICE] callbacks.on_step_start(182): Cycle 41/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:42.009 - [NOTICE] callbacks.on_step_start(182): Cycle 41/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:42.030 - [NOTICE] callbacks.on_step_start(182): Cycle 41/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:42.073 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.092 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:42.074 - [NOTICE] callbacks.on_cycle_start(174): Cycle 42/500 (4.336 s elapsed) --------------------
2022-12-04 21:24:42.074 - [NOTICE] callbacks.on_step_start(182): Cycle 42/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:42.092 - [NOTICE] callbacks.on_step_start(182): Cycle 42/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:42.108 - [NOTICE] callbacks.on_step_start(182): Cycle 42/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:42.129 - [NOTICE] callbacks.on_step_start(182): Cycle 42/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:42.174 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.075 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:42.174 - [NOTICE] callbacks.on_cycle_start(174): Cycle 43/500 (4.436 s elapsed) --------------------
2022-12-04 21:24:42.175 - [NOTICE] callbacks.on_step_start(182): Cycle 43/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:42.196 - [NOTICE] callbacks.on_step_start(182): Cycle 43/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:42.211 - [NOTICE] callbacks.on_step_start(182): Cycle 43/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:42.231 - [NOTICE] callbacks.on_step_start(182): Cycle 43/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:42.272 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.059 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:42.273 - [NOTICE] callbacks.on_cycle_start(174): Cycle 44/500 (4.535 s elapsed) --------------------
2022-12-04 21:24:42.273 - [NOTICE] callbacks.on_step_start(182): Cycle 44/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:42.295 - [NOTICE] callbacks.on_step_start(182): Cycle 44/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:42.309 - [NOTICE] callbacks.on_step_start(182): Cycle 44/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:42.330 - [NOTICE] callbacks.on_step_start(182): Cycle 44/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:42.372 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.042 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:42.373 - [NOTICE] callbacks.on_cycle_start(174): Cycle 45/500 (4.635 s elapsed) --------------------
2022-12-04 21:24:42.373 - [NOTICE] callbacks.on_step_start(182): Cycle 45/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:42.396 - [NOTICE] callbacks.on_step_start(182): Cycle 45/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:42.410 - [NOTICE] callbacks.on_step_start(182): Cycle 45/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:42.427 - [NOTICE] callbacks.on_step_start(182): Cycle 45/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:42.475 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.026 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:42.476 - [NOTICE] callbacks.on_cycle_start(174): Cycle 46/500 (4.738 s elapsed) --------------------
2022-12-04 21:24:42.476 - [NOTICE] callbacks.on_step_start(182): Cycle 46/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:42.498 - [NOTICE] callbacks.on_step_start(182): Cycle 46/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:42.519 - [NOTICE] callbacks.on_step_start(182): Cycle 46/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:42.536 - [NOTICE] callbacks.on_step_start(182): Cycle 46/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:42.582 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.010 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:42.582 - [NOTICE] callbacks.on_cycle_start(174): Cycle 47/500 (4.844 s elapsed) --------------------
2022-12-04 21:24:42.582 - [NOTICE] callbacks.on_step_start(182): Cycle 47/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:42.603 - [NOTICE] callbacks.on_step_start(182): Cycle 47/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:42.617 - [NOTICE] callbacks.on_step_start(182): Cycle 47/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:42.634 - [NOTICE] callbacks.on_step_start(182): Cycle 47/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:42.678 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 3.994 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:42.679 - [NOTICE] callbacks.on_cycle_start(174): Cycle 48/500 (4.941 s elapsed) --------------------
2022-12-04 21:24:42.680 - [NOTICE] callbacks.on_step_start(182): Cycle 48/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:42.698 - [NOTICE] callbacks.on_step_start(182): Cycle 48/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:42.711 - [NOTICE] callbacks.on_step_start(182): Cycle 48/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:42.727 - [NOTICE] callbacks.on_step_start(182): Cycle 48/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:42.772 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 3.978 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:42.772 - [NOTICE] callbacks.on_cycle_start(174): Cycle 49/500 (5.034 s elapsed) --------------------
2022-12-04 21:24:42.773 - [NOTICE] callbacks.on_step_start(182): Cycle 49/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:42.789 - [NOTICE] callbacks.on_step_start(182): Cycle 49/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:42.804 - [NOTICE] callbacks.on_step_start(182): Cycle 49/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:42.824 - [NOTICE] callbacks.on_step_start(182): Cycle 49/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:42.871 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 3.962 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:42.871 - [NOTICE] callbacks.on_cycle_start(174): Cycle 50/500 (5.133 s elapsed) --------------------
2022-12-04 21:24:42.871 - [NOTICE] callbacks.on_step_start(182): Cycle 50/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:42.890 - [NOTICE] callbacks.on_step_start(182): Cycle 50/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:42.904 - [NOTICE] callbacks.on_step_start(182): Cycle 50/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:42.924 - [NOTICE] callbacks.on_step_start(182): Cycle 50/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:42.977 - [NOTICE] callbacks.on_cycle_end(201): Stopping experiment since capacity (3.947 Ah) is below stopping capacity (3.952 Ah).
2022-12-04 21:24:42.979 - [NOTICE] callbacks.on_experiment_end(222): Finish experiment simulation, took 5.239 s
2022-12-04 21:24:42.979 - [NOTICE] callbacks.on_cycle_start(174): Cycle 1/500 (15.583 us elapsed) --------------------
2022-12-04 21:24:42.980 - [NOTICE] callbacks.on_step_start(182): Cycle 1/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:43.020 - [NOTICE] callbacks.on_step_start(182): Cycle 1/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:43.036 - [NOTICE] callbacks.on_step_start(182): Cycle 1/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:43.061 - [NOTICE] callbacks.on_step_start(182): Cycle 1/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:43.105 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.941 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:43.106 - [NOTICE] callbacks.on_cycle_start(174): Cycle 2/500 (126.469 ms elapsed) --------------------
2022-12-04 21:24:43.107 - [NOTICE] callbacks.on_step_start(182): Cycle 2/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:43.131 - [NOTICE] callbacks.on_step_start(182): Cycle 2/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:43.145 - [NOTICE] callbacks.on_step_start(182): Cycle 2/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:43.166 - [NOTICE] callbacks.on_step_start(182): Cycle 2/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:43.209 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.913 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:43.209 - [NOTICE] callbacks.on_cycle_start(174): Cycle 3/500 (229.755 ms elapsed) --------------------
2022-12-04 21:24:43.210 - [NOTICE] callbacks.on_step_start(182): Cycle 3/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:43.232 - [NOTICE] callbacks.on_step_start(182): Cycle 3/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:43.246 - [NOTICE] callbacks.on_step_start(182): Cycle 3/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:43.270 - [NOTICE] callbacks.on_step_start(182): Cycle 3/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:43.315 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.886 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:43.316 - [NOTICE] callbacks.on_cycle_start(174): Cycle 4/500 (336.229 ms elapsed) --------------------
2022-12-04 21:24:43.316 - [NOTICE] callbacks.on_step_start(182): Cycle 4/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:43.341 - [NOTICE] callbacks.on_step_start(182): Cycle 4/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:43.357 - [NOTICE] callbacks.on_step_start(182): Cycle 4/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:43.382 - [NOTICE] callbacks.on_step_start(182): Cycle 4/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:43.424 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.859 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:43.425 - [NOTICE] callbacks.on_cycle_start(174): Cycle 5/500 (445.575 ms elapsed) --------------------
2022-12-04 21:24:43.426 - [NOTICE] callbacks.on_step_start(182): Cycle 5/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:43.450 - [NOTICE] callbacks.on_step_start(182): Cycle 5/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:43.463 - [NOTICE] callbacks.on_step_start(182): Cycle 5/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:43.485 - [NOTICE] callbacks.on_step_start(182): Cycle 5/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:43.531 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.833 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:43.532 - [NOTICE] callbacks.on_cycle_start(174): Cycle 6/500 (552.278 ms elapsed) --------------------
2022-12-04 21:24:43.532 - [NOTICE] callbacks.on_step_start(182): Cycle 6/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:43.552 - [NOTICE] callbacks.on_step_start(182): Cycle 6/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:43.572 - [NOTICE] callbacks.on_step_start(182): Cycle 6/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:43.592 - [NOTICE] callbacks.on_step_start(182): Cycle 6/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:43.640 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.807 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:43.641 - [NOTICE] callbacks.on_cycle_start(174): Cycle 7/500 (661.270 ms elapsed) --------------------
2022-12-04 21:24:43.642 - [NOTICE] callbacks.on_step_start(182): Cycle 7/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:43.666 - [NOTICE] callbacks.on_step_start(182): Cycle 7/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:43.682 - [NOTICE] callbacks.on_step_start(182): Cycle 7/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:43.704 - [NOTICE] callbacks.on_step_start(182): Cycle 7/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:43.762 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.781 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:43.763 - [NOTICE] callbacks.on_cycle_start(174): Cycle 8/500 (783.208 ms elapsed) --------------------
2022-12-04 21:24:43.763 - [NOTICE] callbacks.on_step_start(182): Cycle 8/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:43.788 - [NOTICE] callbacks.on_step_start(182): Cycle 8/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:43.805 - [NOTICE] callbacks.on_step_start(182): Cycle 8/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:43.832 - [NOTICE] callbacks.on_step_start(182): Cycle 8/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:43.883 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.756 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:43.889 - [NOTICE] callbacks.on_cycle_start(174): Cycle 9/500 (909.409 ms elapsed) --------------------
2022-12-04 21:24:43.902 - [NOTICE] callbacks.on_step_start(182): Cycle 9/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:43.939 - [NOTICE] callbacks.on_step_start(182): Cycle 9/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:43.955 - [NOTICE] callbacks.on_step_start(182): Cycle 9/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:43.977 - [NOTICE] callbacks.on_step_start(182): Cycle 9/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:44.022 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.732 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:44.023 - [NOTICE] callbacks.on_cycle_start(174): Cycle 10/500 (1.043 s elapsed) --------------------
2022-12-04 21:24:44.023 - [NOTICE] callbacks.on_step_start(182): Cycle 10/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:44.046 - [NOTICE] callbacks.on_step_start(182): Cycle 10/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:44.065 - [NOTICE] callbacks.on_step_start(182): Cycle 10/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:44.087 - [NOTICE] callbacks.on_step_start(182): Cycle 10/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:44.132 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.708 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:44.132 - [NOTICE] callbacks.on_cycle_start(174): Cycle 11/500 (1.153 s elapsed) --------------------
2022-12-04 21:24:44.132 - [NOTICE] callbacks.on_step_start(182): Cycle 11/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:44.157 - [NOTICE] callbacks.on_step_start(182): Cycle 11/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:44.172 - [NOTICE] callbacks.on_step_start(182): Cycle 11/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:44.194 - [NOTICE] callbacks.on_step_start(182): Cycle 11/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:44.238 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.684 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:44.238 - [NOTICE] callbacks.on_cycle_start(174): Cycle 12/500 (1.259 s elapsed) --------------------
2022-12-04 21:24:44.239 - [NOTICE] callbacks.on_step_start(182): Cycle 12/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:44.263 - [NOTICE] callbacks.on_step_start(182): Cycle 12/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:44.277 - [NOTICE] callbacks.on_step_start(182): Cycle 12/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:44.296 - [NOTICE] callbacks.on_step_start(182): Cycle 12/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:44.342 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.660 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:44.343 - [NOTICE] callbacks.on_cycle_start(174): Cycle 13/500 (1.363 s elapsed) --------------------
2022-12-04 21:24:44.343 - [NOTICE] callbacks.on_step_start(182): Cycle 13/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:44.363 - [NOTICE] callbacks.on_step_start(182): Cycle 13/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:44.377 - [NOTICE] callbacks.on_step_start(182): Cycle 13/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:44.399 - [NOTICE] callbacks.on_step_start(182): Cycle 13/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:44.447 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.637 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:44.448 - [NOTICE] callbacks.on_cycle_start(174): Cycle 14/500 (1.468 s elapsed) --------------------
2022-12-04 21:24:44.448 - [NOTICE] callbacks.on_step_start(182): Cycle 14/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:44.470 - [NOTICE] callbacks.on_step_start(182): Cycle 14/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:44.484 - [NOTICE] callbacks.on_step_start(182): Cycle 14/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:44.504 - [NOTICE] callbacks.on_step_start(182): Cycle 14/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:44.550 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.614 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:44.551 - [NOTICE] callbacks.on_cycle_start(174): Cycle 15/500 (1.572 s elapsed) --------------------
2022-12-04 21:24:44.551 - [NOTICE] callbacks.on_step_start(182): Cycle 15/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:44.572 - [NOTICE] callbacks.on_step_start(182): Cycle 15/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:44.586 - [NOTICE] callbacks.on_step_start(182): Cycle 15/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:44.604 - [NOTICE] callbacks.on_step_start(182): Cycle 15/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:44.651 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.592 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:44.651 - [NOTICE] callbacks.on_cycle_start(174): Cycle 16/500 (1.672 s elapsed) --------------------
2022-12-04 21:24:44.651 - [NOTICE] callbacks.on_step_start(182): Cycle 16/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:44.674 - [NOTICE] callbacks.on_step_start(182): Cycle 16/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:44.690 - [NOTICE] callbacks.on_step_start(182): Cycle 16/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:44.720 - [NOTICE] callbacks.on_step_start(182): Cycle 16/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:44.770 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.569 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:44.772 - [NOTICE] callbacks.on_cycle_start(174): Cycle 17/500 (1.792 s elapsed) --------------------
2022-12-04 21:24:44.774 - [NOTICE] callbacks.on_step_start(182): Cycle 17/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:44.802 - [NOTICE] callbacks.on_step_start(182): Cycle 17/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:44.828 - [NOTICE] callbacks.on_step_start(182): Cycle 17/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:44.851 - [NOTICE] callbacks.on_step_start(182): Cycle 17/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:44.897 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.548 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:44.898 - [NOTICE] callbacks.on_cycle_start(174): Cycle 18/500 (1.918 s elapsed) --------------------
2022-12-04 21:24:44.898 - [NOTICE] callbacks.on_step_start(182): Cycle 18/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:44.922 - [NOTICE] callbacks.on_step_start(182): Cycle 18/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:44.938 - [NOTICE] callbacks.on_step_start(182): Cycle 18/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:44.961 - [NOTICE] callbacks.on_step_start(182): Cycle 18/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:45.008 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.526 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:45.009 - [NOTICE] callbacks.on_cycle_start(174): Cycle 19/500 (2.030 s elapsed) --------------------
2022-12-04 21:24:45.010 - [NOTICE] callbacks.on_step_start(182): Cycle 19/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:45.033 - [NOTICE] callbacks.on_step_start(182): Cycle 19/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:45.046 - [NOTICE] callbacks.on_step_start(182): Cycle 19/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:45.065 - [NOTICE] callbacks.on_step_start(182): Cycle 19/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:45.111 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.505 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:45.111 - [NOTICE] callbacks.on_cycle_start(174): Cycle 20/500 (2.132 s elapsed) --------------------
2022-12-04 21:24:45.112 - [NOTICE] callbacks.on_step_start(182): Cycle 20/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:45.131 - [NOTICE] callbacks.on_step_start(182): Cycle 20/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:45.145 - [NOTICE] callbacks.on_step_start(182): Cycle 20/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:45.163 - [NOTICE] callbacks.on_step_start(182): Cycle 20/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:45.212 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.484 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:45.213 - [NOTICE] callbacks.on_cycle_start(174): Cycle 21/500 (2.233 s elapsed) --------------------
2022-12-04 21:24:45.213 - [NOTICE] callbacks.on_step_start(182): Cycle 21/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:45.235 - [NOTICE] callbacks.on_step_start(182): Cycle 21/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:45.249 - [NOTICE] callbacks.on_step_start(182): Cycle 21/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:45.268 - [NOTICE] callbacks.on_step_start(182): Cycle 21/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:45.314 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.463 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:45.315 - [NOTICE] callbacks.on_cycle_start(174): Cycle 22/500 (2.335 s elapsed) --------------------
2022-12-04 21:24:45.315 - [NOTICE] callbacks.on_step_start(182): Cycle 22/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:45.336 - [NOTICE] callbacks.on_step_start(182): Cycle 22/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:45.350 - [NOTICE] callbacks.on_step_start(182): Cycle 22/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:45.370 - [NOTICE] callbacks.on_step_start(182): Cycle 22/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:45.415 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.442 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:45.416 - [NOTICE] callbacks.on_cycle_start(174): Cycle 23/500 (2.436 s elapsed) --------------------
2022-12-04 21:24:45.416 - [NOTICE] callbacks.on_step_start(182): Cycle 23/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:45.437 - [NOTICE] callbacks.on_step_start(182): Cycle 23/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:45.450 - [NOTICE] callbacks.on_step_start(182): Cycle 23/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:45.471 - [NOTICE] callbacks.on_step_start(182): Cycle 23/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:45.690 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.422 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:45.690 - [NOTICE] callbacks.on_cycle_start(174): Cycle 24/500 (2.711 s elapsed) --------------------
2022-12-04 21:24:45.691 - [NOTICE] callbacks.on_step_start(182): Cycle 24/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:45.715 - [NOTICE] callbacks.on_step_start(182): Cycle 24/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:45.729 - [NOTICE] callbacks.on_step_start(182): Cycle 24/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:45.750 - [NOTICE] callbacks.on_step_start(182): Cycle 24/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:45.795 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.402 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:45.795 - [NOTICE] callbacks.on_cycle_start(174): Cycle 25/500 (2.816 s elapsed) --------------------
2022-12-04 21:24:45.796 - [NOTICE] callbacks.on_step_start(182): Cycle 25/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:45.819 - [NOTICE] callbacks.on_step_start(182): Cycle 25/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:45.833 - [NOTICE] callbacks.on_step_start(182): Cycle 25/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:45.855 - [NOTICE] callbacks.on_step_start(182): Cycle 25/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:45.899 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.382 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:45.900 - [NOTICE] callbacks.on_cycle_start(174): Cycle 26/500 (2.920 s elapsed) --------------------
2022-12-04 21:24:45.900 - [NOTICE] callbacks.on_step_start(182): Cycle 26/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:45.923 - [NOTICE] callbacks.on_step_start(182): Cycle 26/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:45.939 - [NOTICE] callbacks.on_step_start(182): Cycle 26/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:45.963 - [NOTICE] callbacks.on_step_start(182): Cycle 26/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:46.006 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.362 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:46.006 - [NOTICE] callbacks.on_cycle_start(174): Cycle 27/500 (3.027 s elapsed) --------------------
2022-12-04 21:24:46.007 - [NOTICE] callbacks.on_step_start(182): Cycle 27/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:46.028 - [NOTICE] callbacks.on_step_start(182): Cycle 27/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:46.043 - [NOTICE] callbacks.on_step_start(182): Cycle 27/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:46.061 - [NOTICE] callbacks.on_step_start(182): Cycle 27/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:46.109 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.343 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:46.109 - [NOTICE] callbacks.on_cycle_start(174): Cycle 28/500 (3.130 s elapsed) --------------------
2022-12-04 21:24:46.110 - [NOTICE] callbacks.on_step_start(182): Cycle 28/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:46.134 - [NOTICE] callbacks.on_step_start(182): Cycle 28/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:46.147 - [NOTICE] callbacks.on_step_start(182): Cycle 28/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:46.165 - [NOTICE] callbacks.on_step_start(182): Cycle 28/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:46.209 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.324 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:46.210 - [NOTICE] callbacks.on_cycle_start(174): Cycle 29/500 (3.230 s elapsed) --------------------
2022-12-04 21:24:46.210 - [NOTICE] callbacks.on_step_start(182): Cycle 29/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:46.233 - [NOTICE] callbacks.on_step_start(182): Cycle 29/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:46.247 - [NOTICE] callbacks.on_step_start(182): Cycle 29/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:46.265 - [NOTICE] callbacks.on_step_start(182): Cycle 29/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:46.310 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.305 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:46.311 - [NOTICE] callbacks.on_cycle_start(174): Cycle 30/500 (3.332 s elapsed) --------------------
2022-12-04 21:24:46.312 - [NOTICE] callbacks.on_step_start(182): Cycle 30/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:46.332 - [NOTICE] callbacks.on_step_start(182): Cycle 30/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:46.346 - [NOTICE] callbacks.on_step_start(182): Cycle 30/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:46.364 - [NOTICE] callbacks.on_step_start(182): Cycle 30/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:46.411 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.286 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:46.411 - [NOTICE] callbacks.on_cycle_start(174): Cycle 31/500 (3.432 s elapsed) --------------------
2022-12-04 21:24:46.412 - [NOTICE] callbacks.on_step_start(182): Cycle 31/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:46.432 - [NOTICE] callbacks.on_step_start(182): Cycle 31/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:46.445 - [NOTICE] callbacks.on_step_start(182): Cycle 31/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:46.465 - [NOTICE] callbacks.on_step_start(182): Cycle 31/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:46.508 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.267 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:46.509 - [NOTICE] callbacks.on_cycle_start(174): Cycle 32/500 (3.529 s elapsed) --------------------
2022-12-04 21:24:46.509 - [NOTICE] callbacks.on_step_start(182): Cycle 32/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:46.528 - [NOTICE] callbacks.on_step_start(182): Cycle 32/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:46.546 - [NOTICE] callbacks.on_step_start(182): Cycle 32/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:46.566 - [NOTICE] callbacks.on_step_start(182): Cycle 32/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:46.612 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.249 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:46.613 - [NOTICE] callbacks.on_cycle_start(174): Cycle 33/500 (3.634 s elapsed) --------------------
2022-12-04 21:24:46.613 - [NOTICE] callbacks.on_step_start(182): Cycle 33/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:46.635 - [NOTICE] callbacks.on_step_start(182): Cycle 33/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:46.650 - [NOTICE] callbacks.on_step_start(182): Cycle 33/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:46.670 - [NOTICE] callbacks.on_step_start(182): Cycle 33/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:46.714 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.231 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:46.714 - [NOTICE] callbacks.on_cycle_start(174): Cycle 34/500 (3.735 s elapsed) --------------------
2022-12-04 21:24:46.715 - [NOTICE] callbacks.on_step_start(182): Cycle 34/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:46.736 - [NOTICE] callbacks.on_step_start(182): Cycle 34/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:46.749 - [NOTICE] callbacks.on_step_start(182): Cycle 34/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:46.769 - [NOTICE] callbacks.on_step_start(182): Cycle 34/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:46.814 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.213 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:46.814 - [NOTICE] callbacks.on_cycle_start(174): Cycle 35/500 (3.835 s elapsed) --------------------
2022-12-04 21:24:46.815 - [NOTICE] callbacks.on_step_start(182): Cycle 35/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:46.837 - [NOTICE] callbacks.on_step_start(182): Cycle 35/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:46.857 - [NOTICE] callbacks.on_step_start(182): Cycle 35/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:46.877 - [NOTICE] callbacks.on_step_start(182): Cycle 35/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:46.922 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.195 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:46.926 - [NOTICE] callbacks.on_cycle_start(174): Cycle 36/500 (3.947 s elapsed) --------------------
2022-12-04 21:24:46.928 - [NOTICE] callbacks.on_step_start(182): Cycle 36/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:46.952 - [NOTICE] callbacks.on_step_start(182): Cycle 36/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:46.986 - [NOTICE] callbacks.on_step_start(182): Cycle 36/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:47.020 - [NOTICE] callbacks.on_step_start(182): Cycle 36/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:47.094 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.177 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:47.095 - [NOTICE] callbacks.on_cycle_start(174): Cycle 37/500 (4.115 s elapsed) --------------------
2022-12-04 21:24:47.095 - [NOTICE] callbacks.on_step_start(182): Cycle 37/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:47.120 - [NOTICE] callbacks.on_step_start(182): Cycle 37/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:47.134 - [NOTICE] callbacks.on_step_start(182): Cycle 37/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:47.151 - [NOTICE] callbacks.on_step_start(182): Cycle 37/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:47.195 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.160 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:47.196 - [NOTICE] callbacks.on_cycle_start(174): Cycle 38/500 (4.216 s elapsed) --------------------
2022-12-04 21:24:47.196 - [NOTICE] callbacks.on_step_start(182): Cycle 38/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:47.216 - [NOTICE] callbacks.on_step_start(182): Cycle 38/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:47.233 - [NOTICE] callbacks.on_step_start(182): Cycle 38/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:47.252 - [NOTICE] callbacks.on_step_start(182): Cycle 38/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:47.296 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.143 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:47.297 - [NOTICE] callbacks.on_cycle_start(174): Cycle 39/500 (4.317 s elapsed) --------------------
2022-12-04 21:24:47.297 - [NOTICE] callbacks.on_step_start(182): Cycle 39/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:47.318 - [NOTICE] callbacks.on_step_start(182): Cycle 39/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:47.333 - [NOTICE] callbacks.on_step_start(182): Cycle 39/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:47.352 - [NOTICE] callbacks.on_step_start(182): Cycle 39/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:47.396 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.126 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:47.397 - [NOTICE] callbacks.on_cycle_start(174): Cycle 40/500 (4.417 s elapsed) --------------------
2022-12-04 21:24:47.397 - [NOTICE] callbacks.on_step_start(182): Cycle 40/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:47.418 - [NOTICE] callbacks.on_step_start(182): Cycle 40/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:47.432 - [NOTICE] callbacks.on_step_start(182): Cycle 40/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:47.450 - [NOTICE] callbacks.on_step_start(182): Cycle 40/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:47.493 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.109 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:47.494 - [NOTICE] callbacks.on_cycle_start(174): Cycle 41/500 (4.514 s elapsed) --------------------
2022-12-04 21:24:47.494 - [NOTICE] callbacks.on_step_start(182): Cycle 41/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:47.512 - [NOTICE] callbacks.on_step_start(182): Cycle 41/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:47.528 - [NOTICE] callbacks.on_step_start(182): Cycle 41/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:47.551 - [NOTICE] callbacks.on_step_start(182): Cycle 41/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:47.594 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.092 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:47.594 - [NOTICE] callbacks.on_cycle_start(174): Cycle 42/500 (4.615 s elapsed) --------------------
2022-12-04 21:24:47.594 - [NOTICE] callbacks.on_step_start(182): Cycle 42/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:47.613 - [NOTICE] callbacks.on_step_start(182): Cycle 42/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:47.627 - [NOTICE] callbacks.on_step_start(182): Cycle 42/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:47.649 - [NOTICE] callbacks.on_step_start(182): Cycle 42/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:47.693 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.075 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:47.693 - [NOTICE] callbacks.on_cycle_start(174): Cycle 43/500 (4.714 s elapsed) --------------------
2022-12-04 21:24:47.694 - [NOTICE] callbacks.on_step_start(182): Cycle 43/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:47.716 - [NOTICE] callbacks.on_step_start(182): Cycle 43/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:47.729 - [NOTICE] callbacks.on_step_start(182): Cycle 43/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:47.748 - [NOTICE] callbacks.on_step_start(182): Cycle 43/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:47.791 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.059 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:47.792 - [NOTICE] callbacks.on_cycle_start(174): Cycle 44/500 (4.813 s elapsed) --------------------
2022-12-04 21:24:47.793 - [NOTICE] callbacks.on_step_start(182): Cycle 44/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:47.817 - [NOTICE] callbacks.on_step_start(182): Cycle 44/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:47.835 - [NOTICE] callbacks.on_step_start(182): Cycle 44/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:47.854 - [NOTICE] callbacks.on_step_start(182): Cycle 44/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:47.899 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.042 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:47.900 - [NOTICE] callbacks.on_cycle_start(174): Cycle 45/500 (4.920 s elapsed) --------------------
2022-12-04 21:24:47.900 - [NOTICE] callbacks.on_step_start(182): Cycle 45/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:47.922 - [NOTICE] callbacks.on_step_start(182): Cycle 45/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:47.938 - [NOTICE] callbacks.on_step_start(182): Cycle 45/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:47.958 - [NOTICE] callbacks.on_step_start(182): Cycle 45/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:48.003 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.026 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:48.003 - [NOTICE] callbacks.on_cycle_start(174): Cycle 46/500 (5.024 s elapsed) --------------------
2022-12-04 21:24:48.004 - [NOTICE] callbacks.on_step_start(182): Cycle 46/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:48.027 - [NOTICE] callbacks.on_step_start(182): Cycle 46/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:48.041 - [NOTICE] callbacks.on_step_start(182): Cycle 46/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:48.060 - [NOTICE] callbacks.on_step_start(182): Cycle 46/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:48.117 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.010 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:48.122 - [NOTICE] callbacks.on_cycle_start(174): Cycle 47/500 (5.142 s elapsed) --------------------
2022-12-04 21:24:48.127 - [NOTICE] callbacks.on_step_start(182): Cycle 47/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:48.155 - [NOTICE] callbacks.on_step_start(182): Cycle 47/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:48.193 - [NOTICE] callbacks.on_step_start(182): Cycle 47/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:48.208 - [NOTICE] callbacks.on_step_start(182): Cycle 47/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:48.256 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 3.994 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:48.256 - [NOTICE] callbacks.on_cycle_start(174): Cycle 48/500 (5.277 s elapsed) --------------------
2022-12-04 21:24:48.257 - [NOTICE] callbacks.on_step_start(182): Cycle 48/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:48.274 - [NOTICE] callbacks.on_step_start(182): Cycle 48/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:48.290 - [NOTICE] callbacks.on_step_start(182): Cycle 48/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:48.309 - [NOTICE] callbacks.on_step_start(182): Cycle 48/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:48.356 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 3.978 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:48.357 - [NOTICE] callbacks.on_cycle_start(174): Cycle 49/500 (5.378 s elapsed) --------------------
2022-12-04 21:24:48.358 - [NOTICE] callbacks.on_step_start(182): Cycle 49/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:48.375 - [NOTICE] callbacks.on_step_start(182): Cycle 49/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:48.389 - [NOTICE] callbacks.on_step_start(182): Cycle 49/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:48.410 - [NOTICE] callbacks.on_step_start(182): Cycle 49/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:48.457 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 3.962 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:48.458 - [NOTICE] callbacks.on_cycle_start(174): Cycle 50/500 (5.478 s elapsed) --------------------
2022-12-04 21:24:48.458 - [NOTICE] callbacks.on_step_start(182): Cycle 50/500, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:48.478 - [NOTICE] callbacks.on_step_start(182): Cycle 50/500, step 2/4: Rest for 1 hour
2022-12-04 21:24:48.494 - [NOTICE] callbacks.on_step_start(182): Cycle 50/500, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:48.513 - [NOTICE] callbacks.on_step_start(182): Cycle 50/500, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:48.560 - [NOTICE] callbacks.on_cycle_end(201): Stopping experiment since capacity (3.947 Ah) is below stopping capacity (3.952 Ah).
2022-12-04 21:24:48.561 - [NOTICE] callbacks.on_experiment_end(222): Finish experiment simulation, took 5.580 s
[11]:
sol_int.cycles
[11]:
[<pybamm.solvers.solution.Solution at 0x1729c8130>,
None,
None,
None,
<pybamm.solvers.solution.Solution at 0x17299b760>,
None,
None,
None,
None,
<pybamm.solvers.solution.Solution at 0x172a40220>,
None,
None,
None,
None,
<pybamm.solvers.solution.Solution at 0x172b05190>,
None,
None,
None,
None,
<pybamm.solvers.solution.Solution at 0x172c5f2e0>,
None,
None,
None,
None,
<pybamm.solvers.solution.Solution at 0x172d43730>,
None,
None,
None,
None,
<pybamm.solvers.solution.Solution at 0x172bc1ac0>,
None,
None,
None,
None,
<pybamm.solvers.solution.Solution at 0x172f10940>,
None,
None,
None,
None,
<pybamm.solvers.solution.Solution at 0x172ec28e0>,
None,
None,
None,
None,
<pybamm.solvers.solution.Solution at 0x1731078b0>,
None,
None,
None,
None,
<pybamm.solvers.solution.Solution at 0x1731a2520>]
All summary variables are always available for every cycle, since these are much less memory-intensive
[15]:
pybamm.plot_summary_variables(sol_list)
[15]:
array([[<AxesSubplot:xlabel='Cycle number', ylabel='Capacity [A.h]'>,
<AxesSubplot:xlabel='Cycle number', ylabel='Loss of lithium inventory [%]'>,
<AxesSubplot:xlabel='Cycle number', ylabel='Loss of capacity to SEI [A.h]'>],
[<AxesSubplot:xlabel='Cycle number', ylabel='Loss of active material in negative electrode [%]'>,
<AxesSubplot:xlabel='Cycle number', ylabel='Loss of active material in positive electrode [%]'>,
<AxesSubplot:xlabel='Cycle number', ylabel='x_100'>],
[<AxesSubplot:xlabel='Cycle number', ylabel='x_0'>,
<AxesSubplot:xlabel='Cycle number', ylabel='y_100'>,
<AxesSubplot:xlabel='Cycle number', ylabel='y_0'>]], dtype=object)
A simulation can be performed iteratively by using the starting_solution feature. For example, we first solve for 10 cycles
[16]:
experiment=pybamm.Experiment([("Discharge at 1C until 3V","Rest for 1 hour","Charge at 1C until 4.2V","Hold at 4.2V until C/50",)]*10,termination="80% capacity",)sim=pybamm.Simulation(spm,experiment=experiment,parameter_values=parameter_values)sol=sim.solve()
2022-12-04 21:24:50.503 - [NOTICE] callbacks.on_cycle_start(174): Cycle 1/10 (22.792 us elapsed) --------------------
2022-12-04 21:24:50.503 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:50.544 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 2/4: Rest for 1 hour
2022-12-04 21:24:50.572 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:50.613 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:50.754 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.941 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:50.754 - [NOTICE] callbacks.on_cycle_start(174): Cycle 2/10 (251.450 ms elapsed) --------------------
2022-12-04 21:24:50.755 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:50.779 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 2/4: Rest for 1 hour
2022-12-04 21:24:50.800 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:50.825 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:50.868 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.913 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:50.869 - [NOTICE] callbacks.on_cycle_start(174): Cycle 3/10 (366.027 ms elapsed) --------------------
2022-12-04 21:24:50.869 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:50.893 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 2/4: Rest for 1 hour
2022-12-04 21:24:50.909 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:50.934 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:50.988 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.886 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:50.989 - [NOTICE] callbacks.on_cycle_start(174): Cycle 4/10 (486.547 ms elapsed) --------------------
2022-12-04 21:24:50.990 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:51.016 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 2/4: Rest for 1 hour
2022-12-04 21:24:51.031 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:51.055 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:51.103 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.859 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:51.104 - [NOTICE] callbacks.on_cycle_start(174): Cycle 5/10 (600.658 ms elapsed) --------------------
2022-12-04 21:24:51.104 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:51.128 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 2/4: Rest for 1 hour
2022-12-04 21:24:51.143 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:51.171 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:51.230 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.833 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:51.230 - [NOTICE] callbacks.on_cycle_start(174): Cycle 6/10 (727.148 ms elapsed) --------------------
2022-12-04 21:24:51.230 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:51.253 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 2/4: Rest for 1 hour
2022-12-04 21:24:51.267 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:51.288 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:51.337 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.807 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:51.338 - [NOTICE] callbacks.on_cycle_start(174): Cycle 7/10 (834.650 ms elapsed) --------------------
2022-12-04 21:24:51.338 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:51.361 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 2/4: Rest for 1 hour
2022-12-04 21:24:51.381 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:51.401 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:51.449 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.781 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:51.450 - [NOTICE] callbacks.on_cycle_start(174): Cycle 8/10 (946.869 ms elapsed) --------------------
2022-12-04 21:24:51.450 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:51.470 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 2/4: Rest for 1 hour
2022-12-04 21:24:51.484 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:51.503 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:51.551 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.756 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:51.552 - [NOTICE] callbacks.on_cycle_start(174): Cycle 9/10 (1.049 s elapsed) --------------------
2022-12-04 21:24:51.552 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:51.575 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 2/4: Rest for 1 hour
2022-12-04 21:24:51.589 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:51.611 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:51.659 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.732 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:51.660 - [NOTICE] callbacks.on_cycle_start(174): Cycle 10/10 (1.157 s elapsed) --------------------
2022-12-04 21:24:51.660 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:51.682 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 2/4: Rest for 1 hour
2022-12-04 21:24:51.695 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:51.717 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:51.766 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.708 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:51.767 - [NOTICE] callbacks.on_experiment_end(222): Finish experiment simulation, took 1.263 s
If we give sol as the starting solution this will then solve for the next 10 cycles
[17]:
sol2=sim.solve(starting_solution=sol)
2022-12-04 21:24:51.827 - [NOTICE] callbacks.on_cycle_start(174): Cycle 11/20 (30.792 us elapsed) --------------------
2022-12-04 21:24:51.828 - [NOTICE] callbacks.on_step_start(182): Cycle 11/20, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:51.859 - [NOTICE] callbacks.on_step_start(182): Cycle 11/20, step 2/4: Rest for 1 hour
2022-12-04 21:24:51.876 - [NOTICE] callbacks.on_step_start(182): Cycle 11/20, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:51.900 - [NOTICE] callbacks.on_step_start(182): Cycle 11/20, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:51.974 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.684 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:51.974 - [NOTICE] callbacks.on_cycle_start(174): Cycle 12/20 (147.399 ms elapsed) --------------------
2022-12-04 21:24:51.975 - [NOTICE] callbacks.on_step_start(182): Cycle 12/20, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:51.997 - [NOTICE] callbacks.on_step_start(182): Cycle 12/20, step 2/4: Rest for 1 hour
2022-12-04 21:24:52.010 - [NOTICE] callbacks.on_step_start(182): Cycle 12/20, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:52.028 - [NOTICE] callbacks.on_step_start(182): Cycle 12/20, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:52.227 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.660 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:52.227 - [NOTICE] callbacks.on_cycle_start(174): Cycle 13/20 (400.113 ms elapsed) --------------------
2022-12-04 21:24:52.227 - [NOTICE] callbacks.on_step_start(182): Cycle 13/20, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:52.245 - [NOTICE] callbacks.on_step_start(182): Cycle 13/20, step 2/4: Rest for 1 hour
2022-12-04 21:24:52.258 - [NOTICE] callbacks.on_step_start(182): Cycle 13/20, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:52.276 - [NOTICE] callbacks.on_step_start(182): Cycle 13/20, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:52.326 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.637 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:52.326 - [NOTICE] callbacks.on_cycle_start(174): Cycle 14/20 (498.938 ms elapsed) --------------------
2022-12-04 21:24:52.326 - [NOTICE] callbacks.on_step_start(182): Cycle 14/20, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:52.345 - [NOTICE] callbacks.on_step_start(182): Cycle 14/20, step 2/4: Rest for 1 hour
2022-12-04 21:24:52.360 - [NOTICE] callbacks.on_step_start(182): Cycle 14/20, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:52.378 - [NOTICE] callbacks.on_step_start(182): Cycle 14/20, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:52.428 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.614 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:52.428 - [NOTICE] callbacks.on_cycle_start(174): Cycle 15/20 (601.364 ms elapsed) --------------------
2022-12-04 21:24:52.429 - [NOTICE] callbacks.on_step_start(182): Cycle 15/20, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:52.450 - [NOTICE] callbacks.on_step_start(182): Cycle 15/20, step 2/4: Rest for 1 hour
2022-12-04 21:24:52.463 - [NOTICE] callbacks.on_step_start(182): Cycle 15/20, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:52.481 - [NOTICE] callbacks.on_step_start(182): Cycle 15/20, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:52.534 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.592 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:52.534 - [NOTICE] callbacks.on_cycle_start(174): Cycle 16/20 (707.075 ms elapsed) --------------------
2022-12-04 21:24:52.535 - [NOTICE] callbacks.on_step_start(182): Cycle 16/20, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:52.558 - [NOTICE] callbacks.on_step_start(182): Cycle 16/20, step 2/4: Rest for 1 hour
2022-12-04 21:24:52.571 - [NOTICE] callbacks.on_step_start(182): Cycle 16/20, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:52.593 - [NOTICE] callbacks.on_step_start(182): Cycle 16/20, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:52.645 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.569 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:52.645 - [NOTICE] callbacks.on_cycle_start(174): Cycle 17/20 (818.355 ms elapsed) --------------------
2022-12-04 21:24:52.646 - [NOTICE] callbacks.on_step_start(182): Cycle 17/20, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:52.669 - [NOTICE] callbacks.on_step_start(182): Cycle 17/20, step 2/4: Rest for 1 hour
2022-12-04 21:24:52.683 - [NOTICE] callbacks.on_step_start(182): Cycle 17/20, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:52.709 - [NOTICE] callbacks.on_step_start(182): Cycle 17/20, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:52.762 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.548 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:52.763 - [NOTICE] callbacks.on_cycle_start(174): Cycle 18/20 (935.547 ms elapsed) --------------------
2022-12-04 21:24:52.763 - [NOTICE] callbacks.on_step_start(182): Cycle 18/20, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:52.786 - [NOTICE] callbacks.on_step_start(182): Cycle 18/20, step 2/4: Rest for 1 hour
2022-12-04 21:24:52.799 - [NOTICE] callbacks.on_step_start(182): Cycle 18/20, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:52.821 - [NOTICE] callbacks.on_step_start(182): Cycle 18/20, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:52.877 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.526 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:52.878 - [NOTICE] callbacks.on_cycle_start(174): Cycle 19/20 (1.051 s elapsed) --------------------
2022-12-04 21:24:52.878 - [NOTICE] callbacks.on_step_start(182): Cycle 19/20, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:52.900 - [NOTICE] callbacks.on_step_start(182): Cycle 19/20, step 2/4: Rest for 1 hour
2022-12-04 21:24:52.914 - [NOTICE] callbacks.on_step_start(182): Cycle 19/20, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:52.933 - [NOTICE] callbacks.on_step_start(182): Cycle 19/20, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:52.988 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.505 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:52.988 - [NOTICE] callbacks.on_cycle_start(174): Cycle 20/20 (1.161 s elapsed) --------------------
2022-12-04 21:24:52.989 - [NOTICE] callbacks.on_step_start(182): Cycle 20/20, step 1/4: Discharge at 1C until 3V
2022-12-04 21:24:53.007 - [NOTICE] callbacks.on_step_start(182): Cycle 20/20, step 2/4: Rest for 1 hour
2022-12-04 21:24:53.020 - [NOTICE] callbacks.on_step_start(182): Cycle 20/20, step 3/4: Charge at 1C until 4.2V
2022-12-04 21:24:53.039 - [NOTICE] callbacks.on_step_start(182): Cycle 20/20, step 4/4: Hold at 4.2V until C/50
2022-12-04 21:24:53.094 - [NOTICE] callbacks.on_cycle_end(196): Capacity is now 4.484 Ah (originally 4.941 Ah, will stop at 3.952 Ah)
2022-12-04 21:24:53.095 - [NOTICE] callbacks.on_experiment_end(222): Finish experiment simulation, took 1.267 s
[1] Weilong Ai, Ludwig Kraft, Johannes Sturm, Andreas Jossen, and Billy Wu. Electrochemical thermal-mechanical modelling of stress inhomogeneity in lithium-ion pouch cells. Journal of The Electrochemical Society, 167(1):013512, 2019. doi:10.1149/2.0122001JES.
[2] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[3] Ferran Brosa Planella and W. Dhammika Widanage. Systematic derivation of a Single Particle Model with Electrolyte and Side Reactions (SPMe+SR) for degradation of lithium-ion batteries. Submitted for publication, ():, 2022. doi:.
[4] Rutooj Deshpande, Mark Verbrugge, Yang-Tse Cheng, John Wang, and Ping Liu. Battery cycle life prediction with coupled chemical degradation and fatigue mechanics. Journal of the Electrochemical Society, 159(10):A1730, 2012. doi:10.1149/2.049210jes.
[5] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[6] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[7] Peyman Mohtat, Suhak Lee, Jason B Siegel, and Anna G Stefanopoulou. Towards better estimability of electrode-specific state of health: decoding the cell expansion. Journal of Power Sources, 427:101–111, 2019.
[8] Peyman Mohtat, Suhak Lee, Valentin Sulzer, Jason B. Siegel, and Anna G. Stefanopoulou. Differential Expansion and Voltage Model for Li-ion Batteries at Practical Charging Rates. Journal of The Electrochemical Society, 167(11):110561, 2020. doi:10.1149/1945-7111/aba5d1.
[9] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[10] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
Tip
An interactive online version of this notebook is available, which can be
accessed via
The simplest way to solve a model is to use the Simulation class. This automatically processes the model (setting of parameters, setting up the mesh and discretisation, etc.) for you, and provides built-in functionality for solving and plotting. Changing things such as parameters in handled by passing options to the Simulation, as shown in the Getting Started guides, example notebooks and
documentation.
In this notebook we show how to solve a model using a Simulation and compare this to manually handling the different stages of the process, such as setting parameters, ourselves step-by-step.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybamm
The easiest way to get started is to pick a model and create a simulation using that model. For simplicity, we’ll use the SPM with all the default options here.
The simulation can then be solved, passing a time interval (in seconds) to integrate over
[3]:
simulation.solve([0,3600])
[3]:
<pybamm.solvers.solution.Solution at 0x2698c4bb3d0>
and the results plotted
[4]:
simulation.plot()
[4]:
<pybamm.plotting.quick_plot.QuickPlot at 0x2698c4da370>
Simple!
A GIF of the simulation can also be obtained
[5]:
# using less number of images in the example# for a smoother GIF use more imagessimulation.create_gif(number_of_images=5,duration=0.2,output_filename="simulation.gif")
One way of gaining more control over the simulation processing is by passing options, as outlined in the documentation. However, you can also process the model step-by-step yourself. A detailed example of this can be found in the SPM notebook, but here we outline the basic steps.
First we pick a model
[6]:
model=pybamm.lithium_ion.SPM()
Next we must set up the geometry. We’ll use the default geometry for the SPM. In all of the following steps we will also use the default settings provided by the model. For a look at changing these options, see the change settings notebook.
[7]:
geometry=model.default_geometry
Both the model and geometry depend on parameters (such as the electrode thickness, or diffusivity). We’ll use the default model parameters
[8]:
param=model.default_parameter_values
Now that we have picked our parameters we can “process” the model and geometry. This just means we look through the model and geometry for any parameter symbols and replace them with the numeric values (or functions, in the case of parameters that have functional dependence) defined by our parameter values.
Next we must create a mesh on which to solve the discretised equations. This not only depends on the geometry, but also on the type of submesh (e.g. uniformly space) and number of mesh points to use.
Now that we have defined a mesh we can discretise our model. In order to do so we must choose a spatial method. The default for the SPM is the Finite Volume Method. We first define a discretisation, which depends on the mesh and spatial method, and then use this to process our model. This turns the variables in the models into a StateVector, and replaces spatial operators with matrix-vector multiplications, ready to be passed to a time stepping algorithm.
<pybamm.models.full_battery_models.lithium_ion.spm.SPM at 0x2699d43f100>
Finally, we pick a solver to step the problem forward in time. We’ll use the default ODE solver for the SPM
[12]:
solver=model.default_solver
We then integrate in time using the solve command, as with the simulation. Note that we now have to pass the model object to the solve command, and that we return the solution object so that we can interact with it later.
[13]:
solution=solver.solve(model,[0,3600])
We can create the default slider plot by passing the solution object to the dynamic_plot method
[14]:
pybamm.dynamic_plot(solution)
[14]:
<pybamm.plotting.quick_plot.QuickPlot at 0x2699064c760>
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[4] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
This notebook shows how to customize PyBaMM’s QuickPlot, using matplotlib’s style sheets and rcParams
First we define and solve the models
[8]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammmodels=[pybamm.lithium_ion.SPM(),pybamm.lithium_ion.SPMe(),pybamm.lithium_ion.DFN()]sims=[]formodelinmodels:sim=pybamm.Simulation(model)sim.solve([0,3600])sims.append(sim)
[notice] A new release of pip is available: 23.0.1 -> 23.1.2[notice] To update, run: pip install --upgrade pip
Note: you may need to restart the kernel to use updated packages.
Call the default plots
[9]:
pybamm.dynamic_plot(sims)
[9]:
<pybamm.plotting.quick_plot.QuickPlot at 0x28810b850>
For example we can use the ggplot style from R. In this case, the title fonts are quite large, so we reduce the number of words in a title before a line break
Sometimes we want further customization of a style, without needing to edit the style sheets. For example, we can update the font sizes and plot again.
Some customization of the QuickPlot object is possible by passing arguments - see the docs for details
We can also further control the plot by calling plot.fig after the figure has been created, and editing the matplotlib objects. For example, here we move the titles to the ylabel, and move the legend.
[13]:
pybamm.settings.max_words_in_line=4plot=pybamm.QuickPlot(sims,figsize=(14,7))plot.plot(0.5)# time in hours# Move title to ylabelforaxinplot.fig.axes:title=ax.get_title()ax.set_title("")ax.set_ylabel(title)# Remove old legend and add a new one in the bottomleg=plot.fig.get_children()[-1]leg.set_visible(False)plot.fig.legend(plot.labels,loc="lower center",ncol=len(plot.labels),fontsize=11)# Adjust layoutplot.gridspec.tight_layout(plot.fig,rect=[0,0.04,1,1])
The figure can then be saved using plot.fig.savefig
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[5] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
Note: you may need to restart the kernel to use updated packages.
At t = 472.93 and h = 1.932e-14, the corrector convergence failed repeatedly or with |h| = hmin.
At t = 232.93 and h = 3.07648e-13, the corrector convergence failed repeatedly or with |h| = hmin.
[1]:
<pybamm.plotting.quick_plot.QuickPlot at 0x1217ec4f0>
A more useful visualization is given by the plot_voltage_components function, which can either plot all the individual voltage components
(<Figure size 800x400 with 1 Axes>, <AxesSubplot: xlabel='Time [h]'>)
or group positive and negative together
[3]:
pybamm.plot_voltage_components(sol)
[3]:
(<Figure size 800x400 with 1 Axes>, <AxesSubplot: xlabel='Time [h]'>)
Note that this function further splits the electrolyte overpotential into the contribution from the electrolyte gradient and the contribution from ohmic losses
[1] Weilong Ai, Ludwig Kraft, Johannes Sturm, Andreas Jossen, and Billy Wu. Electrochemical thermal-mechanical modelling of stress inhomogeneity in lithium-ion pouch cells. Journal of The Electrochemical Society, 167(1):013512, 2019. doi:10.1149/2.0122001JES.
[2] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[3] Chang-Hui Chen, Ferran Brosa Planella, Kieran O'Regan, Dominika Gastol, W. Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.
[4] Rutooj Deshpande, Mark Verbrugge, Yang-Tse Cheng, John Wang, and Ping Liu. Battery cycle life prediction with coupled chemical degradation and fatigue mechanics. Journal of the Electrochemical Society, 159(10):A1730, 2012. doi:10.1149/2.049210jes.
[5] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[6] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[7] Peyman Mohtat, Suhak Lee, Jason B Siegel, and Anna G Stefanopoulou. Towards better estimability of electrode-specific state of health: decoding the cell expansion. Journal of Power Sources, 427:101–111, 2019.
[8] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[9] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this notebook, we show some examples of solving a DAE model. For the purposes of this example, we use the CasADi solver, but the syntax remains the same for other solvers
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpimportosimportmatplotlib.pyplotaspltos.chdir(pybamm.__path__[0]+"/..")
WARNING: You are using pip version 22.0.4; however, version 22.3.1 is available.
You should consider upgrading via the '/home/mrobins/git/PyBaMM/env/bin/python -m pip install --upgrade pip' command.Note: you may need to restart the kernel to use updated packages.
In PyBaMM, a model is solved by calling a solver with solve. This sets up the model to be solved, and then calls the method _integrate, which is specific to each solver. We begin by setting up and discretising a model
Now the model can be solved by calling solver.solve with a specific time vector at which to evaluate the solution
[3]:
# Solve #################################t_eval=np.linspace(0,2,30)dae_solver=pybamm.CasadiSolver(mode="safe")solution=dae_solver.solve(model,t_eval)########################################## Extract u and vt_sol=solution.tu=solution["u"]v=solution["v"]# Plott_fine=np.linspace(0,t_eval[-1],1000)fig,(ax1,ax2)=plt.subplots(1,2,figsize=(13,4))ax1.plot(t_fine,np.exp(-2*t_fine),t_sol,u(t_sol),"o")ax1.set_xlabel("t")ax1.legend(["exp(-2*t)","u"],loc="best")ax2.plot(t_fine,2*np.exp(-2*t_fine),t_sol,v(t_sol),"o")ax2.set_xlabel("t")ax2.legend(["2*exp(-2*t)","v"],loc="best")plt.tight_layout()plt.show()
Note that, where possible, the solver makes use of the mass matrix and jacobian for the model. However, the discretisation or solver will have created the mass matrix and jacobian algorithmically, using the expression tree, so we do not need to calculate and input these manually.
The solution terminates at the final simulation time:
It is possible to specify events at which a solution should terminate. This is done by adding events to the model.events dictionary. In the following example, we solve the same model as before but add a termination event when v=-0.2.
[5]:
# Create modelmodel=pybamm.BaseModel()u=pybamm.Variable("u")v=pybamm.Variable("v")model.rhs={u:-v}# du/dt = -vmodel.algebraic={v:2*u-v}# 2*v = umodel.initial_conditions={u:1,v:2}model.events.append(pybamm.Event("v=0.2",v-0.2))# adding event heremodel.variables={"u":u,"v":v}# Discretise using default discretisationdisc=pybamm.Discretisation()disc.process_model(model)# Solve #################################t_eval=np.linspace(0,2,30)dae_solver=pybamm.CasadiSolver(mode="safe")solution=dae_solver.solve(model,t_eval)########################################## Extract u and vt_sol=solution.tu=solution["u"]v=solution["v"]# Plott_fine=np.linspace(0,t_eval[-1],1000)fig,(ax1,ax2)=plt.subplots(1,2,figsize=(13,4))ax1.plot(t_fine,np.exp(-2*t_fine),t_sol,u(t_sol),"o")ax1.set_xlabel("t")ax1.legend(["exp(-2*t)","u"],loc="best")ax2.plot(t_fine,2*np.exp(-2*t_fine),t_sol,v(t_sol),"o",t_fine,0.2*np.ones_like(t_fine),"k",)ax2.set_xlabel("t")ax2.legend(["2*exp(-2*t)","v","v = 0.2"],loc="best")plt.tight_layout()plt.show()
Now the solution terminates because the event has been reached
The solver will fail if initial conditions that are inconsistent with the algebraic equations are provided. However, before solving the DAE solvers automatically use _set_initial_conditions to obtain consistent initial conditions, starting from a guess of bad initial conditions, using a simple root-finding algorithm.
[7]:
# Create modelmodel=pybamm.BaseModel()u=pybamm.Variable("u")v=pybamm.Variable("v")model.rhs={u:-v}# du/dt = -vmodel.algebraic={v:2*u-v}# 2*v = umodel.initial_conditions={u:1,v:1}# bad initial conditions, solver fixesmodel.events.append(pybamm.Event("v=0.2",v-0.2))model.variables={"u":u,"v":v}# Discretise using default discretisationdisc=pybamm.Discretisation()disc.process_model(model)print(f"y0_guess={model.concatenated_initial_conditions.evaluate().flatten()}")dae_solver=pybamm.CasadiSolver(mode="safe")dae_solver.set_up(model)dae_solver._set_initial_conditions(model,0,{},True)print(f"y0_fixed={model.y0}")
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this notebook, we show some examples of solving an ODE model. For the purposes of this example, we use the Scipy solver, but the syntax remains the same for other solvers
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpimportosimportmatplotlib.pyplotaspltos.chdir(pybamm.__path__[0]+"/..")
WARNING: You are using pip version 22.0.4; however, version 22.3.1 is available.
You should consider upgrading via the '/home/mrobins/git/PyBaMM/env/bin/python -m pip install --upgrade pip' command.Note: you may need to restart the kernel to use updated packages.
In PyBaMM, a model is solved by calling a solver with solve. This sets up the model to be solved, and then calls the method _integrate, which is specific to each solver. We begin by setting up and discretising a model
[2]:
# Create modelmodel=pybamm.BaseModel()u=pybamm.Variable("u")v=pybamm.Variable("v")model.rhs={u:-v,v:u}model.initial_conditions={u:2,v:1}model.variables={"u":u,"v":v}# Discretise using default discretisationdisc=pybamm.Discretisation()disc.process_model(model);
Now the model can be solved by calling solver.solve with a specific time vector at which to evaluate the solution
[3]:
# Solve ########################t_eval=np.linspace(0,5,30)ode_solver=pybamm.ScipySolver()solution=ode_solver.solve(model,t_eval)################################# Extract u and vt_sol=solution.tu=solution["u"]v=solution["v"]# Plott_fine=np.linspace(0,t_eval[-1],1000)fig,(ax1,ax2)=plt.subplots(1,2,figsize=(13,4))ax1.plot(t_fine,2*np.cos(t_fine)-np.sin(t_fine),t_sol,u(t_sol),"o")ax1.set_xlabel("t")ax1.legend(["2*cos(t) - sin(t)","u"],loc="best")ax2.plot(t_fine,2*np.sin(t_fine)+np.cos(t_fine),t_sol,v(t_sol),"o")ax2.set_xlabel("t")ax2.legend(["2*sin(t) + cos(t)","v"],loc="best")plt.tight_layout()plt.show()
Note that, where possible, the solver makes use of the mass matrix and jacobian for the model. However, the discretisation or solver will have created the mass matrix and jacobian algorithmically, using the expression tree, so we do not need to calculate and input these manually.
The solution terminates at the final simulation time:
It is possible to specify events at which a solution should terminate. This is done by adding events to the model.events dictionary. In the following example, we solve the same model as before but add a termination event when v=-2.
[5]:
# Create modelmodel=pybamm.BaseModel()u=pybamm.Variable("u")v=pybamm.Variable("v")model.rhs={u:-v,v:u}model.initial_conditions={u:2,v:1}model.events.append(pybamm.Event("v=-2",v+2))# New termination eventmodel.variables={"u":u,"v":v}# Discretise using default discretisationdisc=pybamm.Discretisation()disc.process_model(model)# Solve ########################t_eval=np.linspace(0,5,30)ode_solver=pybamm.ScipySolver()solution=ode_solver.solve(model,t_eval)################################# Extract u and vt_sol=solution.tu=solution["u"]v=solution["v"]# Plott_fine=np.linspace(0,t_eval[-1],1000)fig,(ax1,ax2)=plt.subplots(1,2,figsize=(13,4))ax1.plot(t_fine,2*np.cos(t_fine)-np.sin(t_fine),t_sol,u(t_sol),"o")ax1.set_xlabel("t")ax1.legend(["2*cos(t) - sin(t)","u"],loc="best")ax2.plot(t_fine,2*np.sin(t_fine)+np.cos(t_fine),t_sol,v(t_sol),"o",t_fine,-2*np.ones_like(t_fine),"k",)ax2.set_xlabel("t")ax2.legend(["2*sin(t) + cos(t)","v","v = -2"],loc="best")plt.tight_layout()plt.show()
Now the solution terminates because the event has been reached
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[4] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
Tip
An interactive online version of this notebook is available, which can be
accessed via
Since it is very easy to switch which solver is used for the model, we recommend you try different solvers for your particular use case. In general, the CasadiSolver is the fastest.
Once you have found a good solver, you can further improve performance by trying out different values for the method, rtol, and atol arguments. Further options are sometimes available, but are solver specific. See solver API docs for details.
The CasadiSolver comes with a mode argument which can be set to “fast” or “safe” (the third option, “safe without grid”, is experimental and should not be used for now). The “fast” mode is faster but ignores any “events” (such as a voltage cut-off point), while the “safe” mode is slower but does stop at events (with manually implemented “step-and-check” under the hood). Therefore, “fast” mode should be used whenever events are not expected to be hit (for example, when simulating a drive
cycle or a constant-current discharge where the time interval is such that the simulation will finish before reaching the voltage cut-off). Conversely, “safe” mode should be used whenever events are important: in particular, when using the Experiment class.
To demonstrate the difference between safe mode and fast mode, consider the following example
[2]:
# Set up modelmodel=pybamm.lithium_ion.DFN()param=model.default_parameter_valuescap=param["Nominal cell capacity [A.h]"]param["Current function [A]"]=cap*pybamm.InputParameter("Crate")sim=pybamm.Simulation(model,parameter_values=param)# Set up solvers. Reduce max_num_steps for the fast solver, for faster errorsfast_solver=pybamm.CasadiSolver(mode="fast",extra_options_setup={"max_num_steps":1000})safe_solver=pybamm.CasadiSolver(mode="safe")
Both solvers can solve the model up to 3700 s, but the fast solver ignores the voltage cut-off around 3.1 V
If we increase the integration interval, the safe solver still stops at the same point, but the fast solver fails
[4]:
safe_sol=sim.solve([0,4500],solver=safe_solver,inputs={"Crate":1})print("Safe:",safe_sol.solve_time)plt.plot(safe_sol["Time [h]"].data,safe_sol["Voltage [V]"].data,"r-",label="Safe")plt.plot(safe_sol["Time [h]"].data,cutoff*np.ones_like(safe_sol["Time [h]"].data),"k--",label="Voltage cut-off",)plt.legend()try:sim.solve([0,4500],solver=fast_solver,inputs={"Crate":1})exceptpybamm.SolverErrorase:print("Solving fast mode, error occurred:",e.args[0])
At t = 506.167, , mxstep steps taken before reaching tout.
Safe: 7.181 s
Solving fast mode, error occurred: Error in Function::call for 'F' [IdasInterface] at .../casadi/core/function.cpp:1401:
Error in Function::call for 'F' [IdasInterface] at .../casadi/core/function.cpp:330:
.../casadi/interfaces/sundials/idas_interface.cpp:596: IDASolve returned "IDA_TOO_MUCH_WORK". Consult IDAS documentation.
At t = 4051.62, , mxstep steps taken before reaching tout.
We can solve with fast mode up to close to this time to understand why the model is failing
In this case, we can see that the reason the solver is failing is that the concentration at the surface of the particles in the positive electrode hit their maximum value of c_max. Since the exchange current density has a term sqrt(c_max-c_s_surf), the square root of a negative number is complex, c_s_surf going above c_max will cause the solver to fail.
As a final note, there are some cases where the “safe” mode prints some warnings. This is linked to how the solver looks for events (sometimes stepping too far), and can be safely ignored if the solution looks sensible.
The parameter dt_max controls how large the steps taken by the CasadiSolver with “safe” mode are when looking for events.
[9]:
fordt_maxin[10,20,100,1000,3700]:safe_sol=sim.solve([0,3600],solver=pybamm.CasadiSolver(mode="safe",dt_max=dt_max),inputs={"Crate":1},)print(f"With dt_max={dt_max}, took {safe_sol.solve_time} "+f"(integration time: {safe_sol.integration_time})")fast_sol=sim.solve([0,3600],solver=fast_solver,inputs={"Crate":1})print(f"With 'fast' mode, took {fast_sol.solve_time} "+f"(integration time: {fast_sol.integration_time})")
With dt_max=10, took 575.783 ms (integration time: 508.473 ms)
With dt_max=20, took 575.500 ms (integration time: 510.705 ms)
With dt_max=100, took 316.721 ms (integration time: 275.459 ms)
With dt_max=1000, took 76.646 ms (integration time: 49.294 ms)
With dt_max=3700, took 48.773 ms (integration time: 32.436 ms)
With 'fast' mode, took 42.224 ms (integration time: 32.177 ms)
In general, a larger value of dt_max gives a faster solution, since fewer integrator creations and calls are required.
Below the solution time interval of 36s, the value of dt_max does not affect the solve time, since steps must be at least 36s large. The discrepancy between the solve time and integration time is due to the extra operations recorded by “solve time”, such as creating the integrator. The “fast” solver does not need to do this (it reuses the first one it had already created), so the solve time is much closer to the integration time.
The example above was a case where no events are triggered, so the largest dt_max works well. If we step over events, then it is possible to makes dt_max too large, so that the solver will attempt (and fail) to take large steps past the event, iteratively reducing the step size until it works. For example:
[10]:
fordt_maxin[10,20,100,1000,3600]:# Reduce max_num_steps to fail fastersafe_sol=sim.solve([0,4500],solver=pybamm.CasadiSolver(mode="safe",dt_max=dt_max,extra_options_setup={"max_num_steps":1000}),inputs={"Crate":1},)print(f"With dt_max={dt_max}, took {safe_sol.solve_time} "+f"(integration time: {safe_sol.integration_time})")
With dt_max=10, took 504.163 ms (integration time: 419.740 ms)
With dt_max=20, took 504.691 ms (integration time: 421.396 ms)
With dt_max=100, took 286.620 ms (integration time: 238.390 ms)
With dt_max=1000, took 98.500 ms (integration time: 60.880 ms)
At t = 460.712, , mxstep steps taken before reaching tout.
At t = 460.712 and h = 1.26752e-15, the corrector convergence failed repeatedly or with |h| = hmin.
At t = 460.712 and h = 9.51455e-16, the corrector convergence failed repeatedly or with |h| = hmin.
With dt_max=3600, took 645.118 ms (integration time: 32.601 ms)
The integration time with dt_max=3600 remains the fastest, but the solve time is the slowest due to all the failed steps.
The “period” argument of the experiments also affects how long the simulations take, for a similar reason to dt_max. Therefore, this argument can be manually tuned to speed up how long an experiment takes to solve.
We start with one cycle of CCCV
[11]:
experiment=pybamm.Experiment(["Discharge at C/10 for 10 hours or until 3.3 V","Rest for 1 hour","Charge at 1 A until 4.1 V","Hold at 4.1 V until 50 mA","Rest for 1 hour",])solver=pybamm.CasadiSolver(mode="safe",extra_options_setup={"max_num_steps":1000})sim=pybamm.Simulation(model,experiment=experiment,solver=solver)sol=sim.solve()print("Took ",sol.solve_time)
We can speed up the experiment by increasing the period, but tradeoff is that the resolution of the solution becomes worse
[13]:
experiment=pybamm.Experiment(["Discharge at C/10 for 10 hours or until 3.3 V","Rest for 1 hour","Charge at 1 A until 4.1 V","Hold at 4.1 V until 50 mA","Rest for 1 hour",],period="10 minutes",)sim=pybamm.Simulation(model,experiment=experiment,solver=solver)sol=sim.solve()print("Took ",sol.solve_time)plt.plot(sol["Time [s]"].data,sol["Voltage [V]"].data);
Took 598.237 ms
If we increase the period too much, the experiment becomes slower as the solver takes more failing steps
[14]:
experiment=pybamm.Experiment(["Discharge at C/10 for 10 hours or until 3.3 V","Rest for 1 hour","Charge at 1 A until 4.1 V","Hold at 4.1 V until 50 mA","Rest for 1 hour",],period="30 minutes",)sim=pybamm.Simulation(model,experiment=experiment,solver=solver)sol=sim.solve()print("Took ",sol.solve_time)plt.plot(sol["Time [s]"].data,sol["Voltage [V]"].data);
At t = 1262.29 and h = 3.51225e-14, the corrector convergence failed repeatedly or with |h| = hmin.
Took 532.042 ms
We can control the period of individual parts of the experiment to get the fastest solution (again, at the cost of resolution)
[15]:
s=pybamm.step.stringexperiment=pybamm.Experiment([s("Discharge at C/10 for 10 hours or until 3.3 V",period="5 hours"),s("Rest for 1 hour",period="30 minutes"),s("Charge at 1 C until 4.1 V",period="10 minutes"),s("Hold at 4.1 V until 50 mA",period="10 minutes"),s("Rest for 1 hour",period="30 minutes"),],)solver=pybamm.CasadiSolver(mode="safe",extra_options_setup={"max_num_steps":1000})sim=pybamm.Simulation(model,experiment=experiment,solver=solver)sol=sim.solve()print("Took ",sol.solve_time)plt.plot(sol["Time [s]"].data,sol["Voltage [V]"].data);
Took 257.314 ms
As you can see, this kind of optimization requires a lot of manual tuning. We are working on ways to make the experiment class more efficient in general.
Finally, in some cases, changing the time interval (either the step size or the final time) may affect whether or not the casadi solver can solve the system. Therefore, if the casadi solver is failing, it may be worth changing the time interval (usually, reducing step size or final time) to see if that allows the solver to solve the model. Unfortunately, we have not yet been able to isolate a minimum working example to demonstrate this effect.
If the solver is taking a lot of steps, possibly failing with a max_steps error, and the error persists with different solvers and options, this suggests a problem with the model itself. This can be due to a few things:
A singularity in the model (such as division by zero). Solve up to the time where the model fails, and plot some variables to see if they are going to infinity. You can then narrow down the source of the problem.
High model stiffness. Set the scale parameter of variables so that their scaled nominal value (e.g. initial condition) is of order 1, and multiply algebraic equations by appropriate constants so that they are roughly of order 1.
If none of these fixes work, we are interested in finding out why - please get in touch!
Smooth approximations to non-differentiable functions#
Some functions, such as minimum, maximum, heaviside, and abs, are discontinuous and/or non-differentiable (their derivative is discontinuous). Adaptive solvers can deal with this discontinuity, but will take many more steps close to the discontinuity in order to resolve it. Therefore, using soft approximations instead can reduce the number of steps taken by the solver, and hence the integration time. See this
post for more details.
Here is an example using the maximum function. The function maximum(x,1) is continuous but non-differentiable at x=1, where its derivative jumps from 0 to 1. However, we can approximate it using the `softplus function <https://en.wikipedia.org/wiki/Rectifier_(neural_networks)#Softplus>`__, which is smooth everywhere and is sometimes used in neural networks as a smooth approximation to the RELU activation function. The softplus function is given by
\[s(x,y;k) = \frac{\log(\exp(kx)+\exp(ky))}{k},\]
where k is a strictly positive smoothing (or sharpness) parameter. The larger the value of k, the better the approximation but the stiffer the term (exp blows up quickly!). Usually, a value of k=10 is a good middle ground.
In PyBaMM, you can either call the softplus function directly, or change pybamm.settings.max_smoothing to automatically replace all your calls to pybamm.maximum with softplus.
[16]:
x=pybamm.Variable("x")y=pybamm.Variable("y")# Normal maximumprint(f"Exact maximum: {pybamm.maximum(x,y)}")# Softplusprint("Softplus (k=10): ",pybamm.softplus(x,y,10))# Changing the setting to call softplus automaticallypybamm.settings.min_max_mode="soft"pybamm.settings.min_max_smoothing=20print(f"Softplus (k=20): {pybamm.maximum(x,y)}")# All smoothing parameters can be changed at oncepybamm.settings.set_smoothing_parameters(30)print(f"Softplus (k=30): {pybamm.maximum(x,y)}")# Change backpybamm.settings.set_smoothing_parameters("exact")print(f"Exact maximum: {pybamm.maximum(x,y)}")
Note that if both sides are constant then pybamm will use the exact value even if the setting is set to smoothing
[17]:
a=pybamm.InputParameter("a")pybamm.settings.max_smoothing=20# Both inputs are constant so uses exact maximumprint("Exact:",pybamm.maximum(0.999,1).evaluate())# One input is not constant (InputParameter) so uses softplusprint("Softplus:",pybamm.maximum(a,1).evaluate(inputs={"a":0.999}))pybamm.settings.set_smoothing_parameters("exact")
Exact: 1.0
Softplus: 1.0
Here is the plot of softplus with different values of k
Solving a model with the exact maximum and soft approximation, demonstrates a clear speed-up even for a very simple model
[19]:
model_exact=pybamm.BaseModel()model_exact.rhs={x:pybamm.maximum(x,1)}model_exact.initial_conditions={x:0.5}model_exact.variables={"x":x,"max(x,1)":pybamm.maximum(x,1)}model_smooth=pybamm.BaseModel()k=pybamm.InputParameter("k")model_smooth.rhs={x:pybamm.softplus(x,1,k)}model_smooth.initial_conditions={x:0.5}model_smooth.variables={"x":x,"max(x,1)":pybamm.softplus(x,1,k)}# Exact solutiontimer=pybamm.Timer()time=0solver=pybamm.CasadiSolver(mode="fast")for_inrange(100):exact_sol=solver.solve(model_exact,[0,2])# Report integration time, which is the time spent actually doing the integrationtime+=exact_sol.integration_timeprint("Exact:",time/100)sols=[exact_sol]ks=[5,10,100]solver=pybamm.CasadiSolver(mode="fast")forkinks:time=0for_inrange(100):sol=solver.solve(model_smooth,[0,2],inputs={"k":k})time+=sol.integration_timeprint(f"Soft, k={k}:",time/100)sols.append(sol)pybamm.dynamic_plot(sols,["x","max(x,1)"],labels=["exact"]+[f"soft (k={k})"forkinks]);
Exact: 172.706 us
Soft, k=5: 160.098 us
Soft, k=10: 133.737 us
Soft, k=100: 168.833 us
For the minimum and maximum functions, an alternative smoothing functions (smooth_max, smooth_min) are provided.
For the smooth minimum and maximum functions, the recommended value of k is 100, where the function closely approximates the exact function, but is differentiable.
Changing between the soft, smooth, and exact functions can be done by setting the min_max_mode and the value of k stored in min_max_smoothing
[20]:
x=pybamm.Variable("x")y=pybamm.Variable("y")# Normal maximumprint(f"Exact maximum: {pybamm.maximum(x,y)}")# Smooth plus can be called explicitlyprint("Smooth plus (k=100): ",pybamm.smooth_max(x,y,100))# Smooth plus and smooth minus will be used when the mode is set to "smooth"pybamm.settings.min_max_mode="smooth"pybamm.settings.min_max_smoothing=200print(f"Smooth plus (k=200): {pybamm.maximum(x,y)}")# Setting the smoothing parameters with set_smoothing_parameters() defaults to softpluspybamm.settings.set_smoothing_parameters(10)print(f"Softplus (k=10): {pybamm.maximum(x,y)}")# Change backpybamm.settings.set_smoothing_parameters("exact")print(f"Exact maximum: {pybamm.maximum(x,y)}")
Solving a model with the exact maximum and smooth approximation, demonstrates a clear speed-up even for a very simple model
[22]:
model_exact=pybamm.BaseModel()model_exact.rhs={x:pybamm.maximum(x,1)}model_exact.initial_conditions={x:0.5}model_exact.variables={"x":x,"max(x,1)":pybamm.maximum(x,1)}model_smooth=pybamm.BaseModel()k=pybamm.InputParameter("k")model_smooth.rhs={x:pybamm.smooth_max(x,1,k)}model_smooth.initial_conditions={x:0.5}model_smooth.variables={"x":x,"max(x,1)":pybamm.smooth_max(x,1,k)}# Exact solutiontimer=pybamm.Timer()time=0solver=pybamm.CasadiSolver(mode="fast")for_inrange(100):exact_sol=solver.solve(model_exact,[0,2])# Report integration time, which is the time spent actually doing the integrationtime+=exact_sol.integration_timeprint("Exact:",time/100)sols=[exact_sol]ks=[10,50,100,1000,10000]solver=pybamm.CasadiSolver(mode="fast")forkinks:time=0for_inrange(100):sol=solver.solve(model_smooth,[0,2],inputs={"k":k})time+=sol.integration_timeprint(f"Smooth, k={k}:",time/100)sols.append(sol)pybamm.dynamic_plot(sols,["x","max(x,1)"],labels=["exact"]+[f"soft (k={k})"forkinks]);
Exact: 168.348 us
Smooth, k=10: 149.515 us
Smooth, k=50: 170.092 us
Smooth, k=100: 137.928 us
Smooth, k=1000: 200.991 us
Smooth, k=10000: 175.300 us
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[5] Peyman Mohtat, Suhak Lee, Jason B Siegel, and Anna G Stefanopoulou. Towards better estimability of electrode-specific state of health: decoding the cell expansion. Journal of Power Sources, 427:101–111, 2019.
[6] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[7] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
[8] Andrew Weng, Jason B Siegel, and Anna Stefanopoulou. Differential voltage analysis for battery manufacturing process control. arXiv preprint arXiv:2303.07088, 2023.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this notebook, we explain the discretisation process that converts an expression tree, representing a model, to a linear algebra tree that can be evaluated by the solvers.
We use Finite Volumes as an example of a spatial method, since it is the default spatial method for most PyBaMM models. This is a good spatial method for battery problems as it is conservative: for lithium-ion battery models, we can be sure that the total amount of lithium in the system is constant. For more details on the Finite Volume method, see Randall Leveque’s
book.
This notebook is structured as follows:
Setting up a discretisation. Overview of the parameters that are passed to the discretisation
Discretisations and spatial methods. Operations that are common to most spatial methods:
Discretising a spatial variable (e.g. \(x\))
Discretising a variable (e.g. concentration)
Example: Finite Volume operators. Finite Volume implementation of some useful operators:
Gradient operator
Divergence operator
Integral operator
Example: Discretising a simple model. Setting up and solving a simple model, using Finite Volumes as the spatial method
We first import pybamm and some useful other modules, and change our working directory to the root of the PyBaMM folder:
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpimportosimportmatplotlib.pyplotaspltos.chdir(pybamm.__path__[0]+"/..")
Note: you may need to restart the kernel to use updated packages.
To set up a discretisation, we must create a geometry, mesh this geometry, and then create the discretisation with the appropriate spatial method(s). The easiest way to create a geometry is to the inbuilt battery geometry:
Spatial variables, such as \(x\) and \(r\), are converted to pybamm.Vector nodes
[5]:
# Set upmacroscale=["negative electrode","separator","positive electrode"]x_var=pybamm.SpatialVariable("x",domain=macroscale)r_var=pybamm.SpatialVariable("r",domain=["negative particle"])# Discretisex_disc=disc.process_symbol(x_var)r_disc=disc.process_symbol(r_var)print(f"x_disc is a {type(x_disc)}")print(f"r_disc is a {type(r_disc)}")# Evaluatex=x_disc.evaluate()r=r_disc.evaluate()f,(ax1,ax2)=plt.subplots(1,2,figsize=(13,4))ax1.plot(x,"*")ax1.set_xlabel("index")ax1.set_ylabel(r"$x$")ax2.plot(r,"*")ax2.set_xlabel("index")ax2.set_ylabel(r"$r$")plt.tight_layout()plt.show()
x_disc is a <class 'pybamm.expression_tree.vector.Vector'>
r_disc is a <class 'pybamm.expression_tree.vector.Vector'>
We define y_macroscale, y_microscale and y_scalar for evaluation and visualisation of results below
In this notebook, we will work with three variables u, v, w.
[7]:
u=pybamm.Variable("u",domain=macroscale)# u is a variable in the macroscale (e.g. electrolyte potential)v=pybamm.Variable("v",domain=["negative particle"])# v is a variable in the negative particle (e.g. particle concentration)w=pybamm.Variable("w")# w is a variable without a domain (e.g. time, average concentration)variables=[u,v,w]
Before discretising, trying to evaluate the variables raises a NotImplementedError:
method self.evaluate() not implemented for symbol u of type <class 'pybamm.expression_tree.variable.Variable'>
For any spatial method, a pybamm.Variable gets converted to a pybamm.StateVector which, when evaluated, takes the appropriate slice of the input vector y.
[9]:
# Pass the list of variables to the discretisation to calculate the slices to be used (order matters here!)disc.set_variable_slices(variables)# Discretise the variablesu_disc=disc.process_symbol(u)v_disc=disc.process_symbol(v)w_disc=disc.process_symbol(w)# Print the outcomeprint(f"Discretised u is the StateVector {u_disc}")print(f"Discretised v is the StateVector {v_disc}")print(f"Discretised w is the StateVector {w_disc}")
Discretised u is the StateVector y[0:40]
Discretised v is the StateVector y[40:50]
Discretised w is the StateVector y[50:51]
Since the variables have been passed to disc in the order [u,v,w], they each read the appropriate part of y when evaluated:
The gradient operator is converted to a Matrix-StateVector multiplication. In 1D, the gradient operator is equivalent to \(\partial/\partial x\) on the macroscale and \(\partial/\partial r\) on the microscale. In Finite Volumes, we take the gradient of an object on nodes (shape (n,)), which returns an object on the edges (shape (n-1,)).
When evaluated with y_macroscale=x**3/3, grad_u_disc is equal to x**2 as expected:
[14]:
x_edge=macro_mesh.edges[1:-1]# note that grad_u_disc is evaluated on the node edgesfig,ax=plt.subplots()ax.plot(x_fine,x_fine**2,x_edge,grad_u_disc.evaluate(y=y),"o")ax.set_xlabel("x")legend=ax.legend(["x^2","grad(u).evaluate(y=x**3/3)"],loc="best")plt.show()
Similary, we can create, discretise and evaluate the gradient of v, which is a variable in the negative particles. Note that the syntax for doing this is identical: we do not need to explicitly specify that we want the gradient in r, since this is inferred from the domain of v.
[15]:
v.domain
[15]:
['negative particle']
[16]:
grad_v=pybamm.grad(v)grad_v_disc=disc.process_symbol(grad_v)print("grad(v) tree is:\n")grad_v_disc.render()micro_mesh=mesh["negative particle"]print("\n gradient matrix is:\n")print(f"1/dr *\n{micro_mesh.d_nodes[:,np.newaxis]*grad_v_disc.children[0].entries.toarray()}")r_edge=micro_mesh.edges[1:-1]# note that grad_u_disc is evaluated on the node edgesfig,ax=plt.subplots()ax.plot(r_fine,-np.sin(r_fine),r_edge,grad_v_disc.evaluate(y=y),"o")ax.set_xlabel("x")legend=ax.legend(["-sin(r)","grad(v).evaluate(y=cos(r))"],loc="best")plt.show()
If the discretisation is provided with boundary conditions, appropriate ghost nodes are concatenated onto the variable, and a larger gradient matrix is used. The ghost nodes are chosen based on the value of the first/last node in the variable and the boundary condition. For a Dirichlet boundary condition \(u=a\) on the left-hand boundary, we set the value of the left ghost node to be equal to
\[2*a-u[0],\]
where \(u[0]\) is the value of \(u\) in the left-most cell in the domain. Similarly, for a Dirichlet condition \(u=b\) on the right-hand boundary, we set the right ghost node to be
\[2*b-u[-1].\]
[17]:
disc.bcs={u:{"left":(pybamm.Scalar(1),"Dirichlet"),"right":(pybamm.Scalar(2),"Dirichlet"),}}grad_u_disc=disc.process_symbol(grad_u)print("The gradient object is:")(grad_u_disc.render())u_eval=grad_u_disc.evaluate(y=y)dx=np.diff(macro_mesh.nodes)[-1]print(f"The value of u on the left-hand boundary is {y[0]-dx*u_eval[0]/2}")print(f"The value of u on the right-hand boundary is {y[1]+dx*u_eval[-1]/2}")
The gradient object is:
+
├── Column vector of length 41
└── @
├── Sparse Matrix (41, 40)
└── y[0:40]
The value of u on the left-hand boundary is [1.]
The value of u on the right-hand boundary is [1.67902865]
For a Neumann boundary condition \(\partial u/\partial x=c\) on the left-hand boundary, we set the value of the left ghost node to be
\[u[0] - c * dx,\]
where \(dx\) is the step size at the left-hand boundary. For a Neumann boundary condition \(\partial u/\partial x=d\) on the right-hand boundary, we set the value of the right ghost node to be
\[u[-1] + d * dx.\]
[20]:
disc.bcs={u:{"left":(pybamm.Scalar(3),"Neumann"),"right":(pybamm.Scalar(4),"Neumann")}}grad_u_disc=disc.process_symbol(grad_u)print("The gradient object is:")(grad_u_disc.render())grad_u_eval=grad_u_disc.evaluate(y=y)print(f"The gradient on the left-hand boundary is {grad_u_eval[0]}")print(f"The gradient of u on the right-hand boundary is {grad_u_eval[-1]}")
The gradient object is:
+
├── Column vector of length 41
└── @
├── Sparse Matrix (41, 40)
└── y[0:40]
The gradient on the left-hand boundary is [3.]
The gradient of u on the right-hand boundary is [4.]
We can mix the types of the boundary conditions:
[21]:
disc.bcs={u:{"left":(pybamm.Scalar(5),"Dirichlet"),"right":(pybamm.Scalar(6),"Neumann")}}grad_u_disc=disc.process_symbol(grad_u)print("The gradient object is:")(grad_u_disc.render())grad_u_eval=grad_u_disc.evaluate(y=y)u_eval=grad_u_disc.children[1].evaluate(y=y)print(f"The value of u on the left-hand boundary is {(u_eval[0]+u_eval[1])/2}")print(f"The gradient on the right-hand boundary is {grad_u_eval[-1]}")
The gradient object is:
+
├── Column vector of length 41
└── @
├── Sparse Matrix (41, 40)
└── y[0:40]
The value of u on the left-hand boundary is [0.00036458]
The gradient on the right-hand boundary is [6.]
Robin boundary conditions can be implemented by specifying a Neumann condition where the flux depends on the variable.
Now we can process div(grad(u)), converting it to a Matrix-Vector multiplication, plus a vector for the boundary conditions. Since we have Neumann boundary conditions, the divergence of an object of size (n+1,) has size (n,), and so div(grad) of an object of size (n,) has size (n,)
The div(grad) matrix is automatically simplified to the well-known [1,-2,1] matrix (divided by the square of the distance between the edges), except in the first and last rows for boundary conditions
Finally, we can define an integral operator, which integrates the variable across the domain specified by the integration variable.
[25]:
int_u=pybamm.Integral(u,x_var)int_u_disc=disc.process_symbol(int_u)print(f"int(u) = {int_u_disc.evaluate(y=y)} is approximately equal to 1/12, {1/12}")# We divide v by r to evaluate the integral more easilyint_v_over_r2=pybamm.Integral(v/r_var**2,r_var)int_v_over_r2_disc=disc.process_symbol(int_v_over_r2)print("int(v/r^2) = {} is approximately equal to 4 * pi * sin(1), {}".format(int_v_over_r2_disc.evaluate(y=y),4*np.pi*np.sin(1)))
int(u) = [[0.08330729]] is approximately equal to 1/12, 0.08333333333333333
int(v/r^2) = [[11.07985772]] is approximately equal to 4 * pi * sin(1), 10.574236256325824
The integral operators are also Matrix-Vector multiplications
We can now discretise a whole model. We create, and discretise, a simple model for the concentration in the electrolyte and the concentration in the particles, and discretise it with a single command:
disc.process_model(model)
[28]:
model=pybamm.BaseModel()c_e=pybamm.Variable("electrolyte concentration",domain=macroscale)N_e=pybamm.grad(c_e)c_s=pybamm.Variable("particle concentration",domain=["negative particle"])N_s=pybamm.grad(c_s)model.rhs={c_e:pybamm.div(N_e)-5,c_s:pybamm.div(N_s)}model.boundary_conditions={c_e:{"left":(np.cos(0),"Neumann"),"right":(np.cos(10),"Neumann")},c_s:{"left":(0,"Neumann"),"right":(-1,"Neumann")},}model.initial_conditions={c_e:1+0.1*pybamm.sin(10*x_var),c_s:1}# Create a new discretisation and process modeldisc2=pybamm.Discretisation(mesh,spatial_methods)disc2.process_model(model);
The initial conditions are discretised to vectors, and an array of concatenated initial conditions is created.
If a system is advection-dominated (Peclet number greater than around 40), then it is important to use upwinding (if velocity is positive) or downwinding (if velocity is negative) to obtain accurate results. To see this, consider the following model (without upwinding)
[31]:
model=pybamm.BaseModel()# Define concentration and velocityc=pybamm.Variable("c",domain=["negative electrode","separator","positive electrode"])v=pybamm.PrimaryBroadcastToEdges(1,["negative electrode","separator","positive electrode"])model.rhs={c:-pybamm.div(c*v)+1}model.initial_conditions={c:0}model.boundary_conditions={c:{"left":(0,"Dirichlet")}}model.variables={"c":c}defsolve_and_plot(model):model_disc=disc.process_model(model,inplace=False)t_eval=[0,100]solution=pybamm.CasadiSolver().solve(model_disc,t_eval)# plotplot=pybamm.QuickPlot(solution,["c"],spatial_unit="m")plot.dynamic_plot()solve_and_plot(model)
The concentration grows indefinitely, which is clearly an incorrect solution. Instead, we can use upwinding:
Since this notebook is only an introduction to the discretisation, we have not covered everything. More advanced concepts, such as the ones below, can be explored by looking into the API docs.
Gradient and divergence of microscale variables in the P2D model
Indefinite integral
If you would like detailed examples of these operations, please create an issue and we will be happy to help.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
Tip
An interactive online version of this notebook is available, which can be
accessed via
In this notebook, we will be going through the BatchStudy class and will be discussing how different models, experiments, chemistries, etc. can be compared with each other using the same.
We start by creating a simple script to compare SPM, SPMe and DFN model with the default parameters.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybamm# loading up 3 models to comparedfn=pybamm.lithium_ion.DFN()spm=pybamm.lithium_ion.SPM()spme=pybamm.lithium_ion.SPMe()
Note: you may need to restart the kernel to use updated packages.
The BatchStudy class requires a dictionary of models, and all the default values for a given model are used if no additional parameter is passed in.
[2]:
models={"dfn":dfn,"spm":spm,"spme":spme,}# creating a BatchStudy objectbatch_study=pybamm.BatchStudy(models=models)# solving and plotting the comparisonbatch_study.solve(t_eval=[0,3600])batch_study.plot()
[2]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7ffdb8988b80>
BatchStudy by default requires equal number of items in all the dictionaries passed, which can be changed by setting the value of permutations to True. When set True, a cartesian product of all the available items is taken.
For example, here we pass 3 models but only 1 parameter value, hence it is necessary to set permutations to True. Here, the given parameter value is used for all the provided models.
[3]:
# passing parameter_values as a dictionaryparameter_values={"Chen2020":pybamm.ParameterValues("Chen2020")}# creating a BatchStudy object and solving the simulationbatch_study=pybamm.BatchStudy(models=models,parameter_values=parameter_values,permutations=True)batch_study.solve(t_eval=[0,3600])batch_study.plot()
[3]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7ffdcba8cfa0>
BatchStudy can also be used to compare different things (like affect of changing a parameter’s value) on a single model.
In the following cell, we compare different values of "Curentfunction[A]" using the SingleParitcleModelwithelectrolyte.
[4]:
model={"spme":spme}# populating a dictionary with 3 same parameter valuesparameter_values={"Chen2020_1":pybamm.ParameterValues("Chen2020"),"Chen2020_2":pybamm.ParameterValues("Chen2020"),"Chen2020_3":pybamm.ParameterValues("Chen2020"),}# different values for "Current function [A]"current_values=[4.5,4.75,5]# changing the value of "Current function [A]" in all the parameter values present in the# parameter_values dictionaryfork,v,current_valueinzip(parameter_values.keys(),parameter_values.values(),current_values):v["Current function [A]"]=current_value# creating a BatchStudy object with permutations set to True to create a cartesian productbatch_study=pybamm.BatchStudy(models=model,parameter_values=parameter_values,permutations=True)batch_study.solve(t_eval=[0,3600])# generating the required labels and plottinglabels=[f"Current function [A]: {current}"forcurrentincurrent_values]batch_study.plot(labels=labels)
[4]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7ffdccc5bd30>
BatchStudy also includes a create_gif method which can be used to create a GIF of the simulation.
[5]:
# using less number of images in the example# for a smoother GIF use more imagesbatch_study.create_gif(number_of_images=5,duration=0.2,output_filename="batch.gif")
Experiments can also be specified for comparisons, and they are also passed as a dictionary (a dictionary of pybamm.Experiment) in the BatchStudy class.
In the next cell, we compare a single experiment, with a single model, but with a varied parameter value.
[6]:
pybamm.set_logging_level("NOTICE")# using the cccv experiment with 10 cyclescccv=pybamm.Experiment([("Discharge at C/10 for 10 hours or until 3.3 V","Rest for 1 hour","Charge at 1 A until 4.1 V","Hold at 4.1 V until 50 mA","Rest for 1 hour",)]*10,)# creating the experiment dictexperiment={"cccv":cccv}# populating a dictionary with 3 same parameter values (Mohtat2020 chemistry)parameter_values={"Mohtat2020_1":pybamm.ParameterValues("Mohtat2020"),"Mohtat2020_2":pybamm.ParameterValues("Mohtat2020"),"Mohtat2020_3":pybamm.ParameterValues("Mohtat2020"),}# different values for the parameter "Inner SEI open-circuit potential [V]"inner_sei_oc_v_values=[2.0e-4,2.7e-4,3.4e-4]# updating the value of "Inner SEI open-circuit potential [V]" in all the dictionary itemsfork,v,inner_sei_oc_vinzip(parameter_values.keys(),parameter_values.values(),inner_sei_oc_v_values):v.update({"Inner SEI open-circuit potential [V]":inner_sei_oc_v},)# creating a Single Particle Model with "electron-mitigation limited" SEImodel={"spm":pybamm.lithium_ion.SPM({"SEI":"electron-migration limited"})}# creating a BatchStudy object with the given experimen, model and parameter_valuesbatch_study=pybamm.BatchStudy(models=model,experiments=experiment,parameter_values=parameter_values,permutations=True,)# solving and plotting the resultbatch_study.solve(initial_soc=1)labels=[f"Inner SEI open-circuit potential [V]: {inner_sei_oc_v}"forinner_sei_oc_vininner_sei_oc_v_values]batch_study.plot(labels=labels)
2022-07-26 16:44:42.414 - [NOTICE] callbacks.on_cycle_start(174): Cycle 1/10 (35.637 ms elapsed) --------------------
2022-07-26 16:44:42.415 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:42.565 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:42.594 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:42.684 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:42.726 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:42.921 - [NOTICE] callbacks.on_cycle_start(174): Cycle 2/10 (542.652 ms elapsed) --------------------
2022-07-26 16:44:42.922 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:43.047 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:43.063 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:43.128 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:43.142 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:43.234 - [NOTICE] callbacks.on_cycle_start(174): Cycle 3/10 (855.739 ms elapsed) --------------------
2022-07-26 16:44:43.235 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:43.356 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:43.371 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:43.435 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:43.449 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:43.554 - [NOTICE] callbacks.on_cycle_start(174): Cycle 4/10 (1.175 s elapsed) --------------------
2022-07-26 16:44:43.554 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:43.676 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:43.691 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:43.756 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:43.769 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:44.037 - [NOTICE] callbacks.on_cycle_start(174): Cycle 5/10 (1.658 s elapsed) --------------------
2022-07-26 16:44:44.037 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:44.171 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:44.187 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:44.255 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:44.270 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:44.374 - [NOTICE] callbacks.on_cycle_start(174): Cycle 6/10 (1.995 s elapsed) --------------------
2022-07-26 16:44:44.374 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:44.503 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:44.518 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:44.588 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:44.601 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:44.710 - [NOTICE] callbacks.on_cycle_start(174): Cycle 7/10 (2.331 s elapsed) --------------------
2022-07-26 16:44:44.710 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:44.836 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:44.852 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:44.922 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:44.935 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:45.033 - [NOTICE] callbacks.on_cycle_start(174): Cycle 8/10 (2.655 s elapsed) --------------------
2022-07-26 16:44:45.034 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:45.156 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:45.171 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:45.238 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:45.252 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:45.369 - [NOTICE] callbacks.on_cycle_start(174): Cycle 9/10 (2.991 s elapsed) --------------------
2022-07-26 16:44:45.370 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:45.512 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:45.529 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:45.596 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:45.610 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:45.711 - [NOTICE] callbacks.on_cycle_start(174): Cycle 10/10 (3.333 s elapsed) --------------------
2022-07-26 16:44:45.712 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:45.849 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:45.864 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:45.932 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:45.947 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:46.055 - [NOTICE] callbacks.on_experiment_end(222): Finish experiment simulation, took 3.333 s
2022-07-26 16:44:46.893 - [NOTICE] callbacks.on_cycle_start(174): Cycle 1/10 (36.421 ms elapsed) --------------------
2022-07-26 16:44:46.894 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:47.052 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:47.080 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:47.172 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:47.219 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:47.398 - [NOTICE] callbacks.on_cycle_start(174): Cycle 2/10 (542.151 ms elapsed) --------------------
2022-07-26 16:44:47.399 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:47.522 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:47.690 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:47.760 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:47.774 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:47.872 - [NOTICE] callbacks.on_cycle_start(174): Cycle 3/10 (1.016 s elapsed) --------------------
2022-07-26 16:44:47.873 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:48.023 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:48.040 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:48.115 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:48.133 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:48.234 - [NOTICE] callbacks.on_cycle_start(174): Cycle 4/10 (1.378 s elapsed) --------------------
2022-07-26 16:44:48.234 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:48.365 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:48.379 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:48.446 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:48.460 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:48.557 - [NOTICE] callbacks.on_cycle_start(174): Cycle 5/10 (1.700 s elapsed) --------------------
2022-07-26 16:44:48.557 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:48.688 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:48.704 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:48.776 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:48.790 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:48.887 - [NOTICE] callbacks.on_cycle_start(174): Cycle 6/10 (2.031 s elapsed) --------------------
2022-07-26 16:44:48.887 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:49.019 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:49.034 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:49.102 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:49.116 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:49.209 - [NOTICE] callbacks.on_cycle_start(174): Cycle 7/10 (2.353 s elapsed) --------------------
2022-07-26 16:44:49.210 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:49.335 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:49.350 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:49.416 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:49.430 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:49.533 - [NOTICE] callbacks.on_cycle_start(174): Cycle 8/10 (2.677 s elapsed) --------------------
2022-07-26 16:44:49.534 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:49.658 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:49.673 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:49.740 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:49.755 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:49.851 - [NOTICE] callbacks.on_cycle_start(174): Cycle 9/10 (2.995 s elapsed) --------------------
2022-07-26 16:44:49.852 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:49.977 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:49.992 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:50.065 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:50.078 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:50.176 - [NOTICE] callbacks.on_cycle_start(174): Cycle 10/10 (3.320 s elapsed) --------------------
2022-07-26 16:44:50.176 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:50.307 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:50.324 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:50.420 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:50.445 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:50.542 - [NOTICE] callbacks.on_experiment_end(222): Finish experiment simulation, took 3.320 s
2022-07-26 16:44:51.360 - [NOTICE] callbacks.on_cycle_start(174): Cycle 1/10 (36.824 ms elapsed) --------------------
2022-07-26 16:44:51.360 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:51.515 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:51.545 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:51.638 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:51.684 - [NOTICE] callbacks.on_step_start(182): Cycle 1/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:52.031 - [NOTICE] callbacks.on_cycle_start(174): Cycle 2/10 (707.971 ms elapsed) --------------------
2022-07-26 16:44:52.031 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:52.156 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:52.171 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:52.238 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:52.251 - [NOTICE] callbacks.on_step_start(182): Cycle 2/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:52.355 - [NOTICE] callbacks.on_cycle_start(174): Cycle 3/10 (1.033 s elapsed) --------------------
2022-07-26 16:44:52.356 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:52.480 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:52.496 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:52.563 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:52.577 - [NOTICE] callbacks.on_step_start(182): Cycle 3/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:52.676 - [NOTICE] callbacks.on_cycle_start(174): Cycle 4/10 (1.353 s elapsed) --------------------
2022-07-26 16:44:52.676 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:52.800 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:52.815 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:52.881 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:52.895 - [NOTICE] callbacks.on_step_start(182): Cycle 4/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:52.989 - [NOTICE] callbacks.on_cycle_start(174): Cycle 5/10 (1.666 s elapsed) --------------------
2022-07-26 16:44:52.990 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:53.114 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:53.129 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:53.196 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:53.210 - [NOTICE] callbacks.on_step_start(182): Cycle 5/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:53.308 - [NOTICE] callbacks.on_cycle_start(174): Cycle 6/10 (1.985 s elapsed) --------------------
2022-07-26 16:44:53.308 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:53.432 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:53.447 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:53.517 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:53.531 - [NOTICE] callbacks.on_step_start(182): Cycle 6/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:53.627 - [NOTICE] callbacks.on_cycle_start(174): Cycle 7/10 (2.305 s elapsed) --------------------
2022-07-26 16:44:53.628 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:53.753 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:53.768 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:53.836 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:53.849 - [NOTICE] callbacks.on_step_start(182): Cycle 7/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:53.945 - [NOTICE] callbacks.on_cycle_start(174): Cycle 8/10 (2.622 s elapsed) --------------------
2022-07-26 16:44:53.945 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:54.072 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:54.086 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:54.160 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:54.173 - [NOTICE] callbacks.on_step_start(182): Cycle 8/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:54.269 - [NOTICE] callbacks.on_cycle_start(174): Cycle 9/10 (2.946 s elapsed) --------------------
2022-07-26 16:44:54.269 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:54.392 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:54.413 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:54.484 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:54.498 - [NOTICE] callbacks.on_step_start(182): Cycle 9/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:54.603 - [NOTICE] callbacks.on_cycle_start(174): Cycle 10/10 (3.281 s elapsed) --------------------
2022-07-26 16:44:54.604 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 1/5: Discharge at C/10 for 10 hours or until 3.3 V
2022-07-26 16:44:54.731 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 2/5: Rest for 1 hour
2022-07-26 16:44:54.747 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 3/5: Charge at 1 A until 4.1 V
2022-07-26 16:44:54.816 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 4/5: Hold at 4.1 V until 50 mA
2022-07-26 16:44:54.829 - [NOTICE] callbacks.on_step_start(182): Cycle 10/10, step 5/5: Rest for 1 hour
2022-07-26 16:44:54.926 - [NOTICE] callbacks.on_experiment_end(222): Finish experiment simulation, took 3.281 s
[6]:
<pybamm.plotting.quick_plot.QuickPlot at 0x7ffdea372580>
The difference in the individual plots is not very well visible in the above slider plot, but we can access all the simulations created by BatchStudy (batch_study.sims) and pass it to pybamm.plot_summary_variables to plot the summary variables (more details on “summary variables” are available in the `simulating-long-experiments <./simulations_and_experiments/simulating-long-experiments.ipynb>`__ notebook).
array([[<AxesSubplot:xlabel='Cycle number', ylabel='Capacity [A.h]'>,
<AxesSubplot:xlabel='Cycle number', ylabel='Loss of lithium inventory [%]'>,
<AxesSubplot:xlabel='Cycle number', ylabel='Loss of capacity to SEI [A.h]'>],
[<AxesSubplot:xlabel='Cycle number', ylabel='Loss of active material in negative electrode [%]'>,
<AxesSubplot:xlabel='Cycle number', ylabel='Loss of active material in positive electrode [%]'>,
<AxesSubplot:xlabel='Cycle number', ylabel='x_100'>],
[<AxesSubplot:xlabel='Cycle number', ylabel='x_0'>,
<AxesSubplot:xlabel='Cycle number', ylabel='y_100'>,
<AxesSubplot:xlabel='Cycle number', ylabel='y_0'>]], dtype=object)
Other than the above examples, the BatchStudy class can be used to compare a lot of different configurations, like models with different SEIs or a model with reversible and irreversible lithium plating, etc.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Chang-Hui Chen, Ferran Brosa Planella, Kieran O'Regan, Dominika Gastol, W. Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.
[3] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[4] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[5] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[6] Peyman Mohtat, Suhak Lee, Jason B Siegel, and Anna G Stefanopoulou. Towards better estimability of electrode-specific state of health: decoding the cell expansion. Journal of Power Sources, 427:101–111, 2019.
[7] Peyman Mohtat, Suhak Lee, Valentin Sulzer, Jason B. Siegel, and Anna G. Stefanopoulou. Differential Expansion and Voltage Model for Li-ion Batteries at Practical Charging Rates. Journal of The Electrochemical Society, 167(11):110561, 2020. doi:10.1149/1945-7111/aba5d1.
[8] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[9] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
Tip
An interactive online version of this notebook is available, which can be
accessed via
This example notebook showed how to run an SPM battery model, using the default parameters, discretisation and solvers that were defined for that particular model. Naturally we would like the ability to alter these options on a case by case basis, and this notebook gives an example of how to do this, again using the SPM model. In this notebook we explicitly handle all the stages of setting up, processing and solving the model in order to explain them in detail. However,
it is often simpler in practice to use the Simulation class, which handles many of the stages automatically, as shown here.
Below is the code to define and run the default SPM model included in PyBaMM (if this is unfamiliar to you, please see this notebook for more details)
[17]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpimportosimportmatplotlib.pyplotaspltos.chdir(pybamm.__path__[0]+"/..")# create the modelmodel=pybamm.lithium_ion.SPM()# set the default model geometrygeometry=model.default_geometry# set the default model parametersparam=model.default_parameter_values# set the parameters for the model and the geometryparam.process_model(model)param.process_geometry(geometry)# mesh the domainsmesh=pybamm.Mesh(geometry,model.default_submesh_types,model.default_var_pts)# discretise the model equationsdisc=pybamm.Discretisation(mesh,model.default_spatial_methods)disc.process_model(model)# Solve the model at the given time pointssolver=model.default_solvern=100t_eval=np.linspace(0,3600,n)solution=solver.solve(model,t_eval)
WARNING: You are using pip version 22.0.4; however, version 22.3.1 is available.
You should consider upgrading via the '/home/mrobins/git/PyBaMM/env/bin/python -m pip install --upgrade pip' command.Note: you may need to restart the kernel to use updated packages.
To verify the solution, we can look at a plot of the Voltage over time
PARAMETER VALUE
-------------------------------------------------------------------------------------------------
Ratio of lithium moles to SEI moles 2.0
Inner SEI reaction proportion 0.5
Inner SEI partial molar volume [m3.mol-1] 9.585e-05
Outer SEI partial molar volume [m3.mol-1] 9.585e-05
SEI reaction exchange current density [A.m-2] 1.5e-07
SEI resistivity [Ohm.m] 200000.0
Outer SEI solvent diffusivity [m2.s-1] 2.5000000000000002e-22
Bulk solvent concentration [mol.m-3] 2636.0
Inner SEI open-circuit potential [V] 0.1
Outer SEI open-circuit potential [V] 0.8
Inner SEI electron conductivity [S.m-1] 8.95e-14
Inner SEI lithium interstitial diffusivity [m2.s-1] 1e-20
Lithium interstitial reference concentration [mol.m-3] 15.0
Initial inner SEI thickness [m] 2.5e-09
Initial outer SEI thickness [m] 2.5e-09
EC initial concentration in electrolyte [mol.m-3] 4541.0
EC diffusivity [m2.s-1] 2e-18
SEI kinetic rate constant [m.s-1] 1e-12
SEI open-circuit potential [V] 0.4
SEI growth activation energy [J.mol-1] 0.0
Negative electrode reaction-driven LAM factor [m3.mol-1] 0.0
Positive electrode reaction-driven LAM factor [m3.mol-1] 0.0
Negative current collector thickness [m] 2.5e-05
Negative electrode thickness [m] 0.0001
Separator thickness [m] 2.5e-05
Positive electrode thickness [m] 0.0001
Positive current collector thickness [m] 2.5e-05
Electrode height [m] 0.137
Electrode width [m] 0.207
Negative tab width [m] 0.04
Negative tab centre y-coordinate [m] 0.06
Negative tab centre z-coordinate [m] 0.137
Positive tab width [m] 0.04
Positive tab centre y-coordinate [m] 0.147
Positive tab centre z-coordinate [m] 0.137
Cell cooling surface area [m2] 0.0569
Cell volume [m3] 7.8e-06
Negative current collector conductivity [S.m-1] 59600000.0
Positive current collector conductivity [S.m-1] 35500000.0
Negative current collector density [kg.m-3] 8954.0
Positive current collector density [kg.m-3] 2707.0
Negative current collector specific heat capacity [J.kg-1.K-1] 385.0
Positive current collector specific heat capacity [J.kg-1.K-1] 897.0
Negative current collector thermal conductivity [W.m-1.K-1] 401.0
Positive current collector thermal conductivity [W.m-1.K-1] 237.0
Nominal cell capacity [A.h] 0.680616
Typical current [A] 0.680616
Current function [A] 0.680616
Negative electrode conductivity [S.m-1] 100.0
Maximum concentration in negative electrode [mol.m-3] 24983.2619938437
Negative electrode diffusivity [m2.s-1] <function graphite_mcmb2528_diffusivity_Dualfoil1998 at 0x7fb36b2ad040>
Negative electrode OCP [V] <function graphite_mcmb2528_ocp_Dualfoil1998 at 0x7fb36b261670>
Negative electrode porosity 0.3
Negative electrode active material volume fraction 0.6
Negative particle radius [m] 1e-05
Negative electrode Bruggeman coefficient (electrolyte) 1.5
Negative electrode Bruggeman coefficient (electrode) 1.5
Negative electrode cation signed stoichiometry -1.0
Negative electrode electrons in reaction 1.0
Negative electrode charge transfer coefficient 0.5
Negative electrode double-layer capacity [F.m-2] 0.2
Negative electrode exchange-current density [A.m-2] <function graphite_electrolyte_exchange_current_density_Dualfoil1998 at 0x7fb36b2615e0>
Negative electrode density [kg.m-3] 1657.0
Negative electrode specific heat capacity [J.kg-1.K-1] 700.0
Negative electrode thermal conductivity [W.m-1.K-1] 1.7
Negative electrode OCP entropic change [V.K-1] <function graphite_entropic_change_Moura2016 at 0x7fb36b261550>
Positive electrode conductivity [S.m-1] 10.0
Maximum concentration in positive electrode [mol.m-3] 51217.9257309275
Positive electrode diffusivity [m2.s-1] <function lico2_diffusivity_Dualfoil1998 at 0x7fb36b2614c0>
Positive electrode OCP [V] <function lico2_ocp_Dualfoil1998 at 0x7fb36b2613a0>
Positive electrode porosity 0.3
Positive electrode active material volume fraction 0.5
Positive particle radius [m] 1e-05
Positive electrode Bruggeman coefficient (electrolyte) 1.5
Positive electrode Bruggeman coefficient (electrode) 1.5
Positive electrode cation signed stoichiometry -1.0
Positive electrode electrons in reaction 1.0
Positive electrode charge transfer coefficient 0.5
Positive electrode double-layer capacity [F.m-2] 0.2
Positive electrode exchange-current density [A.m-2] <function lico2_electrolyte_exchange_current_density_Dualfoil1998 at 0x7fb36b261310>
Positive electrode density [kg.m-3] 3262.0
Positive electrode specific heat capacity [J.kg-1.K-1] 700.0
Positive electrode thermal conductivity [W.m-1.K-1] 2.1
Positive electrode OCP entropic change [V.K-1] <function lico2_entropic_change_Moura2016 at 0x7fb36b261280>
Separator porosity 1.0
Separator Bruggeman coefficient (electrolyte) 1.5
Separator density [kg.m-3] 397.0
Separator specific heat capacity [J.kg-1.K-1] 700.0
Separator thermal conductivity [W.m-1.K-1] 0.16
Typical electrolyte concentration [mol.m-3] 1000.0
Initial concentration in electrolyte [mol.m-3] 1000.0
Cation transference number 0.4
Thermodynamic factor 1.0
Electrolyte diffusivity [m2.s-1] <function electrolyte_diffusivity_Capiglia1999 at 0x7fb36b2611f0>
Electrolyte conductivity [S.m-1] <function electrolyte_conductivity_Capiglia1999 at 0x7fb36b261160>
Reference temperature [K] 298.15
Ambient temperature [K] 298.15
Negative current collector surface heat transfer coefficient [W.m-2.K-1] 0.0
Positive current collector surface heat transfer coefficient [W.m-2.K-1] 0.0
Negative tab heat transfer coefficient [W.m-2.K-1] 10.0
Positive tab heat transfer coefficient [W.m-2.K-1] 10.0
Edge heat transfer coefficient [W.m-2.K-1] 0.3
Total heat transfer coefficient [W.m-2.K-1] 10.0
Number of electrodes connected in parallel to make a cell 1.0
Number of cells connected in series to make a battery 1.0
Lower voltage cut-off [V] 3.105
Upper voltage cut-off [V] 4.1
Initial concentration in negative electrode [mol.m-3] 19986.609595075
Initial concentration in positive electrode [mol.m-3] 30730.7554385565
Initial temperature [K] 298.15
citations ['Marquis2019']
Most of the parameters in this list have numerical values. Some have string values, that point to particular python functions within PyBaMM. These denote parameters that vary over time and/or space, in a manner defined by the given python function. For the moment we will ignore these, and focus on altering one of the numerical parameters.
variable="Current function [A]"old_value=param[variable]param[variable]=1.4new_value=param[variable]print(variable,"was",old_value)print(variable,"now is",param[variable])
Current function [A] was 0.680616
Current function [A] now is 1.4
In order to compare solutions with different parameter values, parameters must be changed to InputParameter objects, whose value can then be specified when solving. For example, we can compare the Voltage calculated using both the old and new current values.
[22]:
# Set upmodel=pybamm.lithium_ion.SPM()solver=model.default_solverparam["Current function [A]"]="[input]"param.process_model(model)disc.process_model(model)# Solution with current = 0.68old_solution=solver.solve(model,t_eval,inputs={"Current function [A]":0.68})old_time=old_solution["Time [h]"].entriesold_voltage=old_solution["Voltage [V]"].entries# Solution with current = 1.4new_solution=solver.solve(model,t_eval,inputs={"Current function [A]":1.4})new_time=new_solution["Time [h]"].entriesnew_voltage=new_solution["Voltage [V]"].entriesplt.plot(old_time,old_voltage,lw=2,label="Current = 0.68")plt.plot(new_time,new_voltage,lw=2,label="Current = 1.4")plt.xlabel("Time [h]",fontsize=15)plt.ylabel("Voltage [V]",fontsize=15)plt.legend(fontsize=15)plt.show()
Note that there is a small number of parameters for which this is not possible, since the discretisation depends on them (for example, Negative electrode thickness). In this case you would need to remesh the geometry as the relative length of the electrodes would have altered. This would in turn require you to re-discretise the model equations.
To change the discretisation for a particular domain, you can update the spatial methods dict with the new discretiation class that you wish to use, for example:
You can also update the submeshes (meshes used for each domain) in a similar way. We’ll generate a spectral mesh to use with our spectral volume method in the negative particle
Now that we have set the new discretisation method, we can proceed to re-discretise and then solve the model. Note that in this case we need to regenerate the model using pybamm.lithium_ion.SPM, as the original model information was lost when we discretised it above.
[26]:
# re-generate the model and set parameters (with fixed current function this time)model=pybamm.lithium_ion.SPM()solver=model.default_solverparam["Current function [A]"]=0.68param.process_model(model)# re-discretise the model with the new mesh...mesh=pybamm.Mesh(geometry,submesh_types,model.default_var_pts)disc=pybamm.Discretisation(mesh,spatial_methods)disc.process_model(model)# ... and finally solve itsolution=solver.solve(model,t_eval)pybamm.QuickPlot(solution).plot(0)
Which method you use to integrate the discretised model in time can also be changed. PyBaMM has a number of different solvers available, all of which are described in the documentation.
[27]:
print("Default solver for SPM model:",type(model.default_solver).__name__)
Default solver for SPM model: CasadiSolver
To change this, simply create a new solver using one of the available classes and use it to solve your discretised model
[1] Weilong Ai, Ludwig Kraft, Johannes Sturm, Andreas Jossen, and Billy Wu. Electrochemical thermal-mechanical modelling of stress inhomogeneity in lithium-ion pouch cells. Journal of The Electrochemical Society, 167(1):013512, 2019. doi:10.1149/2.0122001JES.
[2] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[3] Rutooj Deshpande, Mark Verbrugge, Yang-Tse Cheng, John Wang, and Ping Liu. Battery cycle life prediction with coupled chemical degradation and fatigue mechanics. Journal of the Electrochemical Society, 159(10):A1730, 2012. doi:10.1149/2.049210jes.
[4] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[5] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[6] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.
[7] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.
[8] Z. J. Wang. Spectral (finite) volume method for conservation laws on unstructured grids. Journal of Computational Physics, 178(1):210–251, 2002. doi:10.1006/jcph.2002.7041.
Tip
An interactive online version of this notebook is available, which can be
accessed via
Example showing how to initialize a model with another model
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportpandasaspdimportosos.chdir(pybamm.__path__[0]+"/..")
WARNING: You are using pip version 21.0.1; however, version 21.1 is available.
You should consider upgrading via the '/Users/vsulzer/Documents/Energy_storage/PyBaMM/.tox/dev/bin/python -m pip install --upgrade pip' command.
Note: you may need to restart the kernel to use updated packages.
# import drive cycle from filedrive_cycle=pd.read_csv("pybamm/input/drive_cycles/US06.csv",comment="#",header=None).to_numpy()# create interpolantparam=model.default_parameter_valuescurrent_interpolant=pybamm.Interpolant(drive_cycle[:,0],drive_cycle[:,1],pybamm.t)# set drive cycleparam["Current function [A]"]=current_interpolant
Create and run simulation using the CasadiSolver in “fast” mode, remembering to pass in the updated parameters
Update initial conditions based on a solution and solve again#
Now pre-charge with CCCV, update the initial conditions, and solve again with the US06 drive cycle
[5]:
experiment=pybamm.Experiment(["Charge at 1 A until 4.1 V","Hold at 4.1 V until 50 mA"])sim_cccv=pybamm.Simulation(model,experiment=experiment)sol_cccv=sim_cccv.solve()# MODEL RE-INITIALIZATION: ############################################################## Now initialize the model with the solution of the charge, and then discharge with# the US06 drive cycle# We could also do this inplace by setting inplace to True, which modifies the original# model in placenew_model=model.set_initial_conditions_from(sol_cccv,inplace=False)########################################################################################sim_US06_2=pybamm.Simulation(new_model,parameter_values=param,solver=pybamm.CasadiSolver(mode="fast"))sol_US06_2=sim_US06_2.solve()
Plot both solutions, we can clearly see the difference now that initial conditions have been updated
We can also initialize the model using the solution of a different model
[7]:
spm=pybamm.lithium_ion.SPM()sim_spm_cccv=pybamm.Simulation(spm,experiment=experiment)sol_spm_cccv=sim_spm_cccv.solve()# MODEL RE-INITIALIZATION: ############################################################## Now initialize the model with the solution of the charge, and then discharge with# the US06 drive cycle# We could also do this inplace by setting inplace to True, which modifies the original# model in placenew_dfn=model.set_initial_conditions_from(sol_spm_cccv,inplace=False)########################################################################################sim_US06_3=pybamm.Simulation(new_dfn,parameter_values=param,solver=pybamm.CasadiSolver(mode="fast"))sol_US06_3=sim_US06_3.solve()
Now the model initialized by the DFN and the model initialized by the SPM give the same solution
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.
[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[5] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). ECSarXiv. February, 2020. doi:10.1149/osf.io/67ckj.
[ ]:
Tip
An interactive online version of this notebook is available, which can be
accessed via
Once you have run a simulation the first thing you want to do is have a look at the data. Most of the examples so far have made use of PyBaMM’s handy QuickPlot function but there are other ways to access the data and this notebook will explore them. First off we will generate a standard SPMe model and use QuickPlot to view the default variables.
[1]:
%pip install "pybamm[plot,cite]" -q # install PyBaMM if it is not installed
importpybammimportnumpyasnpimportosimportmatplotlib.pyplotaspltos.chdir(pybamm.__path__[0]+"/..")# load modelmodel=pybamm.lithium_ion.SPMe()# set up and solve simulationsimulation=pybamm.Simulation(model)dt=90t_eval=np.arange(0,3600,dt)# time in secondssolution=simulation.solve(t_eval)quick_plot=pybamm.QuickPlot(solution)quick_plot.dynamic_plot();
Note: you may need to restart the kernel to use updated packages.
Behind the scenes the QuickPlot classed has created some processed variables which can interpolate the model variables for our solution and has also stored the results for the solution steps
Notice that the dictionary keys are in the same order as the subplots in the QuickPlot figure. We can add new processed variables to the solution by simply using it like a dictionary. First let’s find a few more variables to look at. As you will see there are quite a few:
# to a pickle file (default)solution.save_data("outputs.pickle",["Time [h]","Current [A]","Voltage [V]","Electrolyte concentration [mol.m-3]"],)# to a matlab file# need to give variable names without spacesolution.save_data("outputs.mat",["Time [h]","Current [A]","Voltage [V]","Electrolyte concentration [mol.m-3]"],to_format="matlab",short_names={"Time [h]":"t","Current [A]":"I","Voltage [V]":"V","Electrolyte concentration [mol.m-3]":"c_e",},)# to a csv file (time-dependent outputs only, no spatial dependence allowed)solution.save_data("outputs.csv",["Time [h]","Current [A]","Voltage [V]"],to_format="csv")
The previous solution was created in one go with the solve method, but it is also possible to step the solution and look at the results as we go. In doing so, the results are automatically updated at each step.
[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.
[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.
[3] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.
[4] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). ECSarXiv. February, 2020. doi:10.1149/osf.io/67ckj.
PyBaMM (Python Battery Mathematical Modelling) is an open-source battery simulation package
written in Python. Our mission is to accelerate battery modelling research by
providing open-source tools for multi-institutional, interdisciplinary collaboration.
Broadly, PyBaMM consists of
a framework for writing and solving systems of differential equations,
a library of battery models and parameters, and
specialized tools for simulating battery-specific experiments and visualizing the results.
Together, these enable flexible model definitions and fast battery simulations, allowing users to
explore the effect of different battery designs and modeling assumptions under a variety of operating scenarios.
User Guide
The user guide is the best place to start learning PyBaMM. It contains an installation
guide, an introduction to the main concepts and links to additional tutorials.
Examples
Examples and tutorials can be viewed on the GitHub examples page,
which also provides a link to run them online through Google Colab.
API Documentation
The reference guide contains a detailed description of the functions,
modules, and objects included in PyBaMM. The reference describes how the
methods work and which parameters can be used.
Contributor’s Guide
Contributions to PyBaMM and its development are welcome! If you have ideas for
features, bug fixes, models, spatial methods, or solvers, we would love to hear from you.