From 3861461c1232d0e1cc9204268cdf61bd68a7ff80 Mon Sep 17 00:00:00 2001 From: Brady Planden Date: Mon, 5 Aug 2024 08:34:56 +0100 Subject: [PATCH 1/7] Initial examples --- examples/notebooks/solver_selection.ipynb | 370 ++++++++++++++++++++++ examples/scripts/changing_solvers.py | 83 +++++ 2 files changed, 453 insertions(+) create mode 100644 examples/notebooks/solver_selection.ipynb create mode 100644 examples/scripts/changing_solvers.py diff --git a/examples/notebooks/solver_selection.ipynb b/examples/notebooks/solver_selection.ipynb new file mode 100644 index 000000000..0555526d3 --- /dev/null +++ b/examples/notebooks/solver_selection.ipynb @@ -0,0 +1,370 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "expmkveO04pw" + }, + "source": [ + "## Investigate Different PyBaMM Solvers\n", + "\n", + "In this notebook, we discuss the process of changing PyBaMM solvers and the corresponding performance trade-offs with each. For further reading on different solvers, see the PyBaMM solver documentation:\n", + "\n", + "[[1]: PyBaMM Solvers](https://docs.pybamm.org/en/stable/source/api/solvers/index.html#)\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": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "X87NUGPW04py", + "outputId": "0d785b07-7cff-4aeb-e60a-4ff5a669afbf" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "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 -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 time\n", + "\n", + "import numpy as np\n", + "import pybamm\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.SPMe(parameter_set=parameter_set)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "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\n", + "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": {}, + "outputs": [], + "source": [ + "solvers = dict(\n", + " IDAKLU=pybamm.IDAKLUSolver(atol=1e-6, rtol=1e-6),\n", + " Casadi_safe=pybamm.CasadiSolver(atol=1e-6, rtol=1e-6, mode=\"safe\"),\n", + " Casadi_fast=pybamm.CasadiSolver(atol=1e-6, rtol=1e-6, mode=\"fast\"),\n", + " Casadi_fast_with_events=pybamm.CasadiSolver(\n", + " atol=1e-6, rtol=1e-6, mode=\"fast with events\"\n", + " ),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's setup a toy-problem to showcase the diferences. We run the forward model `n` times, for a variety of input values," + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n = 25 # Number of solves\n", + "inputs = [[x, y] for x, y in zip(np.linspace(0.45, 0.6, n), np.linspace(0.45, 0.6, n))]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, let's call the non-gradient based cost evaluate for each of the solvers and time the correseponding outcomes," + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cost Evaluate for IDA KLU solver: 3.354677200317383\n", + "Cost Evaluate for CasADi solver with 'safe' mode: 2.0228309631347656\n", + "Cost Evaluate for CasADi solver with 'fast' mode: 1.9543209075927734\n", + "Cost Evaluate for CasADi solver with 'fast with events' mode: 2.0191171169281006\n" + ] + } + ], + "source": [ + "parameter_set = pybop.ParameterSet.pybamm(\"Chen2020\")\n", + "for solver in solvers.values():\n", + " model = pybop.lithium_ion.SPMe(parameter_set=parameter_set, solver=solver)\n", + " problem = pybop.FittingProblem(model, parameters, dataset)\n", + " cost = pybop.SumSquaredError(problem)\n", + "\n", + " # Timing\n", + " t1 = time.time()\n", + " for i in range(n):\n", + " cost.evaluate(inputs[i])\n", + " print(f\"Cost Evaluate for {solver.name}: {time.time()-t1}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Great, so the Casadi performance appears to be XX times better! That is, atleast for the non-gradient solutions. Next, let's repeat the same toy problem, but for the gradient-based cost evaluation," + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cost Evaluate for IDA KLU solver: 1.4089879989624023\n", + "Cost Evaluate for CasADi solver with 'safe' mode: 3.617908000946045\n", + "Cost Evaluate for CasADi solver with 'fast' mode: 3.5603108406066895\n", + "Cost Evaluate for CasADi solver with 'fast with events' mode: 3.537614107131958\n" + ] + } + ], + "source": [ + "parameter_set = pybop.ParameterSet.pybamm(\"Chen2020\")\n", + "for solver in solvers.values():\n", + " model = pybop.lithium_ion.SPMe(parameter_set=parameter_set, solver=solver)\n", + " problem = pybop.FittingProblem(model, parameters, dataset)\n", + " cost = pybop.SumSquaredError(problem)\n", + "\n", + " # Timing\n", + " t1 = time.time()\n", + " for i in range(n):\n", + " cost.evaluateS1(inputs[i])\n", + " print(f\"Cost Evaluate for {solver.name}: {time.time()-t1}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now this is interesting, so we have performance variation between the Casadi solvers and the IDAKLU. Given this result, it appears to the Casadi solver is most benefical for non-gradient optimisers, while IDAKLU has a large improve for gradient-based optimisers." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Gradient compared to non-gradient methods\n", + "- No GPU methods in this notebook\n", + "- Import data or use model.predict?" + ] + } + ], + "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 +} diff --git a/examples/scripts/changing_solvers.py b/examples/scripts/changing_solvers.py new file mode 100644 index 000000000..b09df3222 --- /dev/null +++ b/examples/scripts/changing_solvers.py @@ -0,0 +1,83 @@ +import time + +import numpy as np +import pybamm + +import pybop + +# Parameter set and model definition +parameter_set = pybop.ParameterSet.pybamm("Chen2020") +solver = pybamm.IDAKLUSolver(atol=1e-6, rtol=1e-6) +model = pybop.lithium_ion.SPMe(parameter_set=parameter_set, solver=solver) + +solvers = [ + pybamm.IDAKLUSolver(atol=1e-6, rtol=1e-6), + pybamm.CasadiSolver(atol=1e-6, rtol=1e-6), +] + +for solver in solvers: + model.solver = solver + t1 = time.time() + model.predict(t_eval=np.linspace(0, 1, 100)) + print(time.time() - t1) + +# # Fitting parameters +# parameters = pybop.Parameters( +# pybop.Parameter( +# "Negative electrode active material volume fraction", +# prior=pybop.Gaussian(0.68, 0.05), +# ), +# pybop.Parameter( +# "Positive electrode active material volume fraction", +# prior=pybop.Gaussian(0.58, 0.05), +# ), +# ) +# +# # Generate data +# sigma = 0.001 +# t_eval = np.linspace(0, 900, 1800) +# values = model.predict(t_eval=t_eval) +# corrupt_values = values["Voltage [V]"].data + np.random.normal(0, sigma, len(t_eval)) +# +# # Form dataset +# dataset = pybop.Dataset( +# { +# "Time [s]": t_eval, +# "Current function [A]": values["Current [A]"].data, +# "Voltage [V]": corrupt_values, +# } +# ) +# +# # Generate problem, cost function, and optimisation class +# problem = pybop.FittingProblem(model, parameters, dataset) +# cost = pybop.SumSquaredError(problem) +# # optim = pybop.Adam( +# # cost, +# # sigma0=0.0001, +# # verbose=True, +# # min_iterations=125, +# # max_iterations=125, +# # ) +# +# # Run optimisation +# # t1 = time.time() +# # x, final_cost = optim.run() +# # t2 = time.time() +# # print(f"Time:{t2-t1}") +# # print("Estimated parameters:", x) +# +# # Create the list of input dicts +# n = 150 # Number of solves +# inputs = [[x, y] for x, y in zip(np.linspace(0.45, 0.6, n), np.linspace(0.45, 0.6, n))] +# +# t1 = time.time() +# for i in range(n): +# k = model.simulate(t_eval=t_eval, inputs=inputs[i]) +# t2 = time.time() +# print(f"Time Evaluate:{t2-t1}") +# +# t1 = time.time() +# for i in range(n): +# k = model.simulateS1(t_eval=t_eval, inputs=inputs[i]) +# t2 = time.time() +# print(f"Time EvaluateS1:{t2-t1}") From e2816bd7c1ca66f27d3fe341c7d92c2733439829 Mon Sep 17 00:00:00 2001 From: Brady Planden Date: Thu, 8 Aug 2024 10:54:45 +0100 Subject: [PATCH 2/7] Update problem.evaluate failure object type, add IDAKLU logic to BaseProblem, add / update tests --- examples/scripts/changing_solvers.py | 1 + pybop/problems/base_problem.py | 18 +++ pybop/problems/fitting_problem.py | 6 +- .../integration/test_spm_parameterisations.py | 107 ++++++++++-------- tests/unit/test_solvers.py | 75 ++++++++++++ 5 files changed, 158 insertions(+), 49 deletions(-) create mode 100644 tests/unit/test_solvers.py diff --git a/examples/scripts/changing_solvers.py b/examples/scripts/changing_solvers.py index b09df3222..bc0b1b191 100644 --- a/examples/scripts/changing_solvers.py +++ b/examples/scripts/changing_solvers.py @@ -16,6 +16,7 @@ ] for solver in solvers: + model.build() model.solver = solver t1 = time.time() model.predict(t_eval=np.linspace(0, 1, 100)) diff --git a/pybop/problems/base_problem.py b/pybop/problems/base_problem.py index 216ce092f..ac9c2d4b6 100644 --- a/pybop/problems/base_problem.py +++ b/pybop/problems/base_problem.py @@ -1,5 +1,8 @@ from typing import Optional +import numpy as np +from pybamm import IDAKLUSolver + from pybop import BaseModel, Dataset, Parameter, Parameters from pybop.parameters.parameter import Inputs @@ -68,6 +71,21 @@ def __init__( self._time_data = None self._target = None self.verbose = False + self.failure_output = np.asarray([np.inf]) + + # If model.solver is IDAKLU, set output vars for improved performance + self.output_vars = tuple(self.signal + self.additional_variables) + if self._model is not None and isinstance(self._model.solver, IDAKLUSolver): + self._solver_copy = self._model.solver.copy() + self._model.solver = IDAKLUSolver( + atol=self._solver_copy.atol, + rtol=self._solver_copy.rtol, + root_method=self._solver_copy.root_method, + root_tol=self._solver_copy.root_tol, + extrap_tol=self._solver_copy.extrap_tol, + options=self._solver_copy._options, # noqa: SLF001 + output_variables=self.output_vars, + ) @property def n_parameters(self): diff --git a/pybop/problems/fitting_problem.py b/pybop/problems/fitting_problem.py index f3bb10232..91725741a 100644 --- a/pybop/problems/fitting_problem.py +++ b/pybop/problems/fitting_problem.py @@ -107,7 +107,7 @@ def evaluate( except Exception as e: if self.verbose: print(f"Simulation error: {e}") - return {signal: [np.inf] for signal in self.signal} + return {signal: self.failure_output for signal in self.signal} return { signal: sol[signal].data @@ -137,7 +137,9 @@ def evaluateS1(self, inputs: Inputs): ) except Exception as e: print(f"Error: {e}") - return {signal: [np.inf] for signal in self.signal}, [np.inf] + return { + signal: self.failure_output for signal in self.signal + }, self.failure_output y = {signal: sol[signal].data for signal in self.signal} diff --git a/tests/integration/test_spm_parameterisations.py b/tests/integration/test_spm_parameterisations.py index c441c2b25..ace4684e5 100644 --- a/tests/integration/test_spm_parameterisations.py +++ b/tests/integration/test_spm_parameterisations.py @@ -1,4 +1,5 @@ import numpy as np +import pybamm import pytest import pybop @@ -58,14 +59,29 @@ def init_soc(self, request): pybop.MAP, ] ) - def cost_class(self, request): + def cost(self, request): return request.param def noise(self, sigma, values): return np.random.normal(0, sigma, values) + @pytest.fixture( + params=[ + pybop.SciPyDifferentialEvolution, + pybop.AdamW, + pybop.CMAES, + pybop.CuckooSearch, + pybop.IRPropMin, + pybop.NelderMead, + pybop.SNES, + pybop.XNES, + ] + ) + def optimiser(self, request): + return request.param + @pytest.fixture - def spm_costs(self, model, parameters, cost_class, init_soc): + def optim(self, optimiser, model, parameters, cost, init_soc): # Form dataset solution = self.get_data(model, init_soc) dataset = pybop.Dataset( @@ -77,65 +93,62 @@ def spm_costs(self, model, parameters, cost_class, init_soc): } ) - # Define the cost to optimise + # IDAKLU Solver for Gradient-based optimisers + if optimiser in [pybop.AdamW, pybop.IRPropMin]: + model.solver = pybamm.IDAKLUSolver() + + # Define the problem problem = pybop.FittingProblem(model, parameters, dataset) - if cost_class in [pybop.GaussianLogLikelihoodKnownSigma]: - return cost_class(problem, sigma0=self.sigma0) - elif cost_class in [pybop.GaussianLogLikelihood]: - return cost_class(problem, sigma0=self.sigma0 * 4) # Initial sigma0 guess - elif cost_class in [pybop.MAP]: - return cost_class( + + # Cost + if cost in [pybop.GaussianLogLikelihoodKnownSigma]: + cost = cost(problem, sigma0=self.sigma0) + elif cost in [pybop.GaussianLogLikelihood]: + cost = cost(problem, sigma0=self.sigma0 * 4) # Initial sigma0 guess + elif cost in [pybop.MAP]: + cost = cost( problem, pybop.GaussianLogLikelihoodKnownSigma, sigma0=self.sigma0 ) - elif cost_class in [pybop.SumofPower, pybop.Minkowski]: - return cost_class(problem, p=2) + elif cost in [pybop.SumofPower, pybop.Minkowski]: + cost = cost(problem, p=2) else: - return cost_class(problem) + cost = cost(problem) - @pytest.mark.parametrize( - "optimiser", - [ - pybop.SciPyDifferentialEvolution, - pybop.AdamW, - pybop.CMAES, - pybop.CuckooSearch, - pybop.IRPropMin, - pybop.NelderMead, - pybop.SNES, - pybop.XNES, - ], - ) - @pytest.mark.integration - def test_spm_optimisers(self, optimiser, spm_costs): - x0 = spm_costs.parameters.initial_value() common_args = { - "cost": spm_costs, + "cost": cost, "max_iterations": 250, "absolute_tolerance": 1e-6, "max_unchanged_iterations": 55, } - # Add sigma0 to ground truth for GaussianLogLikelihood - if isinstance(spm_costs, pybop.GaussianLogLikelihood): - self.ground_truth = np.concatenate( - (self.ground_truth, np.asarray([self.sigma0])) - ) - if isinstance(spm_costs, pybop.MAP): - for i in spm_costs.parameters.keys(): - spm_costs.parameters[i].prior = pybop.Uniform( + if isinstance(cost, pybop.MAP): + for i in cost.parameters.keys(): + cost.parameters[i].prior = pybop.Uniform( 0.2, 2.0 ) # Increase range to avoid prior == np.inf - # Set sigma0 and create optimiser - sigma0 = 0.05 if isinstance(spm_costs, pybop.MAP) else None + # Set sigma0 and create optimiser + sigma0 = 0.05 if isinstance(cost, pybop.MAP) else None optim = optimiser(sigma0=sigma0, **common_args) # AdamW will use lowest sigma0 for learning rate, so allow more iterations if issubclass(optimiser, (pybop.AdamW, pybop.IRPropMin)) and isinstance( - spm_costs, pybop.GaussianLogLikelihood + cost, pybop.GaussianLogLikelihood ): common_args["max_unchanged_iterations"] = 75 optim = optimiser(**common_args) + return optim + + @pytest.mark.integration + def test_spm_optimisers(self, optim): + x0 = optim.parameters.initial_value() + + # Add sigma0 to ground truth for GaussianLogLikelihood + if isinstance(optim.cost, pybop.GaussianLogLikelihood): + self.ground_truth = np.concatenate( + (self.ground_truth, np.asarray([self.sigma0])) + ) + initial_cost = optim.cost(x0) x, final_cost = optim.run() @@ -143,7 +156,7 @@ def test_spm_optimisers(self, optimiser, spm_costs): if np.allclose(x0, self.ground_truth, atol=1e-5): raise AssertionError("Initial guess is too close to ground truth") - if isinstance(spm_costs, pybop.GaussianLogLikelihood): + if isinstance(optim.cost, pybop.GaussianLogLikelihood): np.testing.assert_allclose(x, self.ground_truth, atol=1.5e-2) np.testing.assert_allclose(x[-1], self.sigma0, atol=5e-4) else: @@ -155,7 +168,7 @@ def test_spm_optimisers(self, optimiser, spm_costs): np.testing.assert_allclose(x, self.ground_truth, atol=1.5e-2) @pytest.fixture - def spm_two_signal_cost(self, parameters, model, cost_class): + def spm_two_signal_cost(self, parameters, model, cost): # Form dataset solution = self.get_data(model, 0.5) dataset = pybop.Dataset( @@ -175,14 +188,14 @@ def spm_two_signal_cost(self, parameters, model, cost_class): signal = ["Voltage [V]", "Bulk open-circuit voltage [V]"] problem = pybop.FittingProblem(model, parameters, dataset, signal=signal) - if cost_class in [pybop.GaussianLogLikelihoodKnownSigma]: - return cost_class(problem, sigma0=self.sigma0) - elif cost_class in [pybop.MAP]: - return cost_class( + if cost in [pybop.GaussianLogLikelihoodKnownSigma]: + return cost(problem, sigma0=self.sigma0) + elif cost in [pybop.MAP]: + return cost( problem, pybop.GaussianLogLikelihoodKnownSigma, sigma0=self.sigma0 ) else: - return cost_class(problem) + return cost(problem) @pytest.mark.parametrize( "multi_optimiser", diff --git a/tests/unit/test_solvers.py b/tests/unit/test_solvers.py new file mode 100644 index 000000000..3fe318378 --- /dev/null +++ b/tests/unit/test_solvers.py @@ -0,0 +1,75 @@ +import numpy as np +import pybamm +import pytest + +import pybop + + +class TestSolvers: + """ + A class to test the forward model solver interface + """ + + @pytest.fixture( + params=[ + pybamm.IDAKLUSolver(atol=1e-4, rtol=1e-4), + pybamm.CasadiSolver(atol=1e-4, rtol=1e-4, mode="safe"), + pybamm.CasadiSolver(atol=1e-4, rtol=1e-4, mode="fast with events"), + ] + ) + def solver(self, request): + solver = request.param + return solver.copy() + + @pytest.fixture + def model(self, solver): + parameter_set = pybop.ParameterSet.pybamm("Marquis2019") + model = pybop.lithium_ion.SPM(parameter_set=parameter_set, solver=solver) + return model + + @pytest.mark.unit + def test_solvers_with_model_predict(self, model, solver): + assert model.solver == solver + assert model.solver.atol == 1e-4 + assert model.solver.rtol == 1e-4 + + # Ensure solver is functional + sol = model.predict(t_eval=np.linspace(0, 1, 100)) + assert np.isfinite(sol["Voltage [V]"].data).all() + + signals = ["Voltage [V]", "Bulk open-circuit voltage [V]"] + additional_vars = [ + "Maximum negative particle concentration", + "Positive electrode volume-averaged concentration [mol.m-3]", + ] + + parameters = pybop.Parameters( + pybop.Parameter( + "Negative electrode conductivity [S.m-1]", prior=pybop.Uniform(0.1, 100) + ) + ) + dataset = pybop.Dataset( + { + "Time [s]": sol["Time [s]"].data, + "Current function [A]": sol["Current [A]"].data, + "Voltage [V]": sol["Voltage [V]"].data, + "Bulk open-circuit voltage [V]": sol[ + "Bulk open-circuit voltage [V]" + ].data, + } + ) + problem = pybop.FittingProblem( + model, + parameters=parameters, + dataset=dataset, + signal=signals, + additional_variables=additional_vars, + ) + + y = problem.evaluate(inputs={"Negative electrode conductivity [S.m-1]": 10}) + + for signal in signals: + assert np.isfinite(y[signal].data).all() + + if isinstance(model.solver, pybamm.IDAKLUSolver): + assert model.solver.output_variables is not None From 1cd175a9f66a47d0f7f80ee7093e9bc9ebe5e53a Mon Sep 17 00:00:00 2001 From: Brady Planden Date: Thu, 8 Aug 2024 11:59:01 +0100 Subject: [PATCH 3/7] Update solver examples, property --- examples/notebooks/solver_selection.ipynb | 227 +++++----------------- examples/scripts/changing_solvers.py | 84 -------- examples/scripts/selecting_a_solver.py | 60 ++++++ pybop/models/base_model.py | 4 + 4 files changed, 115 insertions(+), 260 deletions(-) delete mode 100644 examples/scripts/changing_solvers.py create mode 100644 examples/scripts/selecting_a_solver.py diff --git a/examples/notebooks/solver_selection.ipynb b/examples/notebooks/solver_selection.ipynb index 0555526d3..fac6ed96b 100644 --- a/examples/notebooks/solver_selection.ipynb +++ b/examples/notebooks/solver_selection.ipynb @@ -39,28 +39,8 @@ ], "source": [ "%pip install --upgrade pip ipywidgets -q\n", - "%pip install pybop -q" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "jAvD5fk104p0" - }, - "source": [ - "### Importing Libraries\n", + "%pip install pybop -q\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 time\n", "\n", "import numpy as np\n", @@ -69,35 +49,15 @@ "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):" + "### Setting up the model, and problem\n", + ".\n", + "We start by constructing a pybop model, and a synthetic dataset needed for the pybop problem we will be using for the solver benchmarking " ] }, { @@ -106,95 +66,25 @@ "metadata": {}, "outputs": [], "source": [ + "# Model\n", "parameter_set = pybop.ParameterSet.pybamm(\"Chen2020\")\n", - "model = pybop.lithium_ion.SPMe(parameter_set=parameter_set)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "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", + "model = pybop.lithium_ion.SPM(parameter_set=parameter_set)\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\n", - "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", + "# Synthetic data\n", + "t_eval = np.arange(0, 900, 2)\n", + "values = model.predict(t_eval=t_eval)\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\n", "dataset = pybop.Dataset(\n", " {\n", " \"Time [s]\": t_eval,\n", " \"Current function [A]\": values[\"Current [A]\"].data,\n", - " \"Voltage [V]\": corrupt_values,\n", + " \"Voltage [V]\": values[\"Voltage [V]\"].data,\n", " }\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ffS3CF_704qA" - }, - "source": [ - "### Defining Parameters to Estimate\n", + ")\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", + "# Parameters\n", + "parameters = pybop.Parameters(\n", " pybop.Parameter(\n", " \"Negative electrode active material volume fraction\",\n", " prior=pybop.Gaussian(0.6, 0.02),\n", @@ -205,7 +95,7 @@ " prior=pybop.Gaussian(0.48, 0.02),\n", " bounds=[0.4, 0.7],\n", " ),\n", - "]" + ")" ] }, { @@ -214,9 +104,9 @@ "id": "n4OHa-aF04qA" }, "source": [ - "### Setting up the Optimisation Problem\n", + "### Defining the solvers for benchmarking\n", "\n", - "With the datasets and parameters defined, we can set up the optimisation problem, its cost function, and the optimiser." + "Now that we have set up the majority of the pybop objects, we construct the solvers we want to benchmark on the given model, and applied current." ] }, { @@ -225,21 +115,19 @@ "metadata": {}, "outputs": [], "source": [ - "solvers = dict(\n", - " IDAKLU=pybamm.IDAKLUSolver(atol=1e-6, rtol=1e-6),\n", - " Casadi_safe=pybamm.CasadiSolver(atol=1e-6, rtol=1e-6, mode=\"safe\"),\n", - " Casadi_fast=pybamm.CasadiSolver(atol=1e-6, rtol=1e-6, mode=\"fast\"),\n", - " Casadi_fast_with_events=pybamm.CasadiSolver(\n", - " atol=1e-6, rtol=1e-6, mode=\"fast with events\"\n", - " ),\n", - ")" + "solvers = [\n", + " pybamm.IDAKLUSolver(atol=1e-6, rtol=1e-6),\n", + " pybamm.CasadiSolver(atol=1e-6, rtol=1e-6, mode=\"safe\"),\n", + " pybamm.CasadiSolver(atol=1e-6, rtol=1e-6, mode=\"fast\"),\n", + " pybamm.CasadiSolver(atol=1e-6, rtol=1e-6, mode=\"fast with events\"),\n", + "]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Let's setup a toy-problem to showcase the diferences. We run the forward model `n` times, for a variety of input values," + "Next, we construct a range of inputs for the parameters defined above, and select the number of instances in that range to benchmark on. For more statistically repeatable results, increase the variable `n` below." ] }, { @@ -248,15 +136,15 @@ "metadata": {}, "outputs": [], "source": [ - "n = 25 # Number of solves\n", - "inputs = [[x, y] for x, y in zip(np.linspace(0.45, 0.6, n), np.linspace(0.45, 0.6, n))]" + "n = 50 # Number of solves\n", + "inputs = list(zip(np.linspace(0.45, 0.6, n), np.linspace(0.45, 0.6, n)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Next, let's call the non-gradient based cost evaluate for each of the solvers and time the correseponding outcomes," + "Next, let's benchmark the solvers without sensitivities. This provides a reference for the non-gradient based pybop optimisers and samplers. " ] }, { @@ -268,32 +156,31 @@ "name": "stdout", "output_type": "stream", "text": [ - "Cost Evaluate for IDA KLU solver: 3.354677200317383\n", - "Cost Evaluate for CasADi solver with 'safe' mode: 2.0228309631347656\n", - "Cost Evaluate for CasADi solver with 'fast' mode: 1.9543209075927734\n", - "Cost Evaluate for CasADi solver with 'fast with events' mode: 2.0191171169281006\n" + "Time Evaluate IDA KLU solver: 0.521\n", + "Time Evaluate CasADi solver with 'safe' mode: 0.464\n", + "Time Evaluate CasADi solver with 'fast' mode: 0.458\n", + "Time Evaluate CasADi solver with 'fast with events' mode: 0.451\n" ] } ], "source": [ - "parameter_set = pybop.ParameterSet.pybamm(\"Chen2020\")\n", - "for solver in solvers.values():\n", - " model = pybop.lithium_ion.SPMe(parameter_set=parameter_set, solver=solver)\n", + "for solver in solvers:\n", + " model.solver = solver\n", " problem = pybop.FittingProblem(model, parameters, dataset)\n", - " cost = pybop.SumSquaredError(problem)\n", "\n", - " # Timing\n", - " t1 = time.time()\n", - " for i in range(n):\n", - " cost.evaluate(inputs[i])\n", - " print(f\"Cost Evaluate for {solver.name}: {time.time()-t1}\")" + " start_time = time.time()\n", + " for input_values in inputs:\n", + " problem.evaluate(inputs=input_values)\n", + " print(f\"Time Evaluate {solver.name}: {time.time() - start_time:.3f}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Great, so the Casadi performance appears to be XX times better! That is, atleast for the non-gradient solutions. Next, let's repeat the same toy problem, but for the gradient-based cost evaluation," + "Excellent, given the above results, we know which solver we should select for optimisation on your machine, i.e. the one with the smallest time. \n", + "\n", + "Next, let's repeat the same toy problem, but for the gradient-based cost evaluation," ] }, { @@ -305,41 +192,29 @@ "name": "stdout", "output_type": "stream", "text": [ - "Cost Evaluate for IDA KLU solver: 1.4089879989624023\n", - "Cost Evaluate for CasADi solver with 'safe' mode: 3.617908000946045\n", - "Cost Evaluate for CasADi solver with 'fast' mode: 3.5603108406066895\n", - "Cost Evaluate for CasADi solver with 'fast with events' mode: 3.537614107131958\n" + "Time EvaluateS1 IDA KLU solver: 1.478\n", + "Time EvaluateS1 CasADi solver with 'safe' mode: 2.836\n", + "Time EvaluateS1 CasADi solver with 'fast' mode: 2.699\n", + "Time EvaluateS1 CasADi solver with 'fast with events' mode: 2.714\n" ] } ], "source": [ - "parameter_set = pybop.ParameterSet.pybamm(\"Chen2020\")\n", - "for solver in solvers.values():\n", - " model = pybop.lithium_ion.SPMe(parameter_set=parameter_set, solver=solver)\n", + "for solver in solvers:\n", + " model.solver = solver\n", " problem = pybop.FittingProblem(model, parameters, dataset)\n", - " cost = pybop.SumSquaredError(problem)\n", "\n", - " # Timing\n", - " t1 = time.time()\n", - " for i in range(n):\n", - " cost.evaluateS1(inputs[i])\n", - " print(f\"Cost Evaluate for {solver.name}: {time.time()-t1}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now this is interesting, so we have performance variation between the Casadi solvers and the IDAKLU. Given this result, it appears to the Casadi solver is most benefical for non-gradient optimisers, while IDAKLU has a large improve for gradient-based optimisers." + " start_time = time.time()\n", + " for input_values in inputs:\n", + " problem.evaluateS1(inputs=input_values)\n", + " print(f\"Time EvaluateS1 {solver.name}: {time.time() - start_time:.3f}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "- Gradient compared to non-gradient methods\n", - "- No GPU methods in this notebook\n", - "- Import data or use model.predict?" + "Now we have the relevant information for the gradient-based optimisers. Likewise to the above results, we should select the solver with the smallest time." ] } ], diff --git a/examples/scripts/changing_solvers.py b/examples/scripts/changing_solvers.py deleted file mode 100644 index bc0b1b191..000000000 --- a/examples/scripts/changing_solvers.py +++ /dev/null @@ -1,84 +0,0 @@ -import time - -import numpy as np -import pybamm - -import pybop - -# Parameter set and model definition -parameter_set = pybop.ParameterSet.pybamm("Chen2020") -solver = pybamm.IDAKLUSolver(atol=1e-6, rtol=1e-6) -model = pybop.lithium_ion.SPMe(parameter_set=parameter_set, solver=solver) - -solvers = [ - pybamm.IDAKLUSolver(atol=1e-6, rtol=1e-6), - pybamm.CasadiSolver(atol=1e-6, rtol=1e-6), -] - -for solver in solvers: - model.build() - model.solver = solver - t1 = time.time() - model.predict(t_eval=np.linspace(0, 1, 100)) - print(time.time() - t1) - -# # Fitting parameters -# parameters = pybop.Parameters( -# pybop.Parameter( -# "Negative electrode active material volume fraction", -# prior=pybop.Gaussian(0.68, 0.05), -# ), -# pybop.Parameter( -# "Positive electrode active material volume fraction", -# prior=pybop.Gaussian(0.58, 0.05), -# ), -# ) -# -# # Generate data -# sigma = 0.001 -# t_eval = np.linspace(0, 900, 1800) -# values = model.predict(t_eval=t_eval) -# corrupt_values = values["Voltage [V]"].data + np.random.normal(0, sigma, len(t_eval)) -# -# # Form dataset -# dataset = pybop.Dataset( -# { -# "Time [s]": t_eval, -# "Current function [A]": values["Current [A]"].data, -# "Voltage [V]": corrupt_values, -# } -# ) -# -# # Generate problem, cost function, and optimisation class -# problem = pybop.FittingProblem(model, parameters, dataset) -# cost = pybop.SumSquaredError(problem) -# # optim = pybop.Adam( -# # cost, -# # sigma0=0.0001, -# # verbose=True, -# # min_iterations=125, -# # max_iterations=125, -# # ) -# -# # Run optimisation -# # t1 = time.time() -# # x, final_cost = optim.run() -# # t2 = time.time() -# # print(f"Time:{t2-t1}") -# # print("Estimated parameters:", x) -# -# # Create the list of input dicts -# n = 150 # Number of solves -# inputs = [[x, y] for x, y in zip(np.linspace(0.45, 0.6, n), np.linspace(0.45, 0.6, n))] -# -# t1 = time.time() -# for i in range(n): -# k = model.simulate(t_eval=t_eval, inputs=inputs[i]) -# t2 = time.time() -# print(f"Time Evaluate:{t2-t1}") -# -# t1 = time.time() -# for i in range(n): -# k = model.simulateS1(t_eval=t_eval, inputs=inputs[i]) -# t2 = time.time() -# print(f"Time EvaluateS1:{t2-t1}") diff --git a/examples/scripts/selecting_a_solver.py b/examples/scripts/selecting_a_solver.py new file mode 100644 index 000000000..248d943f5 --- /dev/null +++ b/examples/scripts/selecting_a_solver.py @@ -0,0 +1,60 @@ +import time + +import numpy as np +import pybamm + +import pybop + +# Parameter set and model definition +parameter_set = pybop.ParameterSet.pybamm("Chen2020") +model = pybop.lithium_ion.SPM(parameter_set=parameter_set) + +solvers = [ + pybamm.IDAKLUSolver(atol=1e-6, rtol=1e-6), + pybamm.CasadiSolver(mode="safe", atol=1e-6, rtol=1e-6), + pybamm.CasadiSolver(mode="fast with events", atol=1e-6, rtol=1e-6), +] + +# Fitting parameters +parameters = pybop.Parameters( + pybop.Parameter( + "Negative electrode active material volume fraction", initial_value=0.55 + ), + pybop.Parameter( + "Positive electrode active material volume fraction", initial_value=0.55 + ), +) + +# Define test protocol and generate data +experiment = pybop.Experiment([("Discharge at 0.5C for 10 minutes (3 second period)")]) +values = model.predict( + initial_state={"Initial open-circuit voltage [V]": 4.2}, experiment=experiment +) + +# Form dataset +dataset = pybop.Dataset( + { + "Time [s]": values["Time [s]"].data, + "Current function [A]": values["Current [A]"].data, + "Voltage [V]": values["Voltage [V]"].data, + } +) + +# Create the list of input dicts +n = 150 # Number of solves +inputs = list(zip(np.linspace(0.45, 0.6, n), np.linspace(0.45, 0.6, n))) + +# Iterate over the solvers and print benchmarks +for solver in solvers: + model.solver = solver + problem = pybop.FittingProblem(model, parameters, dataset) + + start_time = time.time() + for input_values in inputs: + problem.evaluate(inputs=input_values) + print(f"Time Evaluate {solver.name}: {time.time() - start_time:.3f}") + + start_time = time.time() + for input_values in inputs: + problem.evaluateS1(inputs=input_values) + print(f"Time EvaluateS1 {solver.name}: {time.time() - start_time:.3f}") diff --git a/pybop/models/base_model.py b/pybop/models/base_model.py index fe12c400f..cd091ec83 100644 --- a/pybop/models/base_model.py +++ b/pybop/models/base_model.py @@ -803,3 +803,7 @@ def spatial_methods(self): @property def solver(self): return self._solver + + @solver.setter + def solver(self, solver): + self._solver = solver.copy() if solver is not None else None From a921c8f88d4dd33c13ada4b87f2e70d70cace408 Mon Sep 17 00:00:00 2001 From: Brady Planden Date: Thu, 8 Aug 2024 12:42:48 +0100 Subject: [PATCH 4/7] Add changelog entry, tidy. --- CHANGELOG.md | 1 + tests/integration/test_spm_parameterisations.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5962d1db8..2bf2e45ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Features +- [#450](https://github.com/pybop-team/PyBOP/pull/450) - Adds support for IDAKLU with output variables, and corresponding examples, tests. - [#435](https://github.com/pybop-team/PyBOP/pull/435) - Adds SLF001 linting for private members. - [#418](https://github.com/pybop-team/PyBOP/issues/418) - Wraps the `get_parameter_info` method from PyBaMM to get a dictionary of parameter names and types. - [#413](https://github.com/pybop-team/PyBOP/pull/413) - Adds `DesignCost` functionality to `WeightedCost` class with additional tests. diff --git a/tests/integration/test_spm_parameterisations.py b/tests/integration/test_spm_parameterisations.py index ace4684e5..2323f0ddc 100644 --- a/tests/integration/test_spm_parameterisations.py +++ b/tests/integration/test_spm_parameterisations.py @@ -100,7 +100,7 @@ def optim(self, optimiser, model, parameters, cost, init_soc): # Define the problem problem = pybop.FittingProblem(model, parameters, dataset) - # Cost + # Construct the cost if cost in [pybop.GaussianLogLikelihoodKnownSigma]: cost = cost(problem, sigma0=self.sigma0) elif cost in [pybop.GaussianLogLikelihood]: @@ -114,6 +114,7 @@ def optim(self, optimiser, model, parameters, cost, init_soc): else: cost = cost(problem) + # Construct optimisation object common_args = { "cost": cost, "max_iterations": 250, @@ -126,7 +127,8 @@ def optim(self, optimiser, model, parameters, cost, init_soc): cost.parameters[i].prior = pybop.Uniform( 0.2, 2.0 ) # Increase range to avoid prior == np.inf - # Set sigma0 and create optimiser + + # Set sigma0 and create optimiser sigma0 = 0.05 if isinstance(cost, pybop.MAP) else None optim = optimiser(sigma0=sigma0, **common_args) From e07118a02494e632e527c61ee166dfbc334f19f5 Mon Sep 17 00:00:00 2001 From: Brady Planden <55357039+BradyPlanden@users.noreply.github.com> Date: Fri, 9 Aug 2024 15:50:52 +0100 Subject: [PATCH 5/7] Apply suggestions from code review Co-authored-by: NicolaCourtier <45851982+NicolaCourtier@users.noreply.github.com> --- examples/notebooks/solver_selection.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/notebooks/solver_selection.ipynb b/examples/notebooks/solver_selection.ipynb index fac6ed96b..212d52b24 100644 --- a/examples/notebooks/solver_selection.ipynb +++ b/examples/notebooks/solver_selection.ipynb @@ -56,7 +56,7 @@ }, "source": [ "### Setting up the model, and problem\n", - ".\n", + "\n", "We start by constructing a pybop model, and a synthetic dataset needed for the pybop problem we will be using for the solver benchmarking " ] }, From debf890349ea35b53c4bd3be37969928bac1f4ec Mon Sep 17 00:00:00 2001 From: Brady Planden Date: Mon, 12 Aug 2024 16:26:51 +0100 Subject: [PATCH 6/7] apply fixes from code review --- .../equivalent_circuit_identification.ipynb | 2 +- .../notebooks/multi_model_identification.ipynb | 4 ++-- .../multi_optimiser_identification.ipynb | 2 +- examples/notebooks/optimiser_calibration.ipynb | 2 +- .../notebooks/pouch_cell_identification.ipynb | 2 +- examples/notebooks/solver_selection.ipynb | 18 +++++++++++++++++- examples/notebooks/spm_AdamW.ipynb | 2 +- examples/notebooks/spm_electrode_design.ipynb | 2 +- 8 files changed, 25 insertions(+), 9 deletions(-) diff --git a/examples/notebooks/equivalent_circuit_identification.ipynb b/examples/notebooks/equivalent_circuit_identification.ipynb index 6aa0d8907..1e7b10b92 100644 --- a/examples/notebooks/equivalent_circuit_identification.ipynb +++ b/examples/notebooks/equivalent_circuit_identification.ipynb @@ -11,7 +11,7 @@ "\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:" + "Before we begin, we need to ensure that we have all the necessary tools. We will install PyBOP and upgrade dependencies:" ] }, { diff --git a/examples/notebooks/multi_model_identification.ipynb b/examples/notebooks/multi_model_identification.ipynb index 43fc92e63..e9b3e9b53 100644 --- a/examples/notebooks/multi_model_identification.ipynb +++ b/examples/notebooks/multi_model_identification.ipynb @@ -12,7 +12,7 @@ "\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:" + "Before we begin, we need to ensure that we have all the necessary tools. We will install PyBOP and upgrade dependencies:" ] }, { @@ -147,7 +147,7 @@ "metadata": {}, "outputs": [], "source": [ - "synth_model.build(dataset, initial_state=initial_state)\n", + "synth_model.build(dataset=dataset, initial_state=initial_state)\n", "synth_model.signal = [\"Voltage [V]\"]\n", "values = synth_model.simulate(t_eval=t_eval, inputs={})" ] diff --git a/examples/notebooks/multi_optimiser_identification.ipynb b/examples/notebooks/multi_optimiser_identification.ipynb index 2131c2b38..7067f6bf0 100644 --- a/examples/notebooks/multi_optimiser_identification.ipynb +++ b/examples/notebooks/multi_optimiser_identification.ipynb @@ -12,7 +12,7 @@ "\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:" + "Before we begin, we need to ensure that we have all the necessary tools. We will install PyBOP and upgrade dependencies:" ] }, { diff --git a/examples/notebooks/optimiser_calibration.ipynb b/examples/notebooks/optimiser_calibration.ipynb index 2b0fd3348..d1c67b08e 100644 --- a/examples/notebooks/optimiser_calibration.ipynb +++ b/examples/notebooks/optimiser_calibration.ipynb @@ -12,7 +12,7 @@ "\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:" + "Before we begin, we need to ensure that we have all the necessary tools. We will install PyBOP and upgrade dependencies:" ] }, { diff --git a/examples/notebooks/pouch_cell_identification.ipynb b/examples/notebooks/pouch_cell_identification.ipynb index 146ee64bd..2f52ab825 100644 --- a/examples/notebooks/pouch_cell_identification.ipynb +++ b/examples/notebooks/pouch_cell_identification.ipynb @@ -12,7 +12,7 @@ "\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:" + "Before we begin, we need to ensure that we have all the necessary tools. We will install PyBOP and upgrade dependencies:" ] }, { diff --git a/examples/notebooks/solver_selection.ipynb b/examples/notebooks/solver_selection.ipynb index 212d52b24..a109b24f6 100644 --- a/examples/notebooks/solver_selection.ipynb +++ b/examples/notebooks/solver_selection.ipynb @@ -14,7 +14,7 @@ "\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:" + "Before we begin, we need to ensure that we have all the necessary tools. We will install PyBOP and upgrade dependencies:" ] }, { @@ -49,6 +49,22 @@ "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": { diff --git a/examples/notebooks/spm_AdamW.ipynb b/examples/notebooks/spm_AdamW.ipynb index 9d683d38a..13c61a2cc 100644 --- a/examples/notebooks/spm_AdamW.ipynb +++ b/examples/notebooks/spm_AdamW.ipynb @@ -16,7 +16,7 @@ "\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:" + "Before we begin, we need to ensure that we have all the necessary tools. We will install PyBOP and upgrade dependencies:" ] }, { diff --git a/examples/notebooks/spm_electrode_design.ipynb b/examples/notebooks/spm_electrode_design.ipynb index 6f07b487b..e41cd161a 100644 --- a/examples/notebooks/spm_electrode_design.ipynb +++ b/examples/notebooks/spm_electrode_design.ipynb @@ -18,7 +18,7 @@ "\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:" + "Before we begin, we need to ensure that we have all the necessary tools. We will install PyBOP and upgrade dependencies:" ] }, { From 65c1c6c5aefe016eaeafe84e8d7f87f8dc10332c Mon Sep 17 00:00:00 2001 From: Brady Planden Date: Mon, 12 Aug 2024 17:08:20 +0100 Subject: [PATCH 7/7] examples: updt state of solver_selection.ipynbgi --- examples/notebooks/solver_selection.ipynb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/notebooks/solver_selection.ipynb b/examples/notebooks/solver_selection.ipynb index a109b24f6..81db4b6c9 100644 --- a/examples/notebooks/solver_selection.ipynb +++ b/examples/notebooks/solver_selection.ipynb @@ -172,10 +172,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "Time Evaluate IDA KLU solver: 0.521\n", + "Time Evaluate IDA KLU solver: 0.477\n", "Time Evaluate CasADi solver with 'safe' mode: 0.464\n", - "Time Evaluate CasADi solver with 'fast' mode: 0.458\n", - "Time Evaluate CasADi solver with 'fast with events' mode: 0.451\n" + "Time Evaluate CasADi solver with 'fast' mode: 0.446\n", + "Time Evaluate CasADi solver with 'fast with events' mode: 0.443\n" ] } ], @@ -208,10 +208,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "Time EvaluateS1 IDA KLU solver: 1.478\n", - "Time EvaluateS1 CasADi solver with 'safe' mode: 2.836\n", - "Time EvaluateS1 CasADi solver with 'fast' mode: 2.699\n", - "Time EvaluateS1 CasADi solver with 'fast with events' mode: 2.714\n" + "Time EvaluateS1 IDA KLU solver: 0.997\n", + "Time EvaluateS1 CasADi solver with 'safe' mode: 2.743\n", + "Time EvaluateS1 CasADi solver with 'fast' mode: 2.689\n", + "Time EvaluateS1 CasADi solver with 'fast with events' mode: 2.688\n" ] } ],