{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "expmkveO04pw" }, "source": [ "## Different Optimisers for SPMe Parameter Estimation\n", "\n", "In this notebook, we demonstrate parameter estimation for a single-particle model for various PyBOP optimisers. PyBOP offers a variety of gradient and non-gradient based optimisers, with a table of the currently supported methods shown in the Readme. In this example, we will set up the model, problem, and cost function and investigate how the different optimisers perform under this task.\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 from its development branch and upgrade some dependencies:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "execution": { "iopub.execute_input": "2024-04-14T18:57:35.623566Z", "iopub.status.busy": "2024-04-14T18:57:35.621718Z", "iopub.status.idle": "2024-04-14T18:57:40.837085Z", "shell.execute_reply": "2024-04-14T18:57:40.836603Z" }, "id": "X87NUGPW04py", "outputId": "0d785b07-7cff-4aeb-e60a-4ff5a669afbf" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Requirement already satisfied: pip in /home/engs2510/.pyenv/versions/3.12.2/envs/pybop/lib/python3.12/site-packages (24.0)\n", "Collecting pip\n", " Using cached pip-24.1.1-py3-none-any.whl.metadata (3.6 kB)\n", "Collecting ipywidgets\n", " Using cached ipywidgets-8.1.3-py3-none-any.whl.metadata (2.4 kB)\n", "Requirement already satisfied: comm>=0.1.3 in /home/engs2510/.pyenv/versions/3.12.2/envs/pybop/lib/python3.12/site-packages (from ipywidgets) (0.2.2)\n", "Requirement already satisfied: ipython>=6.1.0 in /home/engs2510/.pyenv/versions/3.12.2/envs/pybop/lib/python3.12/site-packages (from ipywidgets) (8.26.0)\n", "Requirement already satisfied: traitlets>=4.3.1 in /home/engs2510/.pyenv/versions/3.12.2/envs/pybop/lib/python3.12/site-packages (from ipywidgets) (5.14.3)\n", "Collecting widgetsnbextension~=4.0.11 (from ipywidgets)\n", " Using cached widgetsnbextension-4.0.11-py3-none-any.whl.metadata (1.6 kB)\n", "Collecting jupyterlab-widgets~=3.0.11 (from ipywidgets)\n", " Using cached jupyterlab_widgets-3.0.11-py3-none-any.whl.metadata (4.1 kB)\n", "Requirement already satisfied: decorator in /home/engs2510/.pyenv/versions/3.12.2/envs/pybop/lib/python3.12/site-packages (from ipython>=6.1.0->ipywidgets) (5.1.1)\n", "Requirement already satisfied: jedi>=0.16 in /home/engs2510/.pyenv/versions/3.12.2/envs/pybop/lib/python3.12/site-packages (from ipython>=6.1.0->ipywidgets) (0.19.1)\n", "Requirement already satisfied: matplotlib-inline in /home/engs2510/.pyenv/versions/3.12.2/envs/pybop/lib/python3.12/site-packages (from ipython>=6.1.0->ipywidgets) (0.1.7)\n", "Requirement already satisfied: prompt-toolkit<3.1.0,>=3.0.41 in /home/engs2510/.pyenv/versions/3.12.2/envs/pybop/lib/python3.12/site-packages (from ipython>=6.1.0->ipywidgets) (3.0.47)\n", "Requirement already satisfied: pygments>=2.4.0 in /home/engs2510/.pyenv/versions/3.12.2/envs/pybop/lib/python3.12/site-packages (from ipython>=6.1.0->ipywidgets) (2.18.0)\n", "Requirement already satisfied: stack-data in /home/engs2510/.pyenv/versions/3.12.2/envs/pybop/lib/python3.12/site-packages (from ipython>=6.1.0->ipywidgets) (0.6.3)\n", "Requirement already satisfied: pexpect>4.3 in /home/engs2510/.pyenv/versions/3.12.2/envs/pybop/lib/python3.12/site-packages (from ipython>=6.1.0->ipywidgets) (4.9.0)\n", "Requirement already satisfied: parso<0.9.0,>=0.8.3 in /home/engs2510/.pyenv/versions/3.12.2/envs/pybop/lib/python3.12/site-packages (from jedi>=0.16->ipython>=6.1.0->ipywidgets) (0.8.4)\n", "Requirement already satisfied: ptyprocess>=0.5 in /home/engs2510/.pyenv/versions/3.12.2/envs/pybop/lib/python3.12/site-packages (from pexpect>4.3->ipython>=6.1.0->ipywidgets) (0.7.0)\n", "Requirement already satisfied: wcwidth in /home/engs2510/.pyenv/versions/3.12.2/envs/pybop/lib/python3.12/site-packages (from prompt-toolkit<3.1.0,>=3.0.41->ipython>=6.1.0->ipywidgets) (0.2.13)\n", "Requirement already satisfied: executing>=1.2.0 in /home/engs2510/.pyenv/versions/3.12.2/envs/pybop/lib/python3.12/site-packages (from stack-data->ipython>=6.1.0->ipywidgets) (2.0.1)\n", "Requirement already satisfied: asttokens>=2.1.0 in /home/engs2510/.pyenv/versions/3.12.2/envs/pybop/lib/python3.12/site-packages (from stack-data->ipython>=6.1.0->ipywidgets) (2.4.1)\n", "Requirement already satisfied: pure-eval in /home/engs2510/.pyenv/versions/3.12.2/envs/pybop/lib/python3.12/site-packages (from stack-data->ipython>=6.1.0->ipywidgets) (0.2.2)\n", "Requirement already satisfied: six>=1.12.0 in /home/engs2510/.pyenv/versions/3.12.2/envs/pybop/lib/python3.12/site-packages (from asttokens>=2.1.0->stack-data->ipython>=6.1.0->ipywidgets) (1.16.0)\n", "Using cached pip-24.1.1-py3-none-any.whl (1.8 MB)\n", "Using cached ipywidgets-8.1.3-py3-none-any.whl (139 kB)\n", "Using cached jupyterlab_widgets-3.0.11-py3-none-any.whl (214 kB)\n", "Using cached widgetsnbextension-4.0.11-py3-none-any.whl (2.3 MB)\n", "Installing collected packages: widgetsnbextension, pip, jupyterlab-widgets, ipywidgets\n", " Attempting uninstall: pip\n", " Found existing installation: pip 24.0\n", " Uninstalling pip-24.0:\n", " Successfully uninstalled pip-24.0\n", "Successfully installed ipywidgets-8.1.3 jupyterlab-widgets-3.0.11 pip-24.1.1 widgetsnbextension-4.0.11\n", "Note: you may need to restart the kernel to use updated packages.\n", "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ "%pip install --upgrade pip ipywidgets\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": 2, "metadata": { "execution": { "iopub.execute_input": "2024-04-14T18:57:40.839580Z", "iopub.status.busy": "2024-04-14T18:57:40.839331Z", "iopub.status.idle": "2024-04-14T18:57:46.229867Z", "shell.execute_reply": "2024-04-14T18:57:46.229451Z" }, "id": "SQdt4brD04p1" }, "outputs": [], "source": [ "import numpy as np\n", "\n", "import pybop" ] }, { "cell_type": "markdown", "metadata": { "id": "5XU-dMtU04p2" }, "source": [ "## Generating Synthetic Data\n", "\n", "To demonstrate the parameter estimation, we first need some data. We will generate synthetic data using a PyBOP DFN 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, constructing the DFN for synthetic generation, and the model we will be fitting (SPMe)." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2024-04-14T18:57:46.240177Z", "iopub.status.busy": "2024-04-14T18:57:46.239573Z", "iopub.status.idle": "2024-04-14T18:57:46.367163Z", "shell.execute_reply": "2024-04-14T18:57:46.366832Z" } }, "outputs": [], "source": [ "parameter_set = pybop.ParameterSet.pybamm(\"Chen2020\")\n", "synth_model = pybop.lithium_ion.DFN(parameter_set=parameter_set)\n", "model = pybop.lithium_ion.SPMe(parameter_set=parameter_set)" ] }, { "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 discharge to generate the voltage data." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2024-04-14T18:57:46.370360Z", "iopub.status.busy": "2024-04-14T18:57:46.370226Z", "iopub.status.idle": "2024-04-14T18:57:46.658845Z", "shell.execute_reply": "2024-04-14T18:57:46.658537Z" }, "id": "sBasxv8U04p3" }, "outputs": [], "source": [ "t_eval = np.arange(0, 2000, 10)\n", "init_soc = 1.0\n", "values = synth_model.predict(t_eval=t_eval, init_soc=init_soc)" ] }, { "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": 5, "metadata": { "execution": { "iopub.execute_input": "2024-04-14T18:57:46.660483Z", "iopub.status.busy": "2024-04-14T18:57:46.660397Z", "iopub.status.idle": "2024-04-14T18:57:46.675801Z", "shell.execute_reply": "2024-04-14T18:57:46.675337Z" } }, "outputs": [], "source": [ "sigma = 0.002\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": 6, "metadata": { "execution": { "iopub.execute_input": "2024-04-14T18:57:46.677466Z", "iopub.status.busy": "2024-04-14T18:57:46.677354Z", "iopub.status.idle": "2024-04-14T18:57:46.690633Z", "shell.execute_reply": "2024-04-14T18:57:46.690395Z" }, "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. In this example, non-geometric parameters for each electrode's active material volume fraction are selected." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2024-04-14T18:57:46.691889Z", "iopub.status.busy": "2024-04-14T18:57:46.691798Z", "iopub.status.idle": "2024-04-14T18:57:46.693610Z", "shell.execute_reply": "2024-04-14T18:57:46.693361Z" }, "id": "WPCybXIJ04qA" }, "outputs": [], "source": [ "parameters = pybop.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": {}, "source": [ "### Selecting the Optimisers\n", "\n", "Now, we can select the optimisers to investigate. The first object is a list of non-gradient-based PINTS's optimisers. The next object comprises the gradient-based PINTS's optimisers (AdamW, GradientDescent, IRPropMin). The final object forms the SciPy optimisers which can have gradient and non-gradient-based algorithms." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2024-04-14T18:57:46.695076Z", "iopub.status.busy": "2024-04-14T18:57:46.694955Z", "iopub.status.idle": "2024-04-14T18:57:46.696878Z", "shell.execute_reply": "2024-04-14T18:57:46.696576Z" } }, "outputs": [], "source": [ "gradient_optimisers = [\n", " pybop.AdamW,\n", " pybop.GradientDescent,\n", " pybop.IRPropMin,\n", "]\n", "\n", "non_gradient_optimisers = [\n", " pybop.CMAES,\n", " pybop.SNES,\n", " pybop.PSO,\n", " pybop.XNES,\n", " pybop.NelderMead,\n", " pybop.CuckooSearch,\n", "]\n", "\n", "scipy_optimisers = [\n", " pybop.SciPyMinimize,\n", " pybop.SciPyDifferentialEvolution,\n", "]" ] }, { "cell_type": "markdown", "metadata": { "id": "n4OHa-aF04qA" }, "source": [ "### Setting up the Optimisation Problem\n", "\n", "With the datasets, parameters, and optimisers defined, we can set up the optimisation problem and cost function. In this example we loop through all of the above optimisers and store the results for later visualisation and analysis." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2024-04-14T18:57:46.698400Z", "iopub.status.busy": "2024-04-14T18:57:46.698293Z", "iopub.status.idle": "2024-04-14T18:59:08.049333Z", "shell.execute_reply": "2024-04-14T18:59:08.048882Z" }, "id": "etMzRtx404qA" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Running AdamW\n", "NOTE: Boundaries ignored by AdamW\n", "Running GradientDescent\n", "NOTE: Boundaries ignored by \n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Error: Error in Function::call for 'event_0' [MXFunction] at .../casadi/core/function.cpp:361:\n", ".../casadi/core/function_internal.hpp:1649: Input 1 (i1) has mismatching shape. Got 100-by-1. Allowed dimensions, in general, are:\n", " - The input dimension N-by-M (here 300-by-1)\n", " - A scalar, i.e. 1-by-1\n", " - M-by-N if N=1 or M=1 (i.e. a transposed vector)\n", " - N-by-M1 if K*M1=M for some K (argument repeated horizontally)\n", " - N-by-P*M, indicating evaluation with multiple arguments (P must be a multiple of 1 for consistency with previous inputs)\n", "Running IRPropMin\n" ] } ], "source": [ "optims = []\n", "xs = []\n", "problem = pybop.FittingProblem(model, parameters, dataset, init_soc=init_soc)\n", "cost = pybop.SumSquaredError(problem)\n", "for optimiser in gradient_optimisers:\n", " print(f\"Running {optimiser.__name__}\")\n", " optim = optimiser(cost, max_unchanged_iterations=20, max_iterations=60)\n", " x, _ = optim.run()\n", " optims.append(optim)\n", " xs.append(x)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Running CMAES\n", "Running SNES\n", "Running PSO\n", "Running XNES\n", "Running NelderMead\n", "NOTE: Boundaries ignored by \n", "Running CuckooSearch\n" ] } ], "source": [ "for optimiser in non_gradient_optimisers:\n", " print(f\"Running {optimiser.__name__}\")\n", " optim = optimiser(cost, max_unchanged_iterations=20, max_iterations=60)\n", " x, _ = optim.run()\n", " optims.append(optim)\n", " xs.append(x)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Running SciPyMinimize\n", "Running SciPyDifferentialEvolution\n", "Ignoring x0. Initial conditions are not used for differential_evolution.\n" ] } ], "source": [ "for optimiser in scipy_optimisers:\n", " print(f\"Running {optimiser.__name__}\")\n", " optim = optimiser(cost, max_iterations=60)\n", " x, _ = optim.run()\n", " optims.append(optim)\n", " xs.append(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we can compare the identified parameters across the optimisers. This gives us insight into how well each optimiser traversed the cost landscape. The ground-truth parameter values for the `Chen2020` parameter set are: \n", "\n", "- Negative active material volume fraction: `0.75`\n", "- Positive active material volume fraction: `0.665`" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "execution": { "iopub.execute_input": "2024-04-14T18:59:08.051986Z", "iopub.status.busy": "2024-04-14T18:59:08.051532Z", "iopub.status.idle": "2024-04-14T18:59:08.054819Z", "shell.execute_reply": "2024-04-14T18:59:08.054559Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "| Optimiser: AdamW | Results: [0.79283046 0.66146761] |\n", "| Optimiser: Gradient descent | Results: [0.54971799 0.92691691] |\n", "| Optimiser: iRprop- | Results: [0.72245096 0.67281911] |\n", "| Optimiser: Covariance Matrix Adaptation Evolution Strategy (CMA-ES) | Results: [0.72099365 0.67312846] |\n", "| Optimiser: Seperable Natural Evolution Strategy (SNES) | Results: [0.72092695 0.67313321] |\n", "| Optimiser: Particle Swarm Optimisation (PSO) | Results: [0.71681934 0.67366943] |\n", "| Optimiser: Exponential Natural Evolution Strategy (xNES) | Results: [0.71352763 0.67470134] |\n", "| Optimiser: Nelder-Mead | Results: [0.72127038 0.67308243] |\n", "| Optimiser: Cuckoo Search | Results: [0.70772893 0.67571981] |\n", "| Optimiser: SciPyMinimize | Results: [0.62747952 0.7 ] |\n", "| Optimiser: SciPyDifferentialEvolution | Results: [0.72100138 0.67312735] |\n" ] } ], "source": [ "for optim in optims:\n", " print(f\"| Optimiser: {optim.name()} | Results: {optim.result.x} |\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Many of the above optimisers found the correct value for the positive active material volume fraction. However, none of them found the correct value for the negative electrode. Next, we can investigate if this was an optimiser or parameter observability failure." ] }, { "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 Solutions\n", "\n", "We can quickly plot the system's response using the estimated parameters for each optimiser and the target dataset." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 467 }, "execution": { "iopub.execute_input": "2024-04-14T18:59:08.056146Z", "iopub.status.busy": "2024-04-14T18:59:08.056059Z", "iopub.status.idle": "2024-04-14T18:59:09.176513Z", "shell.execute_reply": "2024-04-14T18:59:09.176211Z" }, "id": "tJUJ80Ve04qD", "outputId": "855fbaa2-1e09-4935-eb1a-8caf7f99eb75" }, "outputs": [ { "data": { "image/svg+xml": [ "05001000150020003.53.63.73.83.94ReferenceModelAdamWTime / sVoltage / V" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "05001000150020003.53.63.73.83.944.1ReferenceModelGradient descentTime / sVoltage / V" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "05001000150020003.53.63.73.83.94ReferenceModeliRprop-Time / sVoltage / V" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "05001000150020003.53.63.73.83.94ReferenceModelCovariance Matrix Adaptation Evolution Strategy (CMA-ES)Time / sVoltage / V" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "05001000150020003.53.63.73.83.94ReferenceModelSeperable Natural Evolution Strategy (SNES)Time / sVoltage / V" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "05001000150020003.53.63.73.83.94ReferenceModelParticle Swarm Optimisation (PSO)Time / sVoltage / V" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "05001000150020003.53.63.73.83.94ReferenceModelExponential Natural Evolution Strategy (xNES)Time / sVoltage / V" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "05001000150020003.53.63.73.83.94ReferenceModelNelder-MeadTime / sVoltage / V" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "05001000150020003.53.63.73.83.94ReferenceModelCuckoo SearchTime / sVoltage / V" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "05001000150020003.53.63.73.83.94ReferenceModelSciPyMinimizeTime / sVoltage / V" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "05001000150020003.53.63.73.83.94ReferenceModelSciPyDifferentialEvolutionTime / sVoltage / V" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "for optim, x in zip(optims, xs):\n", " pybop.quick_plot(optim.cost.problem, problem_inputs=x, title=optim.name())" ] }, { "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": 14, "metadata": { "execution": { "iopub.execute_input": "2024-04-14T18:59:09.178184Z", "iopub.status.busy": "2024-04-14T18:59:09.178044Z", "iopub.status.idle": "2024-04-14T18:59:41.197859Z", "shell.execute_reply": "2024-04-14T18:59:41.197499Z" }, "id": "N5XYkevi04qD" }, "outputs": [ { "data": { "image/svg+xml": [ "5101520253000.511.522.533.5AdamWIterationCost" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "1020300.60.650.70.750.80.851020300.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" }, { "data": { "image/svg+xml": [ "510152011.522.533.5Gradient descentIterationCost" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "510152000.10.20.30.40.50.60.70.851015200.40.50.60.70.80.911.11.21.3Negative 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" }, { "data": { "image/svg+xml": [ "10203040506000.511.522.533.5iRprop-IterationCost" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "2040600.650.70.750.82040600.50.550.60.65Negative 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" }, { "data": { "image/svg+xml": [ "1020304000.511.522.53Covariance Matrix Adaptation Evolution Strategy (CMA-ES)IterationCost" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "501001502002500.50.550.60.650.70.750.8501001502002500.450.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" }, { "data": { "image/svg+xml": [ "10203040506000.511.522.5Seperable Natural Evolution Strategy (SNES)IterationCost" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "1002003000.550.60.650.70.750.81002003000.450.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" }, { "data": { "image/svg+xml": [ "10203040500.0030.00350.0040.00450.005Particle Swarm Optimisation (PSO)IterationCost" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "501001502002500.50.550.60.650.70.750.8501001502002500.450.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" }, { "data": { "image/svg+xml": [ "10203040506000.511.52Exponential Natural Evolution Strategy (xNES)IterationCost" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "1002003000.540.560.580.60.620.640.660.680.70.721002003000.450.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" }, { "data": { "image/svg+xml": [ "10203040506000.511.522.533.5Nelder-MeadIterationCost" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "2040600.620.640.660.680.70.722040600.50.550.60.650.70.75Negative 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" }, { "data": { "image/svg+xml": [ "1020304050600.0030.00350.0040.00450.005Cuckoo SearchIterationCost" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "1002003000.50.550.60.650.70.750.81002003000.40.450.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" }, { "data": { "image/svg+xml": [ "510152000.10.20.30.40.50.6SciPyMinimizeIterationCost" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "102030400.560.580.60.620.640.660.680.70.72102030400.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" }, { "data": { "image/svg+xml": [ "51015200.00290.0030.00310.00320.00330.00340.0035SciPyDifferentialEvolutionIterationCost" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "2004006000.50.550.60.650.70.750.82004006000.40.450.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": [ "for optim in optims:\n", " pybop.plot_convergence(optim, title=optim.name())\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. This should give us additional insight into whether the negative electrode volume fraction is observable or not. For an observable parameter, the cost landscape needs to have a clear minimum with respect to the parameter in question. More clearly, the parameter value has to have an effect on the cost function." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "execution": { "iopub.execute_input": "2024-04-14T18:59:41.199630Z", "iopub.status.busy": "2024-04-14T18:59:41.199505Z", "iopub.status.idle": "2024-04-14T19:00:13.043471Z", "shell.execute_reply": "2024-04-14T19:00:13.043038Z" } }, "outputs": [ { "data": { "image/svg+xml": [ "0.50.550.60.650.70.750.80.550.60.650.70.750.80.40.81.21.622.4AdamWNegative electrode active material volume fractionPositive electrode active material volume fraction" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "0.50.550.60.650.70.750.80.550.60.650.70.750.80.40.81.21.622.4Gradient descentNegative electrode active material volume fractionPositive electrode active material volume fraction" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "0.50.550.60.650.70.750.80.550.60.650.70.750.80.40.81.21.622.4iRprop-Negative electrode active material volume fractionPositive electrode active material volume fraction" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "0.50.550.60.650.70.750.80.550.60.650.70.750.80.40.81.21.622.4Covariance Matrix Adaptation Evolution Strategy (CMA-ES)Negative electrode active material volume fractionPositive electrode active material volume fraction" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "0.50.550.60.650.70.750.80.550.60.650.70.750.80.40.81.21.622.4Seperable Natural Evolution Strategy (SNES)Negative electrode active material volume fractionPositive electrode active material volume fraction" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "0.50.550.60.650.70.750.80.550.60.650.70.750.80.40.81.21.622.4Particle Swarm Optimisation (PSO)Negative electrode active material volume fractionPositive electrode active material volume fraction" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "0.50.550.60.650.70.750.80.550.60.650.70.750.80.40.81.21.622.4Exponential Natural Evolution Strategy (xNES)Negative electrode active material volume fractionPositive electrode active material volume fraction" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "0.50.550.60.650.70.750.80.550.60.650.70.750.80.40.81.21.622.4Nelder-MeadNegative electrode active material volume fractionPositive electrode active material volume fraction" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "0.50.550.60.650.70.750.80.550.60.650.70.750.80.40.81.21.622.4Cuckoo SearchNegative electrode active material volume fractionPositive electrode active material volume fraction" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "0.50.550.60.650.70.750.80.550.60.650.70.750.80.40.81.21.622.4SciPyMinimizeNegative electrode active material volume fractionPositive electrode active material volume fraction" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "0.50.550.60.650.70.750.80.550.60.650.70.750.80.40.81.21.622.4SciPyDifferentialEvolutionNegative electrode active material volume fractionPositive electrode active material volume fraction" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Plot the cost landscape with optimisation path and updated bounds\n", "bounds = np.asarray([[0.5, 0.8], [0.55, 0.8]])\n", "for optim in optims:\n", " pybop.plot2d(optim, bounds=bounds, steps=10, title=optim.name())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Given the synthetic data and corresponding system excitation, the observability of the negative electrode active material fraction is quite low. As such, we would need to excite the system in a different way or observe a different signal to acquire a unique value." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Conclusion\n", "\n", "This notebook illustrates how to perform parameter estimation using PyBOP, across both gradient and non-gradient-based optimisers. " ] } ], "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.2" }, "widgets": { "application/vnd.jupyter.widget-state+json": { "06f2374f91c8455bb63252092512f2ed": { "model_module": "@jupyter-widgets/base", "model_module_version": "2.0.0", "model_name": "LayoutModel", "state": { "_model_module": "@jupyter-widgets/base", "_model_module_version": "2.0.0", "_model_name": "LayoutModel", "_view_count": null, "_view_module": "@jupyter-widgets/base", "_view_module_version": "2.0.0", "_view_name": "LayoutView", "align_content": null, "align_items": null, "align_self": null, "border_bottom": null, "border_left": null, "border_right": null, "border_top": null, "bottom": null, "display": null, "flex": null, "flex_flow": null, "grid_area": null, "grid_auto_columns": null, "grid_auto_flow": null, "grid_auto_rows": null, "grid_column": null, "grid_gap": null, "grid_row": null, "grid_template_areas": null, "grid_template_columns": null, "grid_template_rows": null, "height": null, "justify_content": null, "justify_items": null, "left": null, "margin": null, "max_height": null, "max_width": null, "min_height": null, "min_width": null, "object_fit": null, "object_position": null, "order": null, "overflow": null, "padding": null, "right": null, "top": null, "visibility": null, "width": null } }, "423bffea3a1c42b49a9ad71218e5811b": { "model_module": "@jupyter-widgets/base", "model_module_version": "2.0.0", "model_name": "LayoutModel", "state": { "_model_module": "@jupyter-widgets/base", "_model_module_version": "2.0.0", "_model_name": "LayoutModel", "_view_count": null, "_view_module": "@jupyter-widgets/base", "_view_module_version": "2.0.0", "_view_name": "LayoutView", "align_content": null, "align_items": null, "align_self": null, "border_bottom": null, "border_left": null, "border_right": null, "border_top": null, "bottom": null, "display": null, "flex": null, "flex_flow": null, "grid_area": null, "grid_auto_columns": null, "grid_auto_flow": null, "grid_auto_rows": null, "grid_column": null, "grid_gap": null, "grid_row": null, "grid_template_areas": null, "grid_template_columns": null, "grid_template_rows": null, "height": null, "justify_content": null, "justify_items": null, "left": null, "margin": null, "max_height": null, "max_width": null, "min_height": null, "min_width": null, "object_fit": null, "object_position": null, "order": null, "overflow": null, "padding": null, "right": null, "top": null, "visibility": null, "width": null } }, "56ff19291e464d63b23e63b8e2ac9ea3": { "model_module": "@jupyter-widgets/controls", "model_module_version": "2.0.0", "model_name": "SliderStyleModel", "state": { "_model_module": "@jupyter-widgets/controls", "_model_module_version": "2.0.0", "_model_name": "SliderStyleModel", "_view_count": null, "_view_module": "@jupyter-widgets/base", "_view_module_version": "2.0.0", "_view_name": "StyleView", "description_width": "", "handle_color": null } }, "646a8670cb204a31bb56bc2380898093": { "model_module": "@jupyter-widgets/base", "model_module_version": "2.0.0", "model_name": "LayoutModel", "state": { "_model_module": "@jupyter-widgets/base", "_model_module_version": "2.0.0", "_model_name": "LayoutModel", "_view_count": null, "_view_module": "@jupyter-widgets/base", "_view_module_version": "2.0.0", "_view_name": "LayoutView", "align_content": null, "align_items": null, "align_self": null, "border_bottom": null, "border_left": null, "border_right": null, "border_top": null, "bottom": null, "display": null, "flex": null, "flex_flow": null, "grid_area": null, "grid_auto_columns": null, "grid_auto_flow": null, "grid_auto_rows": null, "grid_column": null, "grid_gap": null, "grid_row": null, "grid_template_areas": null, "grid_template_columns": null, "grid_template_rows": null, "height": null, "justify_content": null, "justify_items": null, "left": null, "margin": null, "max_height": null, "max_width": null, "min_height": null, "min_width": null, "object_fit": null, "object_position": null, "order": null, "overflow": null, "padding": null, "right": null, "top": null, "visibility": null, "width": null } }, "7d46516469314b88be3500e2afcafcf6": { "model_module": "@jupyter-widgets/output", "model_module_version": "1.0.0", "model_name": "OutputModel", "state": { "_dom_classes": [], "_model_module": "@jupyter-widgets/output", "_model_module_version": "1.0.0", "_model_name": "OutputModel", "_view_count": null, "_view_module": "@jupyter-widgets/output", "_view_module_version": "1.0.0", "_view_name": "OutputView", "layout": "IPY_MODEL_646a8670cb204a31bb56bc2380898093", "msg_id": "", "outputs": [], "tabbable": null, "tooltip": null } }, "8d003c14da5f4fa68284b28c15cee6e6": { "model_module": "@jupyter-widgets/controls", "model_module_version": "2.0.0", "model_name": "VBoxModel", "state": { "_dom_classes": [ "widget-interact" ], "_model_module": "@jupyter-widgets/controls", "_model_module_version": "2.0.0", "_model_name": "VBoxModel", "_view_count": null, "_view_module": "@jupyter-widgets/controls", "_view_module_version": "2.0.0", "_view_name": "VBoxView", "box_style": "", "children": [ "IPY_MODEL_aef2fa7adcc14ad0854b73d5910ae3b4", "IPY_MODEL_7d46516469314b88be3500e2afcafcf6" ], "layout": "IPY_MODEL_423bffea3a1c42b49a9ad71218e5811b", "tabbable": null, "tooltip": null } }, "aef2fa7adcc14ad0854b73d5910ae3b4": { "model_module": "@jupyter-widgets/controls", "model_module_version": "2.0.0", "model_name": "FloatSliderModel", "state": { "_dom_classes": [], "_model_module": "@jupyter-widgets/controls", "_model_module_version": "2.0.0", "_model_name": "FloatSliderModel", "_view_count": null, "_view_module": "@jupyter-widgets/controls", "_view_module_version": "2.0.0", "_view_name": "FloatSliderView", "behavior": "drag-tap", "continuous_update": true, "description": "t", "description_allow_html": false, "disabled": false, "layout": "IPY_MODEL_06f2374f91c8455bb63252092512f2ed", "max": 1.1333333333333333, "min": 0, "orientation": "horizontal", "readout": true, "readout_format": ".2f", "step": 0.011333333333333332, "style": "IPY_MODEL_56ff19291e464d63b23e63b8e2ac9ea3", "tabbable": null, "tooltip": null, "value": 0 } } } } }, "nbformat": 4, "nbformat_minor": 4 }