Skip to content

Commit

Permalink
Merge pull request #393 from pybop-team/fixes-ahead-v24.6
Browse files Browse the repository at this point in the history
General additions ahead of v24.6
  • Loading branch information
BradyPlanden committed Jul 8, 2024
2 parents 59e2ce1 + 50bd26d commit 64f635e
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 8 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
## Features

- [#319](https://github.com/pybop-team/PyBOP/pull/319/) - Adds `CuckooSearch` optimiser with corresponding tests.
- [#359](https://github.com/pybop-team/PyBOP/pull/359/) - Aligning Inputs between problem, observer and model.
- [#379](https://github.com/pybop-team/PyBOP/pull/379) - Adds model.simulateS1 to weekly benchmarks.
- [#174](https://github.com/pybop-team/PyBOP/issues/174) - Adds new logo and updates Readme for accessibility.
- [#316](https://github.com/pybop-team/PyBOP/pull/316) - Adds Adam with weight decay (AdamW) optimiser, adds depreciation warning for pints.Adam implementation.
- [#271](https://github.com/pybop-team/PyBOP/issues/271) - Aligns the output of the optimisers via a generalisation of Result class.
- [#315](https://github.com/pybop-team/PyBOP/pull/315) - Updates __init__ structure to remove circular import issues and minimises dependancy imports across codebase for faster PyBOP module import. Adds type-hints to BaseModel and refactors rebuild parameter variables.
- [#236](https://github.com/pybop-team/PyBOP/issues/236) - Restructures the optimiser classes, adds a new optimisation API through direct construction and keyword arguments, and fixes the setting of `max_iterations`, and `_minimising`. Introduces `pybop.BaseOptimiser`, `pybop.BasePintsOptimiser`, and `pybop.BaseSciPyOptimiser` classes.
- [#322](https://github.com/pybop-team/PyBOP/pull/322) - Add `Parameters` class to store and access multiple parameters in one object.
- [#321](https://github.com/pybop-team/PyBOP/pull/321) - Updates Prior classes with BaseClass, adds a `problem.sample_initial_conditions` method to improve stability of SciPy.Minimize optimiser.
- [#249](https://github.com/pybop-team/PyBOP/pull/249) - Add WeppnerHuggins model and GITT example.
- [#304](https://github.com/pybop-team/PyBOP/pull/304) - Decreases the testing suite completion time.
Expand All @@ -26,6 +28,7 @@

## Bug Fixes

- [#393](https://github.com/pybop-team/PyBOP/pull/393) - General integration test fixes. Adds UserWarning when using Plot2d with prior generated bounds.
- [#338](https://github.com/pybop-team/PyBOP/pull/338) - Fixes GaussianLogLikelihood class, adds integration tests, updates non-bounded parameter implementation by applying bounds from priors and `boundary_multiplier` argument. Bugfixes to CMAES construction.
- [#339](https://github.com/pybop-team/PyBOP/issues/339) - Updates the calculation of the cyclable lithium capacity in the spme_max_energy example.
- [#387](https://github.com/pybop-team/PyBOP/issues/387) - Adds keys to ParameterSet and updates ECM OCV check.
Expand All @@ -44,6 +47,13 @@
- [#270](https://github.com/pybop-team/PyBOP/pull/270) - Updates PR template.
- [#91](https://github.com/pybop-team/PyBOP/issues/91) - Adds a check on the number of parameters for CMAES and makes XNES the default optimiser.

## Breaking Changes

- [#322](https://github.com/pybop-team/PyBOP/pull/322) - Add `Parameters` class to store and access multiple parameters in one object (API change).
- [#285](https://github.com/pybop-team/PyBOP/pull/285) - Drop support for Python 3.8.
- [#251](https://github.com/pybop-team/PyBOP/pull/251) - Drop support for PyBaMM v23.5
- [#236](https://github.com/pybop-team/PyBOP/issues/236) - Restructures the optimiser classes (API change).

# [v24.3.1](https://github.com/pybop-team/PyBOP/tree/v24.3.1) - 2024-06-17

## Features
Expand Down
75 changes: 75 additions & 0 deletions examples/scripts/cuckoo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import numpy as np

import pybop

# Define model
parameter_set = pybop.ParameterSet.pybamm("Chen2020")
model = pybop.lithium_ion.SPM(parameter_set=parameter_set)

# Fitting parameters
parameters = pybop.Parameters(
pybop.Parameter(
"Negative electrode active material volume fraction",
prior=pybop.Gaussian(0.6, 0.05),
bounds=[0.4, 0.75],
initial_value=0.41,
true_value=0.7,
),
pybop.Parameter(
"Positive electrode active material volume fraction",
prior=pybop.Gaussian(0.48, 0.05),
bounds=[0.4, 0.75],
initial_value=0.41,
true_value=0.67,
),
)
init_soc = 0.7
experiment = pybop.Experiment(
[
(
"Discharge at 0.5C for 3 minutes (4 second period)",
"Charge at 0.5C for 3 minutes (4 second period)",
),
]
)
values = model.predict(
init_soc=init_soc, experiment=experiment, inputs=parameters.as_dict("true")
)

sigma = 0.002
corrupt_values = values["Voltage [V]"].data + np.random.normal(
0, sigma, len(values["Voltage [V]"].data)
)

# Form dataset
dataset = pybop.Dataset(
{
"Time [s]": values["Time [s]"].data,
"Current function [A]": values["Current [A]"].data,
"Voltage [V]": corrupt_values,
}
)

# Generate problem, cost function, and optimisation class
problem = pybop.FittingProblem(model, parameters, dataset, init_soc=init_soc)
cost = pybop.GaussianLogLikelihood(problem, sigma0=sigma * 4)
optim = pybop.Optimisation(
cost,
optimiser=pybop.CuckooSearch,
max_iterations=100,
)

x, final_cost = optim.run()
print("Estimated parameters:", x)

# Plot the timeseries output
pybop.quick_plot(problem, problem_inputs=x, title="Optimised Comparison")

# Plot convergence
pybop.plot_convergence(optim)

# Plot the parameter traces
pybop.plot_parameters(optim)

# Plot the cost landscape with optimisation path
pybop.plot2d(optim, steps=15)
2 changes: 1 addition & 1 deletion pybop/optimisers/_cuckoo.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class CuckooSearchImpl(PopulationBasedOptimiser):
https://doi.org/10.1016/j.chaos.2011.06.004.
"""

def __init__(self, x0, sigma0=0.01, boundaries=None, pa=0.25):
def __init__(self, x0, sigma0=0.05, boundaries=None, pa=0.25):
super().__init__(x0, sigma0, boundaries=boundaries)

# Problem dimensionality
Expand Down
12 changes: 11 additions & 1 deletion pybop/parameters/parameter.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import warnings
from collections import OrderedDict
from typing import Dict, List, Union

Expand Down Expand Up @@ -46,6 +47,7 @@ def __init__(
self.true_value = true_value
self.initial_value = initial_value
self.value = initial_value
self.applied_prior_bounds = False
self.set_bounds(bounds)
self.margin = 1e-4

Expand Down Expand Up @@ -153,6 +155,7 @@ def set_bounds(self, bounds=None, boundary_multiplier=6):
self.lower_bound = bounds[0]
self.upper_bound = bounds[1]
elif self.prior is not None:
self.applied_prior_bounds = True
self.lower_bound = self.prior.mean - boundary_multiplier * self.prior.sigma
self.upper_bound = self.prior.mean + boundary_multiplier * self.prior.sigma
bounds = [self.lower_bound, self.upper_bound]
Expand Down Expand Up @@ -417,7 +420,14 @@ def get_bounds_for_plotly(self):
bounds = np.empty((len(self), 2))

for i, param in enumerate(self.param.values()):
if param.bounds is not None:
if param.applied_prior_bounds:
warnings.warn(
"Bounds were created from prior distributions. "
"Please provide bounds for better plotting results.",
UserWarning,
stacklevel=2,
)
elif param.bounds is not None:
bounds[i] = param.bounds
else:
raise ValueError("All parameters require bounds for plotting.")
Expand Down
17 changes: 11 additions & 6 deletions tests/integration/test_spm_parameterisations.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ def parameters(self):
return pybop.Parameters(
pybop.Parameter(
"Negative electrode active material volume fraction",
prior=pybop.Uniform(0.4, 0.7),
bounds=[0.375, 0.725],
prior=pybop.Uniform(0.4, 0.75),
bounds=[0.375, 0.75],
),
pybop.Parameter(
"Positive electrode active material volume fraction",
prior=pybop.Uniform(0.4, 0.7),
prior=pybop.Uniform(0.4, 0.75),
# no bounds
),
)
Expand Down Expand Up @@ -100,21 +100,26 @@ def test_spm_optimisers(self, optimiser, spm_costs):
common_args = {
"cost": spm_costs,
"max_iterations": 250,
"absolute_tolerance": 1e-6,
}

# 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(
0.4, 2.0
) # Increase range to avoid prior == np.inf
# Set sigma0 and create optimiser
sigma0 = 0.006 if isinstance(spm_costs, pybop.MAP) else None
sigma0 = 0.05 if isinstance(spm_costs, pybop.MAP) else None
optim = optimiser(sigma0=sigma0, **common_args)

# Set max unchanged iterations for BasePintsOptimisers
if issubclass(optimiser, pybop.BasePintsOptimiser):
optim.set_max_unchanged_iterations(iterations=45, absolute_tolerance=1e-5)
optim.set_max_unchanged_iterations(iterations=55)

# AdamW will use lowest sigma0 for learning rate, so allow more iterations
if issubclass(optimiser, (pybop.AdamW, pybop.IRPropMin)) and isinstance(
Expand Down
24 changes: 24 additions & 0 deletions tests/unit/test_plots.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import warnings

import numpy as np
import pytest
from packaging import version
Expand Down Expand Up @@ -193,3 +195,25 @@ def test_plot2d_incorrect_number_of_parameters(self, model, dataset):
fitting_problem = pybop.FittingProblem(model, parameters, dataset)
cost = pybop.SumSquaredError(fitting_problem)
pybop.plot2d(cost)

@pytest.mark.unit
def test_plot2d_prior_bounds(self, model, dataset):
# Test with prior bounds
parameters = pybop.Parameters(
pybop.Parameter(
"Negative electrode active material volume fraction",
prior=pybop.Gaussian(0.68, 0.01),
),
pybop.Parameter(
"Positive electrode active material volume fraction",
prior=pybop.Gaussian(0.58, 0.01),
),
)
fitting_problem = pybop.FittingProblem(model, parameters, dataset)
cost = pybop.SumSquaredError(fitting_problem)
with pytest.warns(
UserWarning,
match="Bounds were created from prior distributions.",
):
warnings.simplefilter("always")
pybop.plot2d(cost)

0 comments on commit 64f635e

Please sign in to comment.