-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
random_search optimiser added in the pints framework (#580)
* random_search optimiser added in the pints framework * style: pre-commit fixes * description texts updated * style: pre-commit fixes * random_search updated * population size input in random search modified * example updated * unit tests added for randomsearch * style: pre-commit fixes * none type boundary handling modified * style: pre-commit fixes * updated * updated * unit tests updated * unit test updated * unit tests modified * updated * style: pre-commit fixes * randomsearch modified * style: pre-commit fixes * boundary logic updated * unit tests updated * unit tests updated * unit test changed * style: pre-commit fixes * fix: RandomSearch with multistart * unit test updated * unit test modified * suggested changes incorporated * suggested changes updated * unit tests for RandomSearch added * style: pre-commit fixes * unit tests modified * style: pre-commit fixes * changelog updated * unit tests added * style: pre-commit fixes * unit test modified * Apply suggestions from code review * Updates unit tests, upper pin to BPX, bugfix _cuckoo and _random_search for population_size setting. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Brady Planden <[email protected]> Co-authored-by: Brady Planden <[email protected]>
- Loading branch information
1 parent
c637f62
commit a15f7c4
Showing
7 changed files
with
311 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import numpy as np | ||
|
||
import pybop | ||
|
||
# Define model | ||
parameter_set = pybop.ParameterSet.pybamm("Chen2020") | ||
parameter_set.update( | ||
{ | ||
"Negative electrode active material volume fraction": 0.7, | ||
"Positive electrode active material volume fraction": 0.67, | ||
} | ||
) | ||
model = pybop.lithium_ion.SPM(parameter_set=parameter_set) | ||
|
||
# Fitting parameters | ||
parameters = pybop.Parameters( | ||
pybop.Parameter( | ||
"Negative electrode active material volume fraction", | ||
bounds=[0.4, 0.75], | ||
initial_value=0.41, | ||
), | ||
pybop.Parameter( | ||
"Positive electrode active material volume fraction", | ||
bounds=[0.4, 0.75], | ||
initial_value=0.41, | ||
), | ||
) | ||
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(initial_state={"Initial SoC": 0.7}, experiment=experiment) | ||
|
||
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) | ||
cost = pybop.GaussianLogLikelihood(problem, sigma0=sigma * 4) | ||
optim = pybop.Optimisation( | ||
cost, | ||
optimiser=pybop.RandomSearch, | ||
max_iterations=100, | ||
) | ||
|
||
results = optim.run() | ||
|
||
# Plot the timeseries output | ||
pybop.plot.quick(problem, problem_inputs=results.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.plot.contour(optim, steps=10) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import numpy as np | ||
from pints import PopulationBasedOptimiser | ||
|
||
|
||
class RandomSearchImpl(PopulationBasedOptimiser): | ||
""" | ||
Random Search (RS) optimisation algorithm. | ||
This algorithm explores the parameter space by randomly sampling points. | ||
The algorithm does the following: | ||
1. Initialise a population of solutions. | ||
2. At each iteration, generate `n` number of random positions within boundaries. | ||
3. Evaluate the quality/fitness of the positions. | ||
4. Replace the best position with improved position if found. | ||
Parameters: | ||
population_size (optional): Number of solutions to evaluate per iteration. | ||
References: | ||
The Random Search algorithm implemented in this work is based on principles outlined | ||
in "Introduction to Stochastic Search and Optimization: Estimation, Simulation, and | ||
Control" by Spall, J. C. (2003). | ||
The implementation inherits from the PINTS PopulationOptimiser. | ||
""" | ||
|
||
def __init__(self, x0, sigma0=0.05, boundaries=None): | ||
super().__init__(x0, sigma0, boundaries=boundaries) | ||
|
||
# Problem dimensionality | ||
self._dim = len(x0) | ||
|
||
# Initialise best solution | ||
self._x_best = np.copy(x0) | ||
self._f_best = np.inf | ||
self._running = False | ||
self._ready_for_tell = False | ||
|
||
def ask(self): | ||
""" | ||
Returns a list of positions to evaluate in the optimiser-space. | ||
""" | ||
self._ready_for_tell = True | ||
self._running = True | ||
|
||
# Generate random solutions | ||
if self._boundaries: | ||
self._candidates = np.random.uniform( | ||
low=self._boundaries.lower(), | ||
high=self._boundaries.upper(), | ||
size=(self._population_size, self._dim), | ||
) | ||
return self._candidates | ||
|
||
self._candidates = np.random.normal( | ||
self._x0, self._sigma0, size=(self._population_size, self._dim) | ||
) | ||
return self.clip_candidates(self._candidates) | ||
|
||
def tell(self, replies): | ||
""" | ||
Receives a list of cost function values from points previously specified | ||
by `self.ask()`, and updates the optimiser state accordingly. | ||
""" | ||
if not self._ready_for_tell: | ||
raise RuntimeError("ask() must be called before tell().") | ||
|
||
# Evaluate solutions and update the best | ||
for i in range(self._population_size): | ||
f_new = replies[i] | ||
if f_new < self._f_best: | ||
self._f_best = f_new | ||
self._x_best = self._candidates[i] | ||
|
||
def running(self): | ||
""" | ||
Returns ``True`` if the optimisation is in progress. | ||
""" | ||
return self._running | ||
|
||
def x_best(self): | ||
""" | ||
Returns the best parameter values found so far. | ||
""" | ||
return self._x_best | ||
|
||
def f_best(self): | ||
""" | ||
Returns the best score found so far. | ||
""" | ||
return self._f_best | ||
|
||
def name(self): | ||
""" | ||
Returns the name of the optimiser. | ||
""" | ||
return "Random Search" | ||
|
||
def clip_candidates(self, x): | ||
""" | ||
Clip the input array to the boundaries if available. | ||
""" | ||
if self._boundaries: | ||
x = np.clip(x, self._boundaries.lower(), self._boundaries.upper()) | ||
return x | ||
|
||
def _suggested_population_size(self): | ||
""" | ||
Returns a suggested population size based on the dimension of the parameter space. | ||
""" | ||
return 4 + int(3 * np.log(self._n_parameters)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.