Skip to content

Commit

Permalink
Fix parameter set logic and allow FittingProblem to take init_ocv rat…
Browse files Browse the repository at this point in the history
…her than init_soc (#425)

* Update default init_soc

* Update CHANGELOG.md

* Update check_params

* Add pybamm_model as default attribute

* Ensure predict uses unprocessed_model

* Move rebuild check to model.simulate

* Align simulate output with predict

* Replace init_soc with init_ocv for FittingProblem

* Update notebooks

* Update test_observers.py

* Update descriptions and simplify

* Add test_set_initial_state

* Update test_problem.py

* Break connection between parameter_sets

* Allow predict to update initial state

* Add nbstripout pre-commit hook

* Add -q and re-run all notebooks

* Copy parameter sets and remove model.initial_state

* Reset spm_NelderMead.py

* Update CHANGELOG.md

* Allow parameter_set is None

* Re-run notebooks

* Update bounds

* Update notebooks

* Set numpy random seed in notebooks

* Re-run with fixed seed

* Update bounds

* Update notebooks to initial_state

* Add set_initial_state for ECMs

* Add init_ocv setter

* Add init_ocv values

* Re-run notebooks

* Add tests for ECM get_initial_state

* Add ECM initial state error tests

* Remove unused store_optimised_parameters

* Update parameters.initial_value

* Use any Initial SoC from parameter_set

* Update bounds again

* Update init_soc in notebooks

* Move dataset check within unscented_kalman

* Remove unnecessary lines from spm_UKF

* Update all parameters for rebuild

* Ensure value updates alongside initial_value

* Update multi_model_identification

* Update spm_electrode_design.ipynb

* Fix test_plots design problem

* Move Changelog entry to breaking changes

* Add tests

* Update integration tests

* Update spm_weighted_cost.py

* Fix tests

* Fix model type check

* Update _parameter_set to parameter_set

* Update tests with parameter set

* Add model build description

* Revert to _parameter_set

* Apply suggestions from code review

Co-authored-by: Brady Planden <[email protected]>

* Apply suggestions from code review

Co-authored-by: Brady Planden <[email protected]>

* Fix syntax

* Fix variable name

* Update model type check

* Update parameter_set setter

* Add parameters.reset_initial_value

* Add n_outputs property

* Remove public parameter_set setter

* Correct integer to float

* Convert initial_state to dict

* Add guidance

* Remove empty dictionary defaults

* Add warning stacklevels

* Catch simulation errors in problem evaluation

* Add pybamm version comment

* Add set initial ocv check

* Add model.clear and remove setters

* Update unscented_kalman.py

* Update test_models.py

* Update test_set_initial_state

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Brady Planden <[email protected]>
  • Loading branch information
3 people authored Aug 7, 2024
1 parent a8dbbe6 commit 15d21b1
Show file tree
Hide file tree
Showing 41 changed files with 877 additions and 549 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

## Breaking Changes

- [#424](https://github.com/pybop-team/PyBOP/issues/424) - Replaces the `init_soc` input to `FittingProblem` with the option to pass an initial OCV value, updates `BaseModel` and fixes `multi_model_identification.ipynb` and `spm_electrode_design.ipynb`.

# [v24.6.1](https://github.com/pybop-team/PyBOP/tree/v24.6.1) - 2024-07-31

## Features
Expand Down
38 changes: 19 additions & 19 deletions examples/notebooks/multi_model_identification.ipynb

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions examples/notebooks/multi_optimiser_identification.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@
"outputs": [],
"source": [
"t_eval = np.arange(0, 2000, 10)\n",
"init_soc = 1.0\n",
"values = synth_model.predict(t_eval=t_eval, init_soc=init_soc)"
"initial_state = {\"Initial SoC\": 1.0}\n",
"values = synth_model.predict(t_eval=t_eval, initial_state=initial_state)"
]
},
{
Expand Down Expand Up @@ -448,7 +448,8 @@
"source": [
"optims = []\n",
"xs = []\n",
"problem = pybop.FittingProblem(model, parameters, dataset, init_soc=init_soc)\n",
"model.set_initial_state(initial_state)\n",
"problem = pybop.FittingProblem(model, parameters, dataset)\n",
"cost = pybop.SumSquaredError(problem)\n",
"for optimiser in gradient_optimisers:\n",
" print(f\"Running {optimiser.__name__}\")\n",
Expand Down
8 changes: 4 additions & 4 deletions examples/notebooks/optimiser_calibration.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
" }\n",
")\n",
"model = pybop.lithium_ion.SPM(parameter_set=parameter_set)\n",
"init_soc = 0.4\n",
"initial_state = {\"Initial SoC\": 0.4}\n",
"experiment = pybop.Experiment(\n",
" [\n",
" (\n",
Expand All @@ -110,7 +110,7 @@
" ]\n",
" * 2\n",
")\n",
"values = model.predict(init_soc=init_soc, experiment=experiment)"
"values = model.predict(initial_state=initial_state, experiment=experiment)"
]
},
{
Expand Down Expand Up @@ -240,7 +240,7 @@
}
],
"source": [
"problem = pybop.FittingProblem(model, parameters, dataset, init_soc=init_soc)\n",
"problem = pybop.FittingProblem(model, parameters, dataset)\n",
"cost = pybop.SumSquaredError(problem)\n",
"optim = pybop.GradientDescent(cost, sigma0=0.2, max_iterations=100)"
]
Expand Down Expand Up @@ -451,7 +451,7 @@
"optims = []\n",
"for sigma in sigmas:\n",
" print(sigma)\n",
" problem = pybop.FittingProblem(model, parameters, dataset, init_soc=init_soc)\n",
" problem = pybop.FittingProblem(model, parameters, dataset)\n",
" cost = pybop.SumSquaredError(problem)\n",
" optim = pybop.GradientDescent(cost, sigma0=sigma, max_iterations=100)\n",
" x, final_cost = optim.run()\n",
Expand Down
12 changes: 7 additions & 5 deletions examples/notebooks/spm_electrode_design.ipynb

Large diffs are not rendered by default.

15 changes: 8 additions & 7 deletions examples/scripts/cuckoo.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

# 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
Expand All @@ -13,17 +19,14 @@
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(
[
(
Expand All @@ -32,9 +35,7 @@
),
]
)
values = model.predict(
init_soc=init_soc, experiment=experiment, inputs=parameters.as_dict("true")
)
values = model.predict(initial_state={"Initial SoC": 0.7}, experiment=experiment)

sigma = 0.002
corrupt_values = values["Voltage [V]"].data + np.random.normal(
Expand All @@ -51,7 +52,7 @@
)

# Generate problem, cost function, and optimisation class
problem = pybop.FittingProblem(model, parameters, dataset, init_soc=init_soc)
problem = pybop.FittingProblem(model, parameters, dataset)
cost = pybop.GaussianLogLikelihood(problem, sigma0=sigma * 4)
optim = pybop.Optimisation(
cost,
Expand Down
9 changes: 4 additions & 5 deletions examples/scripts/exp_UKF.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from examples.standalone.model import ExponentialDecay

# Parameter set and model definition
parameter_set = {"k": "[input]", "y0": "[input]"}
parameter_set = {"k": 0.1, "y0": 1.0}
model = ExponentialDecay(parameter_set=parameter_set, n_states=1)

# Fitting parameters
Expand All @@ -13,22 +13,21 @@
"k",
prior=pybop.Gaussian(0.1, 0.05),
bounds=[0, 1],
true_value=0.1,
true_value=parameter_set["k"],
),
pybop.Parameter(
"y0",
prior=pybop.Gaussian(1, 0.05),
bounds=[0, 3],
true_value=1.0,
true_value=parameter_set["y0"],
),
)

# Make a prediction with measurement noise
sigma = 1e-2
t_eval = np.linspace(0, 20, 10)
model.parameters = parameters
true_inputs = parameters.as_dict("true")
values = model.predict(t_eval=t_eval, inputs=true_inputs)
values = model.predict(t_eval=t_eval)
values = values["2y"].data
corrupt_values = values + np.random.normal(0, sigma, len(t_eval))

Expand Down
7 changes: 2 additions & 5 deletions examples/scripts/spm_AdamW.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
)

# Generate data
init_soc = 0.5
sigma = 0.003
experiment = pybop.Experiment(
[
Expand All @@ -30,7 +29,7 @@
]
* 2
)
values = model.predict(init_soc=init_soc, experiment=experiment)
values = model.predict(initial_state={"Initial SoC": 0.5}, experiment=experiment)


def noise(sigma):
Expand All @@ -50,9 +49,7 @@ def noise(sigma):

signal = ["Voltage [V]", "Bulk open-circuit voltage [V]"]
# Generate problem, cost function, and optimisation class
problem = pybop.FittingProblem(
model, parameters, dataset, signal=signal, init_soc=init_soc
)
problem = pybop.FittingProblem(model, parameters, dataset, signal=signal)
cost = pybop.Minkowski(problem, p=2)
optim = pybop.AdamW(
cost,
Expand Down
7 changes: 2 additions & 5 deletions examples/scripts/spm_MAP.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

# Set variables
sigma = 0.002
init_soc = 0.7

# Construct and update initial parameter values
parameter_set = pybop.ParameterSet.pybamm("Chen2020")
Expand Down Expand Up @@ -45,9 +44,7 @@
),
]
)
values = model.predict(
init_soc=init_soc, experiment=experiment, inputs=parameters.true_value()
)
values = model.predict(initial_state={"Initial SoC": 0.7}, experiment=experiment)
corrupt_values = values["Voltage [V]"].data + np.random.normal(
0, sigma, len(values["Voltage [V]"].data)
)
Expand All @@ -62,7 +59,7 @@
)

# Generate problem, cost function, and optimisation class
problem = pybop.FittingProblem(model, parameters, dataset, init_soc=init_soc)
problem = pybop.FittingProblem(model, parameters, dataset)
cost = pybop.MAP(problem, pybop.GaussianLogLikelihoodKnownSigma, sigma0=sigma)
optim = pybop.AdamW(
cost,
Expand Down
9 changes: 3 additions & 6 deletions examples/scripts/spm_NelderMead.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
)

# Generate data
init_soc = 0.5
sigma = 0.003
experiment = pybop.Experiment(
[
Expand All @@ -30,7 +29,7 @@
]
* 2
)
values = model.predict(init_soc=init_soc, experiment=experiment)
values = model.predict(initial_state={"Initial SoC": 0.5}, experiment=experiment)


def noise(sigma):
Expand All @@ -48,11 +47,9 @@ def noise(sigma):
}
)

signal = ["Voltage [V]", "Bulk open-circuit voltage [V]"]
# Generate problem, cost function, and optimisation class
problem = pybop.FittingProblem(
model, parameters, dataset, signal=signal, init_soc=init_soc
)
signal = ["Voltage [V]", "Bulk open-circuit voltage [V]"]
problem = pybop.FittingProblem(model, parameters, dataset, signal=signal)
cost = pybop.RootMeanSquaredError(problem)
optim = pybop.NelderMead(
cost,
Expand Down
6 changes: 1 addition & 5 deletions examples/scripts/spm_UKF.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,9 @@
)

# Build the model to get the number of states
model.build(dataset=dataset.data, parameters=parameters)
model.build(dataset=dataset, 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] * 20 + [sigma**2] + [0] * 20 + [sigma**2])
process_noise = np.diag([0] * 20 + [1e-6] + [0] * 20 + [1e-6])
measurement_noise = np.diag([sigma**2])
Expand All @@ -52,7 +49,6 @@
process_noise,
measurement_noise,
dataset,
signal=signal,
)

# Generate problem, cost function, and optimisation class
Expand Down
2 changes: 1 addition & 1 deletion examples/scripts/spm_scipymin.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

# Define the cost to optimise
signal = ["Voltage [V]"]
problem = pybop.FittingProblem(model, parameters, dataset, signal=signal, init_soc=0.98)
problem = pybop.FittingProblem(model, parameters, dataset, signal=signal)
cost = pybop.RootMeanSquaredError(problem)

# Build the optimisation problem
Expand Down
5 changes: 2 additions & 3 deletions examples/scripts/spm_weighted_cost.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

# Generate data
sigma = 0.001
init_soc = 0.5
experiment = pybop.Experiment(
[
(
Expand All @@ -34,7 +33,7 @@
]
* 2
)
values = model.predict(experiment=experiment, init_soc=init_soc)
values = model.predict(experiment=experiment, initial_state={"Initial SoC": 0.5})


def noise(sigma):
Expand All @@ -51,7 +50,7 @@ def noise(sigma):
)

# Generate problem, cost function, and optimisation class
problem = pybop.FittingProblem(model, parameters, dataset, init_soc=init_soc)
problem = pybop.FittingProblem(model, parameters, dataset)
cost1 = pybop.SumSquaredError(problem)
cost2 = pybop.RootMeanSquaredError(problem)
weighted_cost = pybop.WeightedCost(cost1, cost2, weights=[0.1, 1])
Expand Down
3 changes: 1 addition & 2 deletions examples/scripts/spme_max_energy.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,11 @@
experiment = pybop.Experiment(
["Discharge at 1C until 2.5 V (5 seconds period)"],
)
init_soc = 1 # start from full charge
signal = ["Voltage [V]", "Current [A]"]

# Generate problem
problem = pybop.DesignProblem(
model, parameters, experiment, signal=signal, init_soc=init_soc
model, parameters, experiment, signal=signal, initial_state={"Initial SoC": 1.0}
)

# Generate multiple cost functions and combine them.
Expand Down
4 changes: 1 addition & 3 deletions examples/standalone/problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ def __init__(
additional_variables=None,
init_soc=None,
):
super().__init__(
parameters, model, check_model, signal, additional_variables, init_soc
)
super().__init__(parameters, model, check_model, signal, additional_variables)
self._dataset = dataset.data

# Check that the dataset contains time and current
Expand Down
8 changes: 4 additions & 4 deletions pybop/costs/_weighted_cost.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ class WeightedCost(BaseCost):
If True, the shared problem will be evaluated once and saved before the
self.compute() method of each cost is called (default: False).
_has_separable_problem: bool
If True, the shared problem is seperable from the cost function and
will be evaluated for each problem before the cost evaluation is
called (default: False). This attribute is used for sub-cost objects;
however, the top-level WeightedCost attribute is not used (i.e. == False).
This attribute must be set to False for WeightedCost objects. If the
corresponding attribute of an individual cost is True, the problem is
separable from the cost function and will be evaluated before the
individual cost evaluation is called.
"""

def __init__(self, *costs, weights: Optional[list[float]] = None):
Expand Down
Loading

0 comments on commit 15d21b1

Please sign in to comment.