Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MultiFittingProblem class and example #364

Merged
merged 150 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
150 commits
Select commit Hold shift + click to select a range
2a779e1
Add MultiFittingProblem, example and test
NicolaCourtier Jun 14, 2024
48d196e
Remove unused n_problems property
NicolaCourtier Jun 14, 2024
ac8e11a
Merge branch '358-passing-inputs' into 238b-multi-fitting
NicolaCourtier Jul 4, 2024
0b0c0e0
Update multi_fitting.py
NicolaCourtier Jul 4, 2024
2d84260
Merge branch '358-passing-inputs' into 238b-multi-fitting
NicolaCourtier Jul 4, 2024
c614542
Merge branch 'develop' into 238b-multi-fitting
NicolaCourtier Jul 4, 2024
12ab515
Update CHANGELOG.md
NicolaCourtier Jul 4, 2024
2a2eb29
Merge branch 'develop' into 238b-multi-fitting
NicolaCourtier Jul 5, 2024
cd1c7f8
Merge branch 'develop' into 238b-multi-fitting
NicolaCourtier Jul 5, 2024
851255c
Merge branch 'develop' into 238b-multi-fitting
NicolaCourtier Jul 8, 2024
abbeb84
Merge branch 'develop' into 238b-multi-fitting
NicolaCourtier Jul 11, 2024
dd01edb
Remove unused weights
NicolaCourtier Jul 12, 2024
1724cc5
Update problem_list to problem args
NicolaCourtier Jul 12, 2024
84b611d
Concatenate the whole list
NicolaCourtier Jul 12, 2024
c5f58ce
Apply suggestions from code review
NicolaCourtier Jul 12, 2024
6654b1b
Update description
NicolaCourtier Jul 12, 2024
e51decc
Merge branch 'develop' into 238b-multi-fitting
NicolaCourtier Jul 23, 2024
b66a52a
Update default init_soc
NicolaCourtier Jul 24, 2024
157c211
Update CHANGELOG.md
NicolaCourtier Jul 24, 2024
5e60cee
Update check_params
NicolaCourtier Jul 24, 2024
60ae64e
Add pybamm_model as default attribute
NicolaCourtier Jul 24, 2024
c682ace
Ensure predict uses unprocessed_model
NicolaCourtier Jul 25, 2024
914f307
Move rebuild check to model.simulate
NicolaCourtier Jul 25, 2024
367ab12
Align simulate output with predict
NicolaCourtier Jul 25, 2024
4e92192
Replace init_soc with init_ocv for FittingProblem
NicolaCourtier Jul 25, 2024
f799e06
Update notebooks
NicolaCourtier Jul 25, 2024
3f61322
Update test_observers.py
NicolaCourtier Jul 26, 2024
46f31fc
Update descriptions and simplify
NicolaCourtier Jul 26, 2024
28d56fb
Add test_set_initial_state
NicolaCourtier Jul 26, 2024
026fbbd
Copy each model into MultiFittingProblem
NicolaCourtier Jul 26, 2024
cba5c36
Update test_problem.py
NicolaCourtier Jul 26, 2024
781f889
Update ecm.py
NicolaCourtier Jul 26, 2024
61a6fc7
style: pre-commit fixes
pre-commit-ci[bot] Jul 26, 2024
6e6cb70
Break connection between parameter_sets
NicolaCourtier Jul 26, 2024
d17c728
Allow predict to update initial state
NicolaCourtier Jul 26, 2024
aeba8c4
Fix typo
NicolaCourtier Jul 26, 2024
5e65542
Add nbstripout pre-commit hook
NicolaCourtier Jul 28, 2024
c54ae5b
Add -q and re-run all notebooks
NicolaCourtier Jul 28, 2024
4cba081
Copy parameter sets and remove model.initial_state
NicolaCourtier Jul 28, 2024
01115df
Reset spm_NelderMead.py
NicolaCourtier Jul 28, 2024
08df450
Update CHANGELOG.md
NicolaCourtier Jul 28, 2024
0039d8a
Update CHANGELOG.md
NicolaCourtier Jul 29, 2024
e655ed7
Allow parameter_set is None
NicolaCourtier Jul 29, 2024
b2f389b
Merge branch '427-pre-commit-nbstripout' into 424-fitting-ocv
NicolaCourtier Jul 29, 2024
9a7282c
Re-run notebooks
NicolaCourtier Jul 29, 2024
6223251
Update bounds
NicolaCourtier Jul 30, 2024
a71350e
Update notebooks
NicolaCourtier Jul 30, 2024
37be820
Update notebooks
NicolaCourtier Jul 30, 2024
8591ba7
Set numpy random seed in notebooks
NicolaCourtier Jul 30, 2024
d2c7e68
Re-run with fixed seed
NicolaCourtier Jul 30, 2024
e2410cc
Merge branch '427-pre-commit-nbstripout' into 424-fitting-ocv
NicolaCourtier Jul 30, 2024
9b8d27a
Update bounds
NicolaCourtier Jul 30, 2024
2f73168
Update notebooks to initial_state
NicolaCourtier Jul 30, 2024
80df8fe
Add set_initial_state for ECMs
NicolaCourtier Jul 30, 2024
eda8096
Add init_ocv setter
NicolaCourtier Jul 30, 2024
285777b
Add init_ocv values
NicolaCourtier Jul 30, 2024
aebec3c
Re-run notebooks
NicolaCourtier Jul 30, 2024
8df8618
Add tests for ECM get_initial_state
NicolaCourtier Jul 30, 2024
6f05cda
Add ECM initial state error tests
NicolaCourtier Jul 30, 2024
d18b28f
Remove unused store_optimised_parameters
NicolaCourtier Jul 31, 2024
c0d0a24
Merge branch 'develop' into 238b-multi-fitting
NicolaCourtier Jul 31, 2024
5d4f2ea
Update parameters.initial_value
NicolaCourtier Jul 31, 2024
eac17cc
Use any Initial SoC from parameter_set
NicolaCourtier Jul 31, 2024
24e40a0
Merge branch 'develop' into 424-fitting-ocv
NicolaCourtier Jul 31, 2024
b9650c3
Update bounds again
NicolaCourtier Jul 31, 2024
7a7d58a
Update init_soc in notebooks
NicolaCourtier Jul 31, 2024
d993632
Move dataset check within unscented_kalman
NicolaCourtier Jul 31, 2024
5059f71
Remove unnecessary lines from spm_UKF
NicolaCourtier Jul 31, 2024
9bc0c0e
Update all parameters for rebuild
NicolaCourtier Jul 31, 2024
daf3f05
Update init_ocv to _init_ocv
NicolaCourtier Jul 31, 2024
b285885
Ensure value updates alongside initial_value
NicolaCourtier Jul 31, 2024
48788af
Update multi_model_identification
NicolaCourtier Jul 31, 2024
7798af5
Merge branch 'develop' into 421-design_init_soc
NicolaCourtier Jul 31, 2024
07644d4
Update spm_electrode_design.ipynb
NicolaCourtier Jul 31, 2024
799329e
Update spm_electrode_design.ipynb
NicolaCourtier Jul 31, 2024
c7ee29b
Merge branch '421-design_init_soc' into 424-fitting-ocv
NicolaCourtier Jul 31, 2024
61d326f
Fix identation
NicolaCourtier Jul 31, 2024
5be532c
Fix test_plots design problem
NicolaCourtier Jul 31, 2024
29e7a67
Move Changelog entry to breaking changes
NicolaCourtier Aug 1, 2024
f999b96
Move Changelog entry
NicolaCourtier Aug 1, 2024
e4f8ce6
Merge branch 'develop' into 238b-multi-fitting
NicolaCourtier Aug 1, 2024
d31ee54
Merge branch 'develop' into 421-design_init_soc
NicolaCourtier Aug 1, 2024
098e3e2
style: pre-commit fixes
pre-commit-ci[bot] Aug 1, 2024
86f1d17
Fix merge mistake
NicolaCourtier Aug 1, 2024
beac8bb
Merge branch '421-design_init_soc' into 424-fitting-ocv
NicolaCourtier Aug 1, 2024
d205dc9
style: pre-commit fixes
pre-commit-ci[bot] Aug 1, 2024
d1fbd8d
Allow kwargs in MultiFitting evaluate
NicolaCourtier Aug 1, 2024
eef8acf
Add tests
NicolaCourtier Aug 1, 2024
5f7761f
Merge branch '421-design_init_soc' into 424-fitting-ocv
NicolaCourtier Aug 1, 2024
cdd4d1c
Update integration tests
NicolaCourtier Aug 1, 2024
b6c8824
Update spm_weighted_cost.py
NicolaCourtier Aug 1, 2024
ebc5114
Fix tests
NicolaCourtier Aug 2, 2024
d4421fe
style: pre-commit fixes
pre-commit-ci[bot] Aug 2, 2024
043ae52
Merge branch 'develop' into 424-fitting-ocv
NicolaCourtier Aug 2, 2024
3bba9c7
Merge branch 'develop' into 424-fitting-ocv
NicolaCourtier Aug 5, 2024
ede6363
Fix model type check
NicolaCourtier Aug 5, 2024
ab68d15
Merge branch 'develop' into 238b-multi-fitting
NicolaCourtier Aug 5, 2024
fe1397e
Update _parameter_set to parameter_set
NicolaCourtier Aug 5, 2024
3cb4c4d
style: pre-commit fixes
pre-commit-ci[bot] Aug 5, 2024
e3b1466
Update tests with parameter set
NicolaCourtier Aug 5, 2024
cbda2ef
Add model build description
NicolaCourtier Aug 5, 2024
b19035d
Revert to _parameter_set
NicolaCourtier Aug 5, 2024
4d4857b
Fix predict without pybamm test
NicolaCourtier Aug 5, 2024
eba9def
Apply suggestions from code review
NicolaCourtier Aug 5, 2024
892eb77
Apply suggestions from code review
NicolaCourtier Aug 5, 2024
bf01b7e
Fix syntax
NicolaCourtier Aug 5, 2024
71a95cf
Fix variable name
NicolaCourtier Aug 5, 2024
4484951
Update model type check
NicolaCourtier Aug 5, 2024
08cdc4c
Update parameter_set setter
NicolaCourtier Aug 5, 2024
cd87e83
style: pre-commit fixes
pre-commit-ci[bot] Aug 5, 2024
14e4223
Add parameters.reset_initial_value
NicolaCourtier Aug 5, 2024
8a19542
Add n_outputs property
NicolaCourtier Aug 6, 2024
a67c564
style: pre-commit fixes
pre-commit-ci[bot] Aug 6, 2024
5450f21
Remove public parameter_set setter
NicolaCourtier Aug 6, 2024
676e7ed
Correct integer to float
NicolaCourtier Aug 6, 2024
80ef44e
Convert initial_state to dict
NicolaCourtier Aug 6, 2024
8e67d89
Add guidance
NicolaCourtier Aug 6, 2024
d5f63d0
Remove empty dictionary defaults
NicolaCourtier Aug 6, 2024
d0d1bd2
style: pre-commit fixes
pre-commit-ci[bot] Aug 6, 2024
5c52712
Add warning stacklevels
NicolaCourtier Aug 6, 2024
dacafc3
Catch simulation errors in problem evaluation
NicolaCourtier Aug 6, 2024
bcc7acf
Add pybamm version comment
NicolaCourtier Aug 6, 2024
8607a6b
Add set initial ocv check
NicolaCourtier Aug 6, 2024
605f509
Add model.clear and remove setters
NicolaCourtier Aug 7, 2024
a5be6ee
Merge branch '445-remove-setters' into 424-fitting-ocv
NicolaCourtier Aug 7, 2024
a188c4b
Update unscented_kalman.py
NicolaCourtier Aug 7, 2024
ca2a5b1
Update unscented_kalman.py
NicolaCourtier Aug 7, 2024
a4c0030
Update test_models.py
NicolaCourtier Aug 7, 2024
742f9d4
Update test_set_initial_state
NicolaCourtier Aug 7, 2024
db09455
Merge branch 'develop' into 424-fitting-ocv
NicolaCourtier Aug 7, 2024
4f7c75f
Merge branch '424-fitting-ocv' into 238b-multi-fitting
NicolaCourtier Aug 7, 2024
bbece50
Merge branch 'develop' into 238b-multi-fitting
NicolaCourtier Aug 7, 2024
453d618
Use clear in model.new_copy
NicolaCourtier Aug 7, 2024
36ddd93
Reference public attributes
NicolaCourtier Aug 7, 2024
1b8cdb1
Move MultiFittingProblem into separate file
NicolaCourtier Aug 7, 2024
60a6ca8
Update description
NicolaCourtier Aug 7, 2024
9e02d24
Add dataset property
NicolaCourtier Aug 7, 2024
e494be5
Fix changes due to linting
NicolaCourtier Aug 7, 2024
172b484
Add test_multi_fitting_problem
NicolaCourtier Aug 7, 2024
0cb2917
Add problem.set_initial_state
NicolaCourtier Aug 8, 2024
a865732
Merge rebuild into build
NicolaCourtier Aug 8, 2024
0e4754b
Update CHANGELOG.md
NicolaCourtier Aug 8, 2024
791a732
Update base_model.py
NicolaCourtier Aug 8, 2024
f675e2e
Fix notebooks
NicolaCourtier Aug 8, 2024
d55a624
Merge branch '444-merge-rebuild' into 238b-multi-fitting
NicolaCourtier Aug 8, 2024
9c2f0b3
Update multi_fitting with different initial SoC
NicolaCourtier Aug 8, 2024
25f0714
Update copying
NicolaCourtier Aug 8, 2024
2cf54a6
Add check for identical models
NicolaCourtier Aug 8, 2024
dbded27
Merge branch 'develop' into 238b-multi-fitting
NicolaCourtier Aug 9, 2024
7eeeb00
refactor: model.new_copy() args as dictionary and single construction
BradyPlanden Aug 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions examples/scripts/multi_fitting.py
NicolaCourtier marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import numpy as np

