{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "expmkveO04pw" }, "source": [ "## An Electrode Design Optimisation Example\n", "\n", "A design optimisation example loosely based on work by L.D. Couto available at [[1]](https://doi.org/10.1016/j.energy.2022.125966).\n", "\n", "The target is to maximise the gravimetric energy density over a range of possible design parameter values, including for example:\n", "\n", "cross-sectional area = height x width (only need change one), electrode widths, particle radii, volume fractions and separator width.\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/Second_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", "/Users/engs2510/Documents/Git/Second_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": [ "Next, we import the added packages plus any additional dependencies," ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "SQdt4brD04p1" }, "outputs": [], "source": [ "import numpy as np\n", "from pybamm import Parameter\n", "\n", "import pybop\n", "\n", "pybop.plot.PlotlyManager().pio.renderers.default = \"notebook_connected\"" ] }, { "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": "X8-tubYY04p_" }, "source": [ "## Optimising the Parameters" ] }, { "cell_type": "markdown", "metadata": { "id": "PQqhvSZN04p_" }, "source": [ "First, we define the model to be used for the parameter optimisation," ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "zuvGHWID04p_" }, "outputs": [], "source": [ "parameter_set = pybop.ParameterSet.pybamm(\"Chen2020\")\n", "parameter_set.update(\n", " {\n", " \"Electrolyte density [kg.m-3]\": Parameter(\"Separator density [kg.m-3]\"),\n", " \"Negative electrode active material density [kg.m-3]\": Parameter(\n", " \"Negative electrode density [kg.m-3]\"\n", " ),\n", " \"Negative electrode carbon-binder density [kg.m-3]\": Parameter(\n", " \"Negative electrode density [kg.m-3]\"\n", " ),\n", " \"Positive electrode active material density [kg.m-3]\": Parameter(\n", " \"Positive electrode density [kg.m-3]\"\n", " ),\n", " \"Positive electrode carbon-binder density [kg.m-3]\": Parameter(\n", " \"Positive electrode density [kg.m-3]\"\n", " ),\n", " },\n", " check_already_exists=False,\n", ")\n", "model = pybop.lithium_ion.SPMe(parameter_set=parameter_set)" ] }, { "cell_type": "markdown", "metadata": { "id": "ffS3CF_704qA" }, "source": [ "Next, we define the model parameters for optimisation. Furthermore, PyBOP provides functionality to define a prior for the parameters. The initial parameters values used in the optimisation will be randomly drawn from the prior distribution." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "WPCybXIJ04qA" }, "outputs": [], "source": [ "parameters = pybop.Parameters(\n", " pybop.Parameter(\n", " \"Positive electrode thickness [m]\",\n", " prior=pybop.Gaussian(7.56e-05, 0.05e-05),\n", " bounds=[65e-06, 10e-05],\n", " ),\n", " pybop.Parameter(\n", " \"Positive particle radius [m]\",\n", " prior=pybop.Gaussian(5.22e-06, 0.05e-06),\n", " bounds=[2e-06, 9e-06],\n", " ),\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we construct the experiment for design optimisation and the initial state-of-charge," ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "experiment = pybop.Experiment(\n", " [\"Discharge at 1C until 2.5 V (5 seconds period)\"],\n", ")" ] }, { "cell_type": "markdown", "metadata": { "id": "n4OHa-aF04qA" }, "source": [ "We can now define the output signal, the problem (which combines the model with the dataset) and construct a cost function which in this example is the `GravimetricEnergyDensity()` used to maximise the gravimetric energy density of the cell." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "etMzRtx404qA" }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/var/folders/2s/pn1xwy191_7bv761mlf_cgzh0000gq/T/ipykernel_45535/2045688338.py:1: UserWarning:\n", "\n", "The nominal capacity is fixed at the initial model value.\n", "\n" ] } ], "source": [ "problem = pybop.DesignProblem(\n", " model, parameters, experiment, initial_state={\"Initial SoC\": 0.7}\n", ")\n", "cost = pybop.GravimetricEnergyDensity(problem)" ] }, { "cell_type": "markdown", "metadata": { "id": "eQiGurUV04qB" }, "source": [ "Let's construct PyBOP's optimisation class. This class provides the methods needed to fit the forward model. For this example, we use particle swarm optimisation (PSO). Due to the computational requirements of the design optimisation methods, we limit the number of iterations to 15 for this example." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "N3FtAhrT04qB" }, "outputs": [], "source": [ "optim = pybop.PSO(cost, verbose=True, max_iterations=15)" ] }, { "cell_type": "markdown", "metadata": { "id": "caprp-bV04qB" }, "source": [ "Finally, we run the optimisation and return the values obtained," ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "-9OVt0EQ04qB" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Halt: Maximum number of iterations (15) reached.\n", "OptimisationResult:\n", " Initial parameters: [7.56456024e-05 5.27456414e-06]\n", " Optimised parameters: [8.27568156e-05 2.07975193e-06]\n", " Final cost: 279.3737537368842\n", " Optimisation time: 26.728662967681885 seconds\n", " Number of iterations: 15\n", " SciPy result available: No\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Initial gravimetric energy density: 256.31 Wh.kg-1\n", "Optimised gravimetric energy density: 279.37 Wh.kg-1\n" ] } ], "source": [ "results = optim.run()\n", "print(f\"Initial gravimetric energy density: {cost(results.x0):.2f} Wh.kg-1\")\n", "print(f\"Optimised gravimetric energy density: {results.final_cost:.2f} Wh.kg-1\")" ] }, { "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 initial parameters:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "ZVfozY0A04qC" }, "outputs": [ { "data": { "text/html": [ " \n", " " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "