-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Move cost from call to evaluate * Rename and add n_states * Rename and update UkfFilter to SquareRootUKF Update the paper reference, the value of kappa (from 1 to 0), the allowed input values to observe, and the SquareRootUKF functions: init, filtered_cholupdate and cholupdate to cope with downdates as well as updates. * Add example and update tests * Rename exponential_decay as standalone/model * Make observers a type of problem - update model parameters to parameter_set - add n_outputs to BaseCost - remove observer from FittingProblem - change solver to _solver - add evaluate and problem inputs to Observer - except all Exception from call to solver - add dataset to UnscentedKalman - update tests to match * Update to cope with subset of states * Add spm_UKF example * Update bool_mask * Update state indices
- Loading branch information
1 parent
68bf5fa
commit 6d0d460
Showing
12 changed files
with
518 additions
and
155 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import pybop | ||
import pybamm | ||
import numpy as np | ||
from examples.standalone.model import ExponentialDecay | ||
|
||
# Parameter set and model definition | ||
parameter_set = pybamm.ParameterValues({"k": "[input]", "y0": "[input]"}) | ||
model = ExponentialDecay(parameter_set=parameter_set, n_states=1) | ||
x0 = np.array([0.1, 1.0]) | ||
|
||
# Fitting parameters | ||
parameters = [ | ||
pybop.Parameter( | ||
"k", | ||
prior=pybop.Gaussian(0.1, 0.05), | ||
bounds=[0, 1], | ||
), | ||
pybop.Parameter( | ||
"y0", | ||
prior=pybop.Gaussian(1, 0.05), | ||
bounds=[0, 3], | ||
), | ||
] | ||
|
||
# Verification: save fixed inputs for testing | ||
inputs = dict() | ||
for i, param in enumerate(parameters): | ||
inputs[param.name] = x0[i] | ||
|
||
# Make a prediction with measurement noise | ||
sigma = 1e-2 | ||
t_eval = np.linspace(0, 20, 10) | ||
values = model.predict(t_eval=t_eval, inputs=inputs) | ||
values = values["2y"].data | ||
corrupt_values = values + np.random.normal(0, sigma, len(t_eval)) | ||
|
||
# Verification step: compute the analytical solution for 2y | ||
expected_values = 2 * inputs["y0"] * np.exp(-inputs["k"] * t_eval) | ||
|
||
# Verification step: make another prediction using the Observer class | ||
model.build(parameters=parameters) | ||
simulator = pybop.Observer(parameters, model, signal=["2y"], x0=x0) | ||
simulator._time_data = t_eval | ||
measurements = simulator.evaluate(x0) | ||
measurements = measurements[:, 0] | ||
|
||
# Verification step: Compare by plotting | ||
go = pybop.PlotlyManager().go | ||
line1 = go.Scatter(x=t_eval, y=corrupt_values, name="Corrupt values", mode="markers") | ||
line2 = go.Scatter( | ||
x=t_eval, y=expected_values, name="Expected trajectory", mode="lines" | ||
) | ||
line3 = go.Scatter(x=t_eval, y=measurements, name="Observed values", mode="markers") | ||
fig = go.Figure(data=[line1, line2, line3]) | ||
|
||
# Form dataset | ||
dataset = pybop.Dataset( | ||
{ | ||
"Time [s]": t_eval, | ||
"Current function [A]": 0 * t_eval, # placeholder | ||
"2y": corrupt_values, | ||
} | ||
) | ||
|
||
# Build the model to get the number of states | ||
model.build(dataset=dataset.data, parameters=parameters) | ||
|
||
# Define the UKF observer | ||
signal = ["2y"] | ||
n_states = model.n_states | ||
n_signals = len(signal) | ||
covariance = np.diag([sigma**2] * n_states) | ||
process_noise = np.diag([1e-6] * n_states) | ||
measurement_noise = np.diag([sigma**2] * n_signals) | ||
observer = pybop.UnscentedKalmanFilterObserver( | ||
parameters, | ||
model, | ||
covariance, | ||
process_noise, | ||
measurement_noise, | ||
dataset, | ||
signal=signal, | ||
x0=x0, | ||
) | ||
|
||
# Verification step: Find the maximum likelihood estimate given the true parameters | ||
estimation = observer.evaluate(x0) | ||
estimation = estimation[:, 0] | ||
|
||
# Verification step: Add the estimate to the plot | ||
line4 = go.Scatter(x=t_eval, y=estimation, name="Estimated trajectory", mode="lines") | ||
fig.add_trace(line4) | ||
fig.show() | ||
|
||
# Generate problem, cost function, and optimisation class | ||
cost = pybop.ObserverCost(observer) | ||
optim = pybop.Optimisation(cost, optimiser=pybop.CMAES, verbose=True) | ||
|
||
# Run optimisation | ||
x, final_cost = optim.run() | ||
print("Estimated parameters:", x) | ||
|
||
# Plot the timeseries output (requires model that returns Voltage) | ||
pybop.quick_plot(x, cost, title="Optimised Comparison") | ||
|
||
# Plot convergence | ||
pybop.plot_convergence(optim) | ||
|
||
# Plot the parameter traces | ||
pybop.plot_parameters(optim) | ||
|
||
# Plot the cost landscape | ||
pybop.plot_cost2d(cost, steps=15) | ||
|
||
# Plot the cost landscape with optimisation path | ||
pybop.plot_cost2d(cost, optim=optim, steps=15) |
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,82 @@ | ||
import pybop | ||
import numpy as np | ||
|
||
# Parameter set and model definition | ||
parameter_set = pybop.ParameterSet.pybamm("Chen2020") | ||
model = pybop.lithium_ion.SPM(parameter_set=parameter_set) | ||
|
||
# Fitting parameters | ||
parameters = [ | ||
pybop.Parameter( | ||
"Negative electrode active material volume fraction", | ||
prior=pybop.Gaussian(0.6, 0.05), | ||
bounds=[0.5, 0.8], | ||
), | ||
pybop.Parameter( | ||
"Positive electrode active material volume fraction", | ||
prior=pybop.Gaussian(0.48, 0.05), | ||
bounds=[0.4, 0.7], | ||
), | ||
] | ||
|
||
# Make a prediction with measurement noise | ||
sigma = 0.001 | ||
t_eval = np.arange(0, 300, 2) | ||
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, | ||
} | ||
) | ||
|
||
# Build the model to get the number of states | ||
model.build(dataset=dataset.data, parameters=parameters) | ||
|
||
# Define the UKF observer, setting the particle boundaries as uncertain states | ||
signal = ["Voltage [V]"] | ||
n_states = model.n_states | ||
n_signals = len(signal) | ||
covariance = np.diag([0] * 19 + [sigma**2] + [0] * 19 + [sigma**2]) | ||
process_noise = np.diag([0] * 19 + [1e-6] + [0] * 19 + [1e-6]) | ||
measurement_noise = np.diag([sigma**2]) | ||
observer = pybop.UnscentedKalmanFilterObserver( | ||
parameters, | ||
model, | ||
covariance, | ||
process_noise, | ||
measurement_noise, | ||
dataset, | ||
signal=signal, | ||
) | ||
|
||
# Generate problem, cost function, and optimisation class | ||
cost = pybop.ObserverCost(observer) | ||
optim = pybop.Optimisation(cost, optimiser=pybop.PSO, verbose=True) | ||
|
||
# Parameter identification using the current observer implementation is very slow | ||
# so let's restrict the number of iterations and reduce the number of plots | ||
optim.set_max_iterations(5) | ||
|
||
# Run optimisation | ||
x, final_cost = optim.run() | ||
print("Estimated parameters:", x) | ||
|
||
# Plot the timeseries output (requires model that returns Voltage) | ||
pybop.quick_plot(x, cost, title="Optimised Comparison") | ||
|
||
# # Plot convergence | ||
# pybop.plot_convergence(optim) | ||
|
||
# # Plot the parameter traces | ||
# pybop.plot_parameters(optim) | ||
|
||
# # Plot the cost landscape | ||
# pybop.plot_cost2d(cost, steps=5) | ||
|
||
# # Plot the cost landscape with optimisation path | ||
# pybop.plot_cost2d(cost, optim=optim, steps=5) |
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
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.