Skip to content

Commit

Permalink
Replace init_soc with init_ocv for FittingProblem
Browse files Browse the repository at this point in the history
  • Loading branch information
NicolaCourtier committed Jul 25, 2024
1 parent 367ab12 commit 4e92192
Show file tree
Hide file tree
Showing 16 changed files with 151 additions and 141 deletions.
4 changes: 2 additions & 2 deletions examples/scripts/cuckoo.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
]
)
values = model.predict(
init_soc=init_soc, experiment=experiment, inputs=parameters.as_dict("true")
initial_state=init_soc, experiment=experiment, inputs=parameters.as_dict("true")
)

sigma = 0.002
Expand All @@ -51,7 +51,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
6 changes: 2 additions & 4 deletions examples/scripts/spm_AdamW.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
]
* 2
)
values = model.predict(init_soc=init_soc, experiment=experiment)
values = model.predict(initial_state=init_soc, experiment=experiment)


def noise(sigma):
Expand All @@ -50,9 +50,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
4 changes: 2 additions & 2 deletions examples/scripts/spm_MAP.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
]
)
values = model.predict(
init_soc=init_soc, experiment=experiment, inputs=parameters.true_value()
initial_state=init_soc, experiment=experiment, inputs=parameters.true_value()
)
corrupt_values = values["Voltage [V]"].data + np.random.normal(
0, sigma, len(values["Voltage [V]"].data)
Expand All @@ -62,7 +62,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
8 changes: 5 additions & 3 deletions examples/scripts/spm_NelderMead.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
]
* 2
)
values = model.predict(init_soc=init_soc, experiment=experiment)
values = model.predict(initial_state=init_soc, experiment=experiment)


def noise(sigma):
Expand All @@ -48,10 +48,12 @@ def noise(sigma):
}
)

signal = ["Voltage [V]", "Bulk open-circuit voltage [V]"]
# Generate problem, cost function, and optimisation class
signal = ["Voltage [V]", "Bulk open-circuit voltage [V]"]
init_ocv = values["Bulk open-circuit voltage [V]"].data[0]
print(init_ocv)
problem = pybop.FittingProblem(
model, parameters, dataset, signal=signal, init_soc=init_soc
model, parameters, dataset, signal=signal, init_ocv=init_ocv
)
cost = pybop.RootMeanSquaredError(problem)
optim = pybop.NelderMead(
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
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
111 changes: 70 additions & 41 deletions pybop/models/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,15 @@ def __init__(self, name="Base Model", parameter_set=None):
self.standard_parameters = {}
self.param_check_counter = 0
self.allow_infeasible_solutions = True
self.initial_state = None
self.current_function = None

def build(
self,
dataset: Dataset = None,
parameters: Union[Parameters, dict] = None,
check_model: bool = True,
init_soc: Optional[float] = None,
initial_state: Optional[float] = None,
) -> None:
"""
Construct the PyBaMM model if not already built, and set parameters.
Expand All @@ -93,16 +95,17 @@ def build(
A pybop Parameters class or dictionary containing parameter values to apply to the model.
check_model : bool, optional
If True, the model will be checked for correctness after construction.
init_soc : float, optional
The initial state of charge to be used in simulations.
initial_state : float or str, optional
If float, this value is used as the initial state of charge (as a fraction between 0
and 1). If str ending in "V", this value is used as the initial open-circuit voltage.
Defaults to None, indicating that the existing initial concentrations will be used.
"""
self.dataset = dataset
if parameters is not None:
self.parameters = parameters
self.classify_and_update_parameters(self.parameters)

if init_soc is not None:
self.set_init_soc(init_soc)
self.set_initial_state(initial_state or self.initial_state)

if self._built_model:
return
Expand Down Expand Up @@ -131,30 +134,35 @@ def build(

self.n_states = self._built_model.len_rhs_and_alg # len_rhs + len_alg

def set_init_soc(self, init_soc: float):
def set_initial_state(self, initial_state: Optional[float] = None):
"""
Set the initial state of charge for the battery model.
Set the initial concentrations for the battery model.
Parameters
----------
init_soc : float
The initial state of charge to be used in the model.
"""
if self._built_initial_soc != init_soc:
# reset
self._model_with_set_params = None
self._built_model = None
self.op_conds_to_built_models = None
self.op_conds_to_built_solvers = None

param = self.pybamm_model.param
self._parameter_set = (
self._unprocessed_parameter_set.set_initial_stoichiometries(
init_soc, param=param, inplace=False
initial_state : float or str, optional
If float, this value is used as the initial state of charge (as a fraction between 0
and 1). If str ending in "V", this value is used as the initial open-circuit voltage.
Defaults to None, indicating that the existing initial concentrations will be used.
"""
if initial_state is not None:
self.initial_state = initial_state

if self._built_initial_soc != initial_state:
# reset
self._model_with_set_params = None
self._built_model = None
self.op_conds_to_built_models = None
self.op_conds_to_built_solvers = None

param = self.pybamm_model.param
self._parameter_set = (
self._unprocessed_parameter_set.set_initial_stoichiometries(
initial_state, param=param, inplace=False
)
)
)
# Save solved initial SOC in case we need to rebuild the model
self._built_initial_soc = init_soc
# Save solved initial SOC in case we need to rebuild the model
self._built_initial_soc = initial_state

def set_params(self, rebuild=False):
"""
Expand All @@ -175,13 +183,16 @@ def set_params(self, rebuild=False):
self.parameters is None
or "Current function [A]" not in self.parameters.keys()
):
self._parameter_set["Current function [A]"] = pybamm.Interpolant(
self.current_function = pybamm.Interpolant(
self.dataset["Time [s]"],
self.dataset["Current function [A]"],
pybamm.t,
)
self._parameter_set["Current function [A]"] = self.current_function
# Set t_eval
self.time_data = self._parameter_set["Current function [A]"].x[0]
elif rebuild and self.current_function is not None:
self._parameter_set["Current function [A]"] = self.current_function

self._model_with_set_params = self._parameter_set.process_model(
self._unprocessed_model, inplace=False
Expand All @@ -196,7 +207,7 @@ def rebuild(
parameters: Union[Parameters, dict] = None,
parameter_set: ParameterSet = None,
check_model: bool = True,
init_soc: Optional[float] = None,
initial_state: Optional[float] = None,
) -> None:
"""
Rebuild the PyBaMM model for a given parameter set.
Expand All @@ -216,16 +227,17 @@ def rebuild(
A PyBOP parameter set object or a dictionary containing the parameter values
check_model : bool, optional
If True, the model will be checked for correctness after construction.
init_soc : float, optional
The initial state of charge to be used in simulations.
initial_state : float or str, optional
If float, this value is used as the initial state of charge. If str ending in "V", this
value is used as the initial open-circuit voltage. Defaults to None, indicating that the
initial concentrations in the parameter set should be used.
"""
self.dataset = dataset

if parameters is not None:
self.classify_and_update_parameters(parameters)

if init_soc is not None:
self.set_init_soc(init_soc)
self.set_initial_state(initial_state or self.initial_state)

if self._built_model is None:
raise ValueError("Model must be built before calling rebuild")
Expand Down Expand Up @@ -331,8 +343,8 @@ def step(self, state: TimeSeriesState, time: np.ndarray) -> TimeSeriesState:
return TimeSeriesState(sol=new_sol, inputs=state.inputs, t=time)

def simulate(
self, inputs: Inputs, t_eval: np.array
) -> dict[str, np.ndarray[np.float64]]:
self, inputs: Inputs, t_eval: np.array, initial_state: Optional[float] = None
):
"""
Execute the forward model simulation and return the result.
Expand All @@ -342,6 +354,10 @@ def simulate(
The input parameters for the simulation.
t_eval : array-like
An array of time points at which to evaluate the solution.
initial_state : float or str, optional
If float, this value is used as the initial state of charge (as a fraction between 0
and 1). If str ending in "V", this value is used as the initial open-circuit voltage.
Defaults to None, indicating that the existing initial concentrations will be used.
Returns
-------
Expand All @@ -366,8 +382,11 @@ def simulate(
self.parameters[key].update(value=value)
requires_rebuild = True

if initial_state is not None:
requires_rebuild = True

Check warning on line 386 in pybop/models/base_model.py

View check run for this annotation

Codecov / codecov/patch

pybop/models/base_model.py#L386

Added line #L386 was not covered by tests

if requires_rebuild:
self.rebuild(parameters=self.parameters)
self.rebuild(parameters=self.parameters, initial_state=initial_state)

if self.check_params(
inputs=inputs,
Expand All @@ -381,7 +400,9 @@ def simulate(
else:
return [np.inf]

def simulateS1(self, inputs: Inputs, t_eval: np.array):
def simulateS1(
self, inputs: Inputs, t_eval: np.array, initial_state: Optional[float] = None
):
"""
Perform the forward model simulation with sensitivities.
Expand All @@ -392,6 +413,10 @@ def simulateS1(self, inputs: Inputs, t_eval: np.array):
t_eval : array-like
An array of time points at which to evaluate the solution and its
sensitivities.
initial_state : float or str, optional
If float, this value is used as the initial state of charge (as a fraction between 0
and 1). If str ending in "V", this value is used as the initial open-circuit voltage.
Defaults to None, indicating that the existing initial concentrations will be used.
Returns
-------
Expand All @@ -408,7 +433,7 @@ def simulateS1(self, inputs: Inputs, t_eval: np.array):
if self._built_model is None:
raise ValueError("Model must be built before calling simulate")

if self.rebuild_parameters:
if self.rebuild_parameters or initial_state is not None:
raise ValueError(

Check warning on line 437 in pybop/models/base_model.py

View check run for this annotation

Codecov / codecov/patch

pybop/models/base_model.py#L437

Added line #L437 was not covered by tests
"Cannot use sensitivies for parameters which require a model rebuild"
)
Expand Down Expand Up @@ -437,7 +462,7 @@ def predict(
t_eval: np.array = None,
parameter_set: ParameterSet = None,
experiment: Experiment = None,
init_soc: Optional[float] = None,
initial_state: Optional[float] = None,
) -> dict[str, np.ndarray[np.float64]]:
"""
Solve the model using PyBaMM's simulation framework and return the solution.
Expand All @@ -460,9 +485,10 @@ def predict(
experiment : pybamm.Experiment, optional
A PyBaMM Experiment object specifying the experimental conditions under which
the simulation should be run. Defaults to None, indicating no experiment.
init_soc : float, optional
The initial state of charge for the simulation, as a fraction (between 0 and 1).
Defaults to None.
initial_state : float or str, optional
If float, this value is used as the initial state of charge (as a fraction between 0
and 1). If str ending in "V", this value is used as the initial open-circuit voltage.
Defaults to None, indicating that the existing initial concentrations will be used.
Returns
-------
Expand All @@ -485,6 +511,9 @@ def predict(
if inputs is not None:
parameter_set.update(inputs)

# Update the model.initial_state for consistency
self.set_initial_state(initial_state)

if self.check_params(
inputs=inputs,
parameter_set=parameter_set,
Expand All @@ -495,13 +524,13 @@ def predict(
return pybamm.Simulation(
self._unprocessed_model,
parameter_values=parameter_set,
).solve(t_eval=t_eval, initial_soc=init_soc)
).solve(t_eval=t_eval, initial_soc=initial_state)
else:
return pybamm.Simulation(
self._unprocessed_model,
experiment=experiment,
parameter_values=parameter_set,
).solve(initial_soc=init_soc)
).solve(initial_soc=initial_state)
else:
raise ValueError(
"This sim method currently only supports PyBaMM models"
Expand Down
13 changes: 7 additions & 6 deletions pybop/observers/observer.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ class Observer(BaseProblem):
The signal to observe.
additional_variables : List[str], optional
Additional variables to observe and store in the solution (default: []).
init_soc : float, optional
Initial state of charge (default: None).
init_ocv : float, optional
Initial open-circuit voltage (default: None).
"""

# define a subtype for covariance matrices for use by derived classes
Expand All @@ -40,15 +40,13 @@ def __init__(
check_model=True,
signal=None,
additional_variables=None,
init_soc=None,
init_ocv=None,
) -> None:
if additional_variables is None:
additional_variables = []
if signal is None:
signal = ["Voltage [V]"]
super().__init__(
parameters, model, check_model, signal, additional_variables, init_soc
)
super().__init__(parameters, model, check_model, signal, additional_variables)
if model._built_model is None:
raise ValueError("Only built models can be used in Observers")

Expand All @@ -57,6 +55,9 @@ def __init__(
self._model = model
self._signal = self.signal
self._n_outputs = len(self._signal)
self.init_ocv = None
if init_ocv is not None:
self.init_ocv = str(init_ocv) + "V"

Check warning on line 60 in pybop/observers/observer.py

View check run for this annotation

Codecov / codecov/patch

pybop/observers/observer.py#L60

Added line #L60 was not covered by tests

def reset(self, inputs: Inputs) -> None:
inputs = self.parameters.verify(inputs)
Expand Down
Loading

0 comments on commit 4e92192

Please sign in to comment.