{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "expmkveO04pw" }, "source": [ "## Parameter Estimation with AdamW in PyBOP\n", "\n", "In this notebook, we demonstrate an example of parameter estimation for a single-particle model using the AdamW optimiser [1][2]. The AdamW optimiser is an algorithm for gradient-based optimisation, combining the advantages of the Adaptive Gradient Algorithm (AdaGrad) and Root Mean Square Propagation (RMSProp).\n", "\n", "[[1]: Adam: A Method for Stochastic Optimization](https://arxiv.org/abs/1412.6980) \n", "\n", "[[2]: Decoupled Weight Decay Regularization](https://doi.org/10.48550/arXiv.1711.05101)\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": [], "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" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's fix the random seed in order to generate consistent output during development, although this does not need to be done in practice." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.random.seed(8)" ] }, { "cell_type": "markdown", "metadata": { "id": "5XU-dMtU04p2" }, "source": [ "### Generate Synthetic Data\n", "\n", "To demonstrate parameter estimation, we first need some data. We will generate synthetic data using the PyBOP 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(\"Chen2020\")\n", "model = pybop.lithium_ion.SPM(parameter_set=parameter_set)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Simulating 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, 2)\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": [ "## Identify 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 Optimisation 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 = [\n", " pybop.Parameter(\n", " \"Negative electrode active material volume fraction\",\n", " prior=pybop.Gaussian(0.6, 0.02),\n", " bounds=[0.5, 0.8],\n", " ),\n", " pybop.Parameter(\n", " \"Positive electrode active material volume fraction\",\n", " prior=pybop.Gaussian(0.48, 0.02),\n", " bounds=[0.4, 0.7],\n", " ),\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": [ { "name": "stdout", "output_type": "stream", "text": [ "NOTE: Boundaries ignored by AdamW\n" ] } ], "source": [ "problem = pybop.FittingProblem(model, parameters, dataset)\n", "cost = pybop.SumSquaredError(problem)\n", "optim = pybop.Optimisation(cost, optimiser=pybop.AdamW)\n", "optim.set_max_unchanged_iterations(40)\n", "optim.set_max_iterations(150)" ] }, { "cell_type": "markdown", "metadata": { "id": "caprp-bV04qB" }, "source": [ "### Running the Optimisation\n", "\n", "We proceed to run the AdamW 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.76335438, 0.66225687])" ] }, "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": { "image/svg+xml": [ "02004006008003.83.853.93.9544.05ReferenceModelOptimised ComparisonTime / sVoltage / V" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "pybop.quick_plot(problem, problem_inputs=x, title=\"Optimised Comparison\");" ] }, { "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": { "image/svg+xml": [ "2040608010012014000.511.522.533.54ConvergenceIterationCost" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "501001500.60.650.70.750.80.85501001500.50.550.60.650.7Negative electrode active material volume fractionPositive electrode active material volume fractionParameter ConvergenceFunction CallFunction CallNegative electrode active material volume fractionPositive electrode active material volume fraction" ] }, "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": { "image/svg+xml": [ "0.50.550.60.650.70.750.80.40.450.50.550.60.650.70246810Cost LandscapeNegative electrode active material volume fractionPositive electrode active material volume fraction" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "0.60.650.70.750.80.850.90.50.550.60.650.70.750.80.40.81.21.622.4Cost LandscapeNegative electrode active material volume fractionPositive electrode active material volume fraction" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Plot the cost landscape\n", "pybop.plot2d(cost, steps=15)\n", "# Plot the cost landscape with optimisation path and updated bounds\n", "bounds = np.asarray([[0.6, 0.9], [0.5, 0.8]])\n", "pybop.plot2d(optim, bounds=bounds, steps=15);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Conclusion\n", "\n", "This notebook illustrates how to perform parameter estimation using AdamW in PyBOP, providing insights into the optimisation process through various visualisations." ] } ], "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.11.9" } }, "nbformat": 4, "nbformat_minor": 4 }