import pybop

# Parameter set and model definition
parameter_set = pybop.ParameterSet.pybamm("Chen2020")
model_1 = pybop.lithium_ion.SPM(parameter_set=parameter_set)
model_2 = pybop.lithium_ion.SPM(parameter_set=parameter_set.copy())
NicolaCourtier marked this conversation as resolved.
Show resolved Hide resolved

# Fitting parameters
parameters = pybop.Parameters(
pybop.Parameter(
"Negative electrode active material volume fraction",
prior=pybop.Gaussian(0.68, 0.05),
true_value=parameter_set["Negative electrode active material volume fraction"],
),
pybop.Parameter(
"Positive electrode active material volume fraction",
prior=pybop.Gaussian(0.58, 0.05),
true_value=parameter_set["Positive electrode active material volume fraction"],
),
)

# Generate a dataset
sigma = 0.001
experiment_1 = pybop.Experiment([("Discharge at 0.5C for 2 minutes (4 second period)")])
values_1 = model_1.predict(experiment=experiment_1)
dataset_1 = pybop.Dataset(
{
"Time [s]": values_1["Time [s]"].data,
"Current function [A]": values_1["Current [A]"].data,
"Voltage [V]": values_1["Voltage [V]"].data
+ np.random.normal(0, sigma, len(values_1["Voltage [V]"].data)),
}
)

# Generate a second dataset
experiment_2 = pybop.Experiment([("Discharge at 1C for 2 minutes (4 second period)")])
values_2 = model_2.predict(experiment=experiment_2)
dataset_2 = pybop.Dataset(
{
"Time [s]": values_2["Time [s]"].data,
"Current function [A]": values_2["Current [A]"].data,
"Voltage [V]": values_2["Voltage [V]"].data
+ np.random.normal(0, sigma, len(values_2["Voltage [V]"].data)),
}
)

# Generate a problem for each dataset and combine into one
problem_1 = pybop.FittingProblem(model_1, parameters, dataset_1)
problem_2 = pybop.FittingProblem(model_2, parameters, dataset_2)
problem = pybop.MultiFittingProblem(problem_list=[problem_1, problem_2])
BradyPlanden marked this conversation as resolved.
Show resolved Hide resolved

# Generate the cost function and optimisation class
cost = pybop.SumSquaredError(problem)
optim = pybop.IRPropMin(
cost,
# sigma0=0.011,
verbose=True,
max_iterations=12,
)

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

# Plot the timeseries output
pybop.quick_plot(problem_1, inputs=x, title="Optimised Comparison")
pybop.quick_plot(problem_2, 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
bounds = np.array([[0.5, 0.8], [0.4, 0.7]])
pybop.plot2d(optim, bounds=bounds, steps=15)
2 changes: 1 addition & 1 deletion pybop/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
# Problem class
#
from .problems.base_problem import BaseProblem
from .problems.fitting_problem import FittingProblem
from .problems.fitting_problem import FittingProblem, MultiFittingProblem
from .problems.design_problem import DesignProblem

#
Expand Down
3 changes: 2 additions & 1 deletion pybop/problems/base_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ def __init__(
)

self.parameters = parameters
self._model = model
if model is not None:
self._model = model
self.check_model = check_model
if isinstance(signal, str):
signal = [signal]
Expand Down
111 changes: 111 additions & 0 deletions pybop/problems/fitting_problem.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import numpy as np

from pybop import BaseProblem
from pybop._dataset import Dataset
from pybop.models.base_model import Inputs
from pybop.parameters.parameter import Parameters


class FittingProblem(BaseProblem):
Expand Down Expand Up @@ -134,3 +136,112 @@ def evaluateS1(self, inputs: Inputs):
)

return (y, np.asarray(dy))


class MultiFittingProblem(BaseProblem):
"""
NicolaCourtier marked this conversation as resolved.
Show resolved Hide resolved
Problem class for joining mulitple fitting problems.

Extends `FittingProblem` to multiple fitting problems.
"""

def __init__(self, problem_list, weights=None):
NicolaCourtier marked this conversation as resolved.
Show resolved Hide resolved
self.problem_list = problem_list
NicolaCourtier marked this conversation as resolved.
Show resolved Hide resolved
self.weights = weights
NicolaCourtier marked this conversation as resolved.
Show resolved Hide resolved

# Compile the set of parameters, ignoring duplicates
combined_parameters = Parameters()
for problem in self.problem_list:
combined_parameters.join(problem.parameters)

# Combine the target datasets
combined_dataset = Dataset(
{"Time [s]": np.asarray([]), "Combined signal": np.asarray([])}
)
for problem in self.problem_list:
for signal in problem.signal:
combined_dataset["Time [s]"] = np.concatenate(
(combined_dataset["Time [s]"], problem._time_data)
)
combined_dataset["Combined signal"] = np.concatenate(
(combined_dataset["Combined signal"], problem._target[signal])
)
NicolaCourtier marked this conversation as resolved.
Show resolved Hide resolved

super().__init__(
parameters=combined_parameters,
model=None,
signal=["Combined signal"],
)
self._dataset = combined_dataset.data
self.parameters.initial_value()

# Unpack time and target data
self._time_data = self._dataset["Time [s]"]
self.n_time_data = len(self._time_data)
self.set_target(combined_dataset)

def evaluate(self, inputs: Inputs):
"""
Evaluate the model with the given parameters and return the signal.

Parameters
----------
inputs : Inputs
Parameters for evaluation of the model.

Returns
-------
y : np.ndarray
The model output y(t) simulated with given inputs.
"""
inputs = self.parameters.verify(inputs)
self.parameters.update(values=list(inputs.values()))

y = {"Combined signal": np.asarray([])}
for problem in self.problem_list:
problem_inputs = problem.parameters.as_dict()
for signal in problem.signal:
yi = problem.evaluate(problem_inputs)
y["Combined signal"] = np.concatenate(
(y["Combined signal"], yi[signal])
)

return y
NicolaCourtier marked this conversation as resolved.
Show resolved Hide resolved

def evaluateS1(self, inputs: Inputs):
"""
Evaluate the model with the given parameters and return the signal and its derivatives.

Parameters
----------
inputs : Inputs
Parameters for evaluation of the model.

Returns
-------
tuple
A tuple containing the simulation result y(t) and the sensitivities dy/dx(t) evaluated
with given inputs.
"""

inputs = self.parameters.verify(inputs)
self.parameters.update(values=list(inputs.values()))

# y = np.empty((self._target_length))
# dy = np.empty((self._target_length, self.n_parameters))

y = {"Combined signal": np.asarray([])}
dy = None
for problem in self.problem_list:
problem_inputs = problem.parameters.as_dict()
for signal in problem.signal:
yi, dyi = problem.evaluateS1(problem_inputs)
y["Combined signal"] = np.concatenate(
(y["Combined signal"], yi[signal])
)
if dy is None:
dy = dyi
else:
dy = np.concatenate((dy, dyi))

return (y, dy)
NicolaCourtier marked this conversation as resolved.
Show resolved Hide resolved
50 changes: 50 additions & 0 deletions tests/integration/test_model_experiment_changes.py
NicolaCourtier marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,53 @@ def final_cost(self, solution, model, parameters, init_soc):
optim = pybop.PSO(cost)
x, final_cost = optim.run()
return final_cost

@pytest.mark.integration
def test_multi_fitting_problem(self):
parameter_set = pybop.ParameterSet.pybamm("Chen2020")
parameters = pybop.Parameter(
"Negative electrode active material volume fraction",
prior=pybop.Gaussian(0.68, 0.05),
true_value=parameter_set[
"Negative electrode active material volume fraction"
],
)

model_1 = pybop.lithium_ion.SPM(parameter_set=parameter_set)
experiment_1 = pybop.Experiment(
["Discharge at 1C until 3 V (4 seconds period)"]
)
solution_1 = model_1.predict(experiment=experiment_1)
dataset_1 = pybop.Dataset(
{
"Time [s]": solution_1["Time [s]"].data,
"Current function [A]": solution_1["Current [A]"].data,
"Voltage [V]": solution_1["Voltage [V]"].data,
}
)

model_2 = pybop.lithium_ion.SPMe(parameter_set=parameter_set.copy())
experiment_2 = pybop.Experiment(
["Discharge at 3C until 3 V (4 seconds period)"]
)
solution_2 = model_2.predict(experiment=experiment_2)
dataset_2 = pybop.Dataset(
{
"Time [s]": solution_2["Time [s]"].data,
"Current function [A]": solution_2["Current [A]"].data,
"Voltage [V]": solution_2["Voltage [V]"].data,
}
)

# Define a problem for each dataset and combine them into one
problem_1 = pybop.FittingProblem(model_1, parameters, dataset_1)
problem_2 = pybop.FittingProblem(model_2, parameters, dataset_2)
problem = pybop.MultiFittingProblem(problem_list=[problem_1, problem_2])
cost = pybop.RootMeanSquaredError(problem)

# Test with a gradient and non-gradient-based optimiser
for optimiser in [pybop.SNES, pybop.IRPropMin]:
optim = optimiser(cost)
x, final_cost = optim.run()
np.testing.assert_allclose(x, parameters.true_value, atol=2e-5)
np.testing.assert_allclose(final_cost, 0, atol=2e-5)
Loading