{ "cells": [ { "cell_type": "markdown", "id": "0", "metadata": {}, "source": [ "# Interacting with PyBOP optimisers\n", "\n", "This notebook introduces two interfaces to interact with PyBOP's optimiser classes.\n", "\n", "### Set the Environment" ] }, { "cell_type": "code", "execution_count": null, "id": "1", "metadata": {}, "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" ] }, { "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" ] } ], "source": [ "%pip install --upgrade pip ipywidgets -q\n", "%pip install pybop -q\n", "\n", "# Import the necessary libraries\n", "import numpy as np\n", "\n", "import pybop\n", "\n", "pybop.plot.PlotlyManager().pio.renderers.default = \"notebook_connected\"" ] }, { "cell_type": "markdown", "id": "2", "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, "id": "3", "metadata": {}, "outputs": [], "source": [ "np.random.seed(8)" ] }, { "cell_type": "markdown", "id": "4", "metadata": {}, "source": [ "## Setup the model, problem, and cost\n", "\n", "The code block below sets up the model, problem, and cost objects. For more information on this process, take a look at other notebooks in the examples directory." ] }, { "cell_type": "code", "execution_count": null, "id": "5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Setting open-circuit voltage to default function\n" ] } ], "source": [ "# Load the parameters\n", "parameter_set = pybop.ParameterSet(\n", " json_path=\"../scripts/parameters/initial_ecm_parameters.json\"\n", ")\n", "parameter_set.import_parameters()\n", "# Define the model\n", "model = pybop.empirical.Thevenin(\n", " parameter_set=parameter_set, options={\"number of rc elements\": 1}\n", ")\n", "\n", "# Define the parameters\n", "parameters = pybop.Parameter(\n", " \"R0 [Ohm]\",\n", " prior=pybop.Gaussian(0.0002, 0.0001),\n", " bounds=[1e-4, 1e-2],\n", ")\n", "\n", "# Generate synthetic data\n", "t_eval = np.arange(0, 900, 2)\n", "values = model.predict(t_eval=t_eval)\n", "\n", "# Form dataset\n", "dataset = pybop.Dataset(\n", " {\n", " \"Time [s]\": t_eval,\n", " \"Current function [A]\": values[\"Current [A]\"].data,\n", " \"Voltage [V]\": values[\"Voltage [V]\"].data,\n", " }\n", ")\n", "\n", "# Construct problem and cost\n", "problem = pybop.FittingProblem(model, parameters, dataset)\n", "cost = pybop.SumSquaredError(problem)" ] }, { "cell_type": "markdown", "id": "6", "metadata": {}, "source": [ "## Interacting with the Optimisers\n", "\n", "Now that we have set up the required objects, we can introduce the two interfaces for interacting with PyBOP optimisers. These are:\n", " \n", "1. The direct optimiser (e.g. `pybop.XNES`)\n", "2. The optimisation class (i.e. `pybop.Optimisation`)\n", " \n", "These two methods provide two equivalent ways of interacting with PyBOP's optimisers. The first method provides a direct way to select the Optimiser, with the second method being a more general method with a default optimiser (`pybop.XNES`) set if you don't provide an optimiser. \n", "\n", "First, the direct interface is presented. With this interface the user can select from the [list of optimisers](https://github.com/pybop-team/PyBOP?tab=readme-ov-file#supported-methods) supported in PyBOP and construct them directly. Options can be passed as kwargs, or through get() / set() methods in the case of PINTS-based optimisers." ] }, { "cell_type": "code", "execution_count": null, "id": "7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Halt: No significant change for 15 iterations.\n", "OptimisationResult:\n", " Initial parameters: [0.00032648]\n", " Optimised parameters: [0.00099965]\n", " Final cost: 1.363466506257511e-09\n", " Optimisation time: 1.6153881549835205 seconds\n", " Number of iterations: 23\n", " SciPy result available: No\n" ] } ], "source": [ "optim_one = pybop.XNES(\n", " cost, max_iterations=50\n", ") # Direct optimiser class with options as kwargs\n", "optim_one.set_max_iterations(\n", " 50\n", ") # Alternative set() / get() methods for PINTS optimisers\n", "results = optim_one.run()" ] }, { "cell_type": "markdown", "id": "8", "metadata": {}, "source": [ "Next, the `Optimisation` interface is less direct than the previous one, but provides a single class to work with across PyBOP workflows. The options are passed the same way as the above method, through kwargs or get() / set() methods." ] }, { "cell_type": "code", "execution_count": null, "id": "9", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Halt: No significant change for 15 iterations.\n", "OptimisationResult:\n", " Initial parameters: [0.00032648]\n", " Optimised parameters: [0.00099985]\n", " Final cost: 2.431998416403876e-10\n", " Optimisation time: 1.972628116607666 seconds\n", " Number of iterations: 25\n", " SciPy result available: No\n" ] } ], "source": [ "optim_two = pybop.Optimisation(\n", " cost, optimiser=pybop.XNES, max_iterations=50\n", ") # Optimisation class with options as kwargs\n", "optim_two.set_max_iterations(\n", " 50\n", ") # Alternative set() / get() methods for PINTS optimisers\n", "results2 = optim_two.run()" ] }, { "cell_type": "markdown", "id": "10", "metadata": {}, "source": [ "We can show the equivalence of these two methods by comparing the optimiser objects:" ] }, { "cell_type": "code", "execution_count": null, "id": "11", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "isinstance(optim_one, type(optim_two.optimiser))" ] }, { "cell_type": "markdown", "id": "12", "metadata": {}, "source": [ "For completeness, we can show the optimiser solutions:" ] }, { "cell_type": "code", "execution_count": null, "id": "13", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Estimated parameters x1: [0.00099965]\n", "Estimated parameters x2: [0.00099985]\n" ] } ], "source": [ "print(\"Estimated parameters x1:\", results.x)\n", "print(\"Estimated parameters x2:\", results2.x)" ] }, { "cell_type": "markdown", "id": "14", "metadata": {}, "source": [ "## Closing Comments\n", "\n", "As both of these API's provide access to the same optimisers, please use either as you prefer. A couple things to note:\n", "\n", "- If you are using a SciPy-based optimiser (`pybop.SciPyMinimize`, `pybop.SciPyDifferentialEvolution`), the `set()` / `get()` methods for the optimiser options are not currently supported. These optimisers require options to be passed as kwargs.\n", "- The optimiser passed to `pybop.Optimisation` must not be a constructed object." ] } ], "metadata": { "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" } }, "nbformat": 4, "nbformat_minor": 5 }