{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "expmkveO04pw" }, "source": [ "## Pouch Cell Model Parameter Identification\n", "\n", "In this notebook, we present the single particle model with a two dimensional current collector. This is achieved via the potential-pair models introduced in Marquis et al. [[1]](https://doi.org/10.1149/1945-7111/abbce4) as implemented in PyBaMM. At a high-level this is accomplished as a potential-pair model which is resolved across the discretised spatial locations.\n", "\n", "### Setting up the Environment\n", "\n", "Before we begin, we need to ensure that we have all the necessary tools. We will install PyBOP and upgrade dependencies:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "X87NUGPW04py", "outputId": "0d785b07-7cff-4aeb-e60a-4ff5a669afbf" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "/Users/engs2510/Documents/Git/PyBOP/.nox/notebooks-overwrite/bin/python3: No module named pip\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Note: you may need to restart the kernel to use updated packages." ] }, { "name": "stdout", "output_type": "stream", "text": [ "\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "/Users/engs2510/Documents/Git/PyBOP/.nox/notebooks-overwrite/bin/python3: No module named pip\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ "%pip install --upgrade pip ipywidgets -q\n", "%pip install pybop -q" ] }, { "cell_type": "markdown", "metadata": { "id": "jAvD5fk104p0" }, "source": [ "### Importing Libraries\n", "\n", "With the environment set up, we can now import PyBOP alongside other libraries we will need:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "SQdt4brD04p1" }, "outputs": [], "source": [ "import numpy as np\n", "\n", "import pybop\n", "\n", "go = pybop.PlotlyManager().go\n", "pybop.PlotlyManager().pio.renderers.default = \"notebook_connected\"" ] }, { "cell_type": "markdown", "metadata": { "id": "5XU-dMtU04p2" }, "source": [ "## Generating Synthetic Data\n", "\n", "To demonstrate parameter estimation, we first need some data. We will generate synthetic data using a forward model, which requires defining a parameter set and the model itself.\n", "\n", "### Defining Parameters and Model\n", "\n", "We start by creating an example parameter set and then instantiate the single-particle model (SPM):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "parameter_set = pybop.ParameterSet.pybamm(\"Marquis2019\")\n", "parameter_set.update(\n", " {\n", " \"Negative electrode active material volume fraction\": 0.495,\n", " \"Positive electrode active material volume fraction\": 0.612,\n", " }\n", ")\n", "model = pybop.lithium_ion.SPM(\n", " parameter_set=parameter_set,\n", " options={\"current collector\": \"potential pair\", \"dimensionality\": 2},\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Updating the Spatial Grid\n", "\n", "Next, we update the number of spatial locations to solve the potential-pair model," ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model.var_pts[\"y\"] = 5\n", "model.var_pts[\"z\"] = 5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Simulating the Forward Model\n", "\n", "We can then simulate the model using the `predict` method, with a default constant current to generate voltage data." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "sBasxv8U04p3" }, "outputs": [], "source": [ "t_eval = np.arange(0, 900, 3)\n", "values = model.predict(t_eval=t_eval)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Adding Noise to Voltage Data\n", "\n", "To make the parameter estimation more realistic, we add Gaussian noise to the data." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sigma = 0.001\n", "corrupt_values = values[\"Voltage [V]\"].data + np.random.normal(0, sigma, len(t_eval))" ] }, { "cell_type": "markdown", "metadata": { "id": "X8-tubYY04p_" }, "source": [ "## Identifying the Parameters" ] }, { "cell_type": "markdown", "metadata": { "id": "PQqhvSZN04p_" }, "source": [ "We will now set up the parameter estimation process by defining the datasets for optimisation and selecting the model parameters we wish to estimate." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Creating a Dataset\n", "\n", "The dataset for optimisation is composed of time, current, and the noisy voltage data:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "zuvGHWID04p_" }, "outputs": [], "source": [ "dataset = pybop.Dataset(\n", " {\n", " \"Time [s]\": t_eval,\n", " \"Current function [A]\": values[\"Current [A]\"].data,\n", " \"Voltage [V]\": corrupt_values,\n", " }\n", ")" ] }, { "cell_type": "markdown", "metadata": { "id": "ffS3CF_704qA" }, "source": [ "### Defining Parameters to Estimate\n", "\n", "We select the parameters for estimation and set up their prior distributions and bounds:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "WPCybXIJ04qA" }, "outputs": [], "source": [ "parameters = pybop.Parameters(\n", " pybop.Parameter(\n", " \"Negative electrode active material volume fraction\",\n", " prior=pybop.Gaussian(0.7, 0.05),\n", " bounds=[0.45, 0.9],\n", " ),\n", " pybop.Parameter(\n", " \"Positive electrode active material volume fraction\",\n", " prior=pybop.Gaussian(0.58, 0.05),\n", " bounds=[0.5, 0.8],\n", " ),\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For plotting purposes, we want additional variables to be stored in the problem class. These are defined as," ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "additional_variables = [\n", " \"Negative current collector potential [V]\",\n", " \"Positive current collector potential [V]\",\n", "]" ] }, { "cell_type": "markdown", "metadata": { "id": "n4OHa-aF04qA" }, "source": [ "### Setting up the Optimisation Problem\n", "\n", "With the datasets and parameters defined, we can set up the optimisation problem, its cost function, and the optimiser." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "etMzRtx404qA" }, "outputs": [], "source": [ "problem = pybop.FittingProblem(\n", " model, parameters, dataset, additional_variables=additional_variables\n", ")\n", "cost = pybop.SumSquaredError(problem)\n", "optim = pybop.CMAES(cost, max_iterations=30)" ] }, { "cell_type": "markdown", "metadata": { "id": "caprp-bV04qB" }, "source": [ "### Running the Optimisation\n", "\n", "We proceed to run the CMA-ES optimisation algorithm to estimate the parameters:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "-9OVt0EQ04qB" }, "outputs": [], "source": [ "x, final_cost = optim.run()" ] }, { "cell_type": "markdown", "metadata": { "id": "-4pZsDmS04qC" }, "source": [ "### Viewing the Estimated Parameters\n", "\n", "After the optimisation, we can examine the estimated parameter values:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Hgz8SV4i04qC", "outputId": "e1e42ae7-5075-4c47-dd68-1b22ecc170f6" }, "outputs": [ { "data": { "text/plain": [ "array([0.49949096, 0.60907043])" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x # This will output the estimated parameters" ] }, { "cell_type": "markdown", "metadata": { "id": "KxKURtH704qC" }, "source": [ "## Plotting and Visualisation\n", "\n", "PyBOP provides various plotting utilities to visualise the results of the optimisation." ] }, { "cell_type": "markdown", "metadata": { "id": "-cWCOiqR04qC" }, "source": [ "### Comparing System Response\n", "\n", "We can quickly plot the system's response using the estimated parameters compared to the target:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 467 }, "id": "tJUJ80Ve04qD", "outputId": "855fbaa2-1e09-4935-eb1a-8caf7f99eb75" }, "outputs": [ { "data": { "text/html": [ " \n", " " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "pybop.quick_plot(problem, problem_inputs=x, title=\"Optimised Comparison\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Spatial Plotting\n", "\n", "We can now plot the spatial variables from the solution object. First, the final negative current collector potential can be displayed. In this example, this is just a reference variable, but could be used for fitting or optimisation in the correct workflows." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "sol = problem.evaluate(parameters.as_dict(x))\n", "\n", "go.Figure(\n", " [\n", " go.Contour(\n", " x=np.arange(0, model.var_pts[\"y\"] - 1, 1),\n", " y=np.arange(0, model.var_pts[\"z\"] - 1, 1),\n", " z=sol[\"Negative current collector potential [V]\"][:, :, -1],\n", " colorscale=\"Viridis\",\n", " )\n", " ],\n", " layout=dict(\n", " title=\"Negative current collector potential [V]\",\n", " xaxis_title=\"x node\",\n", " yaxis_title=\"y node\",\n", " width=600,\n", " height=600,\n", " ),\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We plot can then plot the positive current collector potential," ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "go.Figure(\n", " [\n", " go.Contour(\n", " x=np.arange(0, model.var_pts[\"y\"] - 1, 1),\n", " y=np.arange(0, model.var_pts[\"z\"] - 1, 1),\n", " z=sol[\"Positive current collector potential [V]\"][:, :, -1],\n", " colorscale=\"Viridis\",\n", " )\n", " ],\n", " layout=dict(\n", " title=\"Positive current collector potential [V]\",\n", " xaxis_title=\"x node\",\n", " yaxis_title=\"y node\",\n", " width=600,\n", " height=600,\n", " ),\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Convergence and Parameter Trajectories\n", "\n", "To assess the optimisation process, we can plot the convergence of the cost function and the trajectories of the parameters:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "N5XYkevi04qD" }, "outputs": [ { "data": { "text/html": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "pybop.plot_convergence(optim)\n", "pybop.plot_parameters(optim);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Cost Landscape\n", "\n", "Finally, we can visualise the cost landscape and the path taken by the optimiser:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "pybop.plot2d(optim, steps=10);" ] } ], "metadata": { "colab": { "provenance": [] }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.4" } }, "nbformat": 4, "nbformat_minor": 4 }