Skip to content

Commit

Permalink
fix: default cuckoo sigma0, updt integration test settings, flag for …
Browse files Browse the repository at this point in the history
…applied boundaries with plot2d catch
  • Loading branch information
BradyPlanden committed Jul 5, 2024
1 parent 59e2ce1 commit c417ced
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 8 deletions.
77 changes: 77 additions & 0 deletions examples/scripts/cuckoo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
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,
sigma0=None,
optimiser=pybop.CuckooSearch,
max_unchanged_iterations=55,
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
8 changes: 7 additions & 1 deletion pybop/parameters/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,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 +154,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 +419,11 @@ 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:
raise ValueError(
"Bounds were created from prior distributions. Please provide bounds for plotting."
)
elif param.bounds is not None:
bounds[i] = param.bounds
else:
raise ValueError("All parameters require bounds for plotting.")
Expand Down
15 changes: 9 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,24 @@ 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)
# 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
19 changes: 19 additions & 0 deletions tests/unit/test_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,22 @@ 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)

# Test with applied prior bounds
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),
bounds=[0.4, 0.7],
),
)
fitting_problem = pybop.FittingProblem(model, parameters, dataset)
cost = pybop.SumSquaredError(fitting_problem)
with pytest.raises(
ValueError, match="Bounds were created from prior distributions"
):
pybop.plot2d(cost)

0 comments on commit c417ced

Please sign in to comment.