From c685c4a76d8acfd9cbd384aa9894660c3c9943d9 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Wed, 20 Mar 2024 16:54:56 -0700 Subject: [PATCH 01/10] get working for half cell case --- .../lithium_ion/electrode_soh_half_cell.py | 16 ++++--- pybamm/parameters/parameter_values.py | 4 +- pybamm/simulation.py | 43 +++++++++++-------- .../test_lithium_ion/test_electrode_soh.py | 8 +++- tests/unit/test_simulation.py | 21 +++++++++ 5 files changed, 67 insertions(+), 25 deletions(-) diff --git a/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py b/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py index 92cbc73dcb..1b8cc7b9bd 100644 --- a/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py +++ b/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py @@ -28,7 +28,7 @@ def __init__(self, name="ElectrodeSOH model"): x_100 = pybamm.Variable("x_100", bounds=(0, 1)) x_0 = pybamm.Variable("x_0", bounds=(0, 1)) - Q_w = pybamm.InputParameter("Q_w") + Q_w = pybamm.Parameter("Total lithium in the working electrode [mol]") T_ref = param.T_ref U_w = param.p.prim.U Q = Q_w * (x_100 - x_0) @@ -64,6 +64,7 @@ def get_initial_stoichiometry_half_cell( param=None, known_value="cyclable lithium capacity", options=None, + inputs=None, ): """ Calculate initial stoichiometry to start off the simulation at a particular @@ -86,7 +87,7 @@ def get_initial_stoichiometry_half_cell( The initial stoichiometry that give the desired initial state of charge """ param = pybamm.LithiumIonParameters(options) - x_0, x_100 = get_min_max_stoichiometries(parameter_values) + x_0, x_100 = get_min_max_stoichiometries(parameter_values, inputs=inputs) if isinstance(initial_value, str) and initial_value.endswith("V"): V_init = float(initial_value[:-1]) @@ -129,7 +130,7 @@ def get_initial_stoichiometry_half_cell( return x -def get_min_max_stoichiometries(parameter_values, options=None): +def get_min_max_stoichiometries(parameter_values, options=None, inputs=None): """ Get the minimum and maximum stoichiometries from the parameter values @@ -142,12 +143,17 @@ def get_min_max_stoichiometries(parameter_values, options=None): :class:`pybamm.BatteryModelOptions`. If None, the default is used: {"working electrode": "positive"} """ + inputs = inputs or {} if options is None: options = {"working electrode": "positive"} esoh_model = pybamm.lithium_ion.ElectrodeSOHHalfCell("ElectrodeSOH") param = pybamm.LithiumIonParameters(options) + #Use Q_w as a symbol rather than a value + Q_w = param.p.Q_init + #Q_w = parameter_values.evaluate(param.p.Q_init) + # Add Q_w to parameter values + parameter_values.update({"Total lithium in the working electrode [mol]": Q_w}, check_already_exists=False) esoh_sim = pybamm.Simulation(esoh_model, parameter_values=parameter_values) - Q_w = parameter_values.evaluate(param.p.Q_init) - esoh_sol = esoh_sim.solve([0], inputs={"Q_w": Q_w}) + esoh_sol = esoh_sim.solve([0], inputs=inputs) x_0, x_100 = esoh_sol["x_0"].data[0], esoh_sol["x_100"].data[0] return x_0, x_100 diff --git a/pybamm/parameters/parameter_values.py b/pybamm/parameters/parameter_values.py index f2dd9ec630..7a7ed60119 100644 --- a/pybamm/parameters/parameter_values.py +++ b/pybamm/parameters/parameter_values.py @@ -292,6 +292,7 @@ def set_initial_stoichiometry_half_cell( known_value="cyclable lithium capacity", inplace=True, options=None, + inputs=None ): """ Set the initial stoichiometry of the working electrode, based on the initial @@ -299,7 +300,7 @@ def set_initial_stoichiometry_half_cell( """ param = param or pybamm.LithiumIonParameters(options) x = pybamm.lithium_ion.get_initial_stoichiometry_half_cell( - initial_value, self, param=param, known_value=known_value, options=options + initial_value, self, param=param, known_value=known_value, options=options, inputs=inputs ) if inplace: parameter_values = self @@ -324,6 +325,7 @@ def set_initial_stoichiometries( known_value="cyclable lithium capacity", inplace=True, options=None, + inputs=None, tol=1e-6, ): """ diff --git a/pybamm/simulation.py b/pybamm/simulation.py index 3dfdac94b5..f0e61df796 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -336,7 +336,7 @@ def set_parameters(self): self._parameter_values.process_geometry(self._geometry) self._model = self._model_with_set_params - def set_initial_soc(self, initial_soc): + def set_initial_soc(self, initial_soc, inputs=None): if self._built_initial_soc != initial_soc: # reset self._model_with_set_params = None @@ -355,20 +355,20 @@ def set_initial_soc(self, initial_soc): elif options["working electrode"] == "positive": self._parameter_values = ( self._unprocessed_parameter_values.set_initial_stoichiometry_half_cell( - initial_soc, param=param, inplace=False, options=options + initial_soc, param=param, inplace=False, options=options, inputs=inputs ) ) else: self._parameter_values = ( self._unprocessed_parameter_values.set_initial_stoichiometries( - initial_soc, param=param, inplace=False, options=options + initial_soc, param=param, inplace=False, options=options, inputs=inputs ) ) # Save solved initial SOC in case we need to re-build the model self._built_initial_soc = initial_soc - def build(self, check_model=True, initial_soc=None): + def build(self, check_model=True, initial_soc=None, inputs=None): """ A method to build the model into a system of matrices and vectors suitable for performing numerical computations. If the model has already been built or @@ -387,7 +387,7 @@ def build(self, check_model=True, initial_soc=None): set. """ if initial_soc is not None: - self.set_initial_soc(initial_soc) + self.set_initial_soc(initial_soc, inputs=inputs) if self.built_model: return @@ -404,13 +404,13 @@ def build(self, check_model=True, initial_soc=None): # rebuilt model so clear solver setup self._solver._model_set_up = {} - def build_for_experiment(self, check_model=True, initial_soc=None): + def build_for_experiment(self, check_model=True, initial_soc=None, inputs=None): """ Similar to :meth:`Simulation.build`, but for the case of simulating an experiment, where there may be several models and solvers to build. """ if initial_soc is not None: - self.set_initial_soc(initial_soc) + self.set_initial_soc(initial_soc, inputs) if self.op_conds_to_built_models: return @@ -450,6 +450,7 @@ def solve( initial_soc=None, callbacks=None, showprogress=False, + inputs=None, **kwargs, ): """ @@ -512,8 +513,10 @@ def solve( callbacks = pybamm.callbacks.setup_callbacks(callbacks) logs = {} + inputs = inputs or {} + if self.operating_mode in ["without experiment", "drive cycle"]: - self.build(check_model=check_model, initial_soc=initial_soc) + self.build(check_model=check_model, initial_soc=initial_soc, inputs=inputs) if save_at_cycles is not None: raise ValueError( "'save_at_cycles' option can only be used if simulating an " @@ -584,11 +587,13 @@ def solve( stacklevel=2, ) - self._solution = solver.solve(self.built_model, t_eval, **kwargs) + self._solution = solver.solve(self.built_model, t_eval, inputs=inputs, **kwargs) elif self.operating_mode == "with experiment": callbacks.on_experiment_start(logs) - self.build_for_experiment(check_model=check_model, initial_soc=initial_soc) + self.build_for_experiment( + check_model=check_model, initial_soc=initial_soc, inputs=inputs + ) if t_eval is not None: pybamm.logger.warning( "Ignoring t_eval as solution times are specified by the experiment" @@ -597,7 +602,7 @@ def solve( # inputs without having to build the simulation again self._solution = starting_solution # Step through all experimental conditions - user_inputs = kwargs.get("inputs", {}) + user_inputs = inputs timer = pybamm.Timer() # Set up eSOH solver (for summary variables) @@ -673,7 +678,7 @@ def solve( # logs["step operating conditions"] = "Initial rest for padding" # callbacks.on_step_start(logs) - kwargs["inputs"] = { + inputs = { **user_inputs, "Ambient temperature [K]": ( op_conds.temperature or self._original_temperature @@ -684,7 +689,7 @@ def solve( step_solution = current_solution.cycles[-1].steps[-1] step_solution_with_rest = self.run_padding_rest( - kwargs, rest_time, step_solution + kwargs, rest_time, step_solution, inputs ) steps[-1] = step_solution + step_solution_with_rest @@ -778,7 +783,7 @@ def solve( logs["step operating conditions"] = op_conds_str callbacks.on_step_start(logs) - kwargs["inputs"] = { + inputs = { **user_inputs, "start time": start_time, } @@ -791,6 +796,7 @@ def solve( dt, t_eval=np.linspace(0, dt, npts), save=False, + inputs=inputs, **kwargs, ) except pybamm.SolverError as error: @@ -827,7 +833,7 @@ def solve( logs["step operating conditions"] = "Rest for padding" callbacks.on_step_start(logs) - kwargs["inputs"] = { + inputs = { **user_inputs, "Ambient temperature [K]": ( op_conds.temperature or self._original_temperature @@ -836,7 +842,7 @@ def solve( } step_solution_with_rest = self.run_padding_rest( - kwargs, rest_time, step_solution + kwargs, rest_time, step_solution, inputs=inputs ) step_solution += step_solution_with_rest @@ -958,7 +964,7 @@ def solve( return self.solution - def run_padding_rest(self, kwargs, rest_time, step_solution): + def run_padding_rest(self, kwargs, rest_time, step_solution, inputs): model = self.op_conds_to_built_models["Rest for padding"] solver = self.op_conds_to_built_solvers["Rest for padding"] @@ -972,6 +978,7 @@ def run_padding_rest(self, kwargs, rest_time, step_solution): rest_time, t_eval=np.linspace(0, rest_time, npts), save=False, + inputs=inputs, **kwargs, ) @@ -984,6 +991,7 @@ def step( t_eval=None, save=True, starting_solution=None, + inputs=None, **kwargs, ): """ @@ -1024,6 +1032,7 @@ def step( dt, t_eval=t_eval, save=save, + inputs=inputs, **kwargs, ) diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py index bebdedd825..b5d551793a 100644 --- a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py +++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py @@ -238,14 +238,18 @@ def test_known_solution(self): param = pybamm.LithiumIonParameters({"working electrode": "positive"}) parameter_values = pybamm.ParameterValues("Xu2019") + Q_w = param.p.Q_init + #Q_w = parameter_values.evaluate(param.p.Q_init) + # Add Q_w to parameter values + parameter_values.update({"Total lithium in the working electrode [mol]": Q_w}, check_already_exists=False) sim = pybamm.Simulation(model, parameter_values=parameter_values) V_min = 3.5 V_max = 4.2 - Q_w = parameter_values.evaluate(param.p.Q_init) + #Q_w = parameter_values.evaluate(param.p.Q_init) # Solve the model and check outputs - sol = sim.solve([0], inputs={"Q_w": Q_w}) + sol = sim.solve([0], inputs={}) self.assertAlmostEqual(sol["Uw(x_100)"].data[0], V_max, places=5) self.assertAlmostEqual(sol["Uw(x_0)"].data[0], V_min, places=5) diff --git a/tests/unit/test_simulation.py b/tests/unit/test_simulation.py index 734f22eb35..1b0aae245f 100644 --- a/tests/unit/test_simulation.py +++ b/tests/unit/test_simulation.py @@ -204,6 +204,16 @@ def test_solve_with_initial_soc(self): sim.build(initial_soc=0.5) self.assertEqual(sim._built_initial_soc, 0.5) + # Test that initial soc works with a relevant input parameter + #model = pybamm.lithium_ion.DFN() + #param = model.default_parameter_values + #param["Positive electrode active material volume fraction"] = ( + # pybamm.InputParameter("eps_p") + #) + #sim = pybamm.Simulation(model, parameter_values=param) + #sim.solve(t_eval=[0, 1], initial_soc=0.8, inputs={"eps_p": 1e-10}) + #self.assertEqual(sim._built_initial_soc, 0.8) + # Test whether initial_soc works with half cell (solve) options = {"working electrode": "positive"} model = pybamm.lithium_ion.DFN(options) @@ -231,6 +241,17 @@ def test_solve_with_initial_soc(self): voltage = sol["Terminal voltage [V]"].entries self.assertAlmostEqual(voltage[0], ucv, places=5) + # Test that initial soc works with a relevant input parameter + model = pybamm.lithium_ion.DFN({"working electrode": "positive"}) + param = model.default_parameter_values + original_eps_p = param["Positive electrode active material volume fraction"] + param["Positive electrode active material volume fraction"] = ( + pybamm.InputParameter("eps_p") + ) + sim = pybamm.Simulation(model, parameter_values=param) + sim.solve(t_eval=[0, 1], initial_soc=0.8, inputs={"eps_p": original_eps_p}) + self.assertEqual(sim._built_initial_soc, 0.8) + # test with MSMR model = pybamm.lithium_ion.MSMR({"number of MSMR reactions": ("6", "4")}) param = pybamm.ParameterValues("MSMR_Example") From 277e62323f4b7f474bccedec35ad7bd8483a4a27 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Fri, 22 Mar 2024 15:43:19 -0700 Subject: [PATCH 02/10] wrap up esoh --- .../lithium_ion/electrode_soh.py | 41 ++++++++++-------- .../lithium_ion/electrode_soh_half_cell.py | 9 ++-- pybamm/parameters/parameter_values.py | 22 ++++++++-- pybamm/simulation.py | 22 ++++++++-- pybamm/solvers/solution.py | 16 ++++--- .../test_lithium_ion/test_electrode_soh.py | 9 ++-- tests/unit/test_simulation.py | 43 +++++++++++++++---- 7 files changed, 117 insertions(+), 45 deletions(-) diff --git a/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py b/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py index ad8ee0633d..d47ef1c00e 100644 --- a/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py +++ b/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py @@ -380,7 +380,8 @@ def solve(self, inputs): # Calculate theoretical energy # TODO: energy calc for MSMR if self.options["open-circuit potential"] != "MSMR": - energy = self.theoretical_energy_integral(sol_dict) + energy_inputs = {**sol_dict, **inputs} + energy = self.theoretical_energy_integral(energy_inputs) sol_dict.update({"Maximum theoretical energy [W.h]": energy}) return sol_dict @@ -604,12 +605,10 @@ def _check_esoh_feasible(self, inputs): else: # address numpy 1.25 deprecation warning: array should have ndim=0 # before conversion - V_lower_bound = float( - self.OCV_function.evaluate(inputs={"x": x0_min, "y": y0_max}).item() - ) - V_upper_bound = float( - self.OCV_function.evaluate(inputs={"x": x100_max, "y": y100_min}).item() - ) + inputs.update({"x": x0_min, "y": y0_max}, check_already_exists=False) + V_lower_bound = float(self.OCV_function.evaluate(inputs=inputs).item()) + inputs.update({"x": x100_max, "y": y100_min}) + V_upper_bound = float(self.OCV_function.evaluate(inputs=inputs).item()) # Check that the min and max achievable voltages span wider than the desired # voltage range @@ -632,7 +631,7 @@ def _check_esoh_feasible(self, inputs): ) ) - def get_initial_stoichiometries(self, initial_value, tol=1e-6): + def get_initial_stoichiometries(self, initial_value, tol=1e-6, inputs=None): """ Calculate initial stoichiometries to start off the simulation at a particular state of charge, given voltage limits, open-circuit potentials, etc defined by @@ -657,7 +656,7 @@ def get_initial_stoichiometries(self, initial_value, tol=1e-6): """ parameter_values = self.parameter_values param = self.param - x_0, x_100, y_100, y_0 = self.get_min_max_stoichiometries() + x_0, x_100, y_100, y_0 = self.get_min_max_stoichiometries(inputs=inputs) if isinstance(initial_value, str) and initial_value.endswith("V"): V_init = float(initial_value[:-1]) @@ -714,7 +713,7 @@ def get_initial_stoichiometries(self, initial_value, tol=1e-6): return x, y - def get_min_max_stoichiometries(self): + def get_min_max_stoichiometries(self, inputs=None): """ Calculate min/max stoichiometries given voltage limits, open-circuit potentials, etc defined by parameter_values @@ -724,18 +723,23 @@ def get_min_max_stoichiometries(self): x_0, x_100, y_100, y_0 The min/max stoichiometries """ + inputs = inputs or {} parameter_values = self.parameter_values param = self.param - Q_n = parameter_values.evaluate(param.n.Q_init) - Q_p = parameter_values.evaluate(param.p.Q_init) + Q_n = parameter_values.evaluate(param.n.Q_init, inputs=inputs) + Q_p = parameter_values.evaluate(param.p.Q_init, inputs=inputs) if self.known_value == "cyclable lithium capacity": - Q_Li = parameter_values.evaluate(param.Q_Li_particles_init) - inputs = {"Q_n": Q_n, "Q_p": Q_p, "Q_Li": Q_Li} + Q_Li = parameter_values.evaluate(param.Q_Li_particles_init, inputs=inputs) + inputs.update( + {"Q_n": Q_n, "Q_p": Q_p, "Q_Li": Q_Li}, check_already_exists=False + ) elif self.known_value == "cell capacity": - Q = parameter_values.evaluate(param.Q / param.n_electrodes_parallel) - inputs = {"Q_n": Q_n, "Q_p": Q_p, "Q": Q} + Q = parameter_values.evaluate( + param.Q / param.n_electrodes_parallel, inputs=inputs + ) + inputs.update({"Q_n": Q_n, "Q_p": Q_p, "Q": Q}, check_already_exists=False) # Solve the model and check outputs sol = self.solve(inputs) return [sol["x_0"], sol["x_100"], sol["y_100"], sol["y_0"]] @@ -814,7 +818,7 @@ def theoretical_energy_integral(self, inputs, points=1000): param = self.param T = param.T_amb_av(0) Vs = self.parameter_values.evaluate( - param.p.prim.U(y_vals, T) - param.n.prim.U(x_vals, T) + param.p.prim.U(y_vals, T) - param.n.prim.U(x_vals, T), inputs=inputs ).flatten() # Calculate dQ Q = Q_p * (y_0 - y_100) @@ -831,6 +835,7 @@ def get_initial_stoichiometries( known_value="cyclable lithium capacity", options=None, tol=1e-6, + inputs=None, ): """ Calculate initial stoichiometries to start off the simulation at a particular @@ -866,7 +871,7 @@ def get_initial_stoichiometries( The initial stoichiometries that give the desired initial state of charge """ esoh_solver = ElectrodeSOHSolver(parameter_values, param, known_value, options) - return esoh_solver.get_initial_stoichiometries(initial_value, tol) + return esoh_solver.get_initial_stoichiometries(initial_value, tol, inputs=inputs) def get_min_max_stoichiometries( diff --git a/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py b/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py index 1b8cc7b9bd..acf68fe573 100644 --- a/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py +++ b/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py @@ -148,11 +148,14 @@ def get_min_max_stoichiometries(parameter_values, options=None, inputs=None): options = {"working electrode": "positive"} esoh_model = pybamm.lithium_ion.ElectrodeSOHHalfCell("ElectrodeSOH") param = pybamm.LithiumIonParameters(options) - #Use Q_w as a symbol rather than a value + # Use Q_w as a symbol rather than a value Q_w = param.p.Q_init - #Q_w = parameter_values.evaluate(param.p.Q_init) + # Q_w = parameter_values.evaluate(param.p.Q_init) # Add Q_w to parameter values - parameter_values.update({"Total lithium in the working electrode [mol]": Q_w}, check_already_exists=False) + parameter_values.update( + {"Total lithium in the working electrode [mol]": Q_w}, + check_already_exists=False, + ) esoh_sim = pybamm.Simulation(esoh_model, parameter_values=parameter_values) esoh_sol = esoh_sim.solve([0], inputs=inputs) x_0, x_100 = esoh_sol["x_0"].data[0], esoh_sol["x_100"].data[0] diff --git a/pybamm/parameters/parameter_values.py b/pybamm/parameters/parameter_values.py index 7a7ed60119..402000b5a1 100644 --- a/pybamm/parameters/parameter_values.py +++ b/pybamm/parameters/parameter_values.py @@ -292,7 +292,7 @@ def set_initial_stoichiometry_half_cell( known_value="cyclable lithium capacity", inplace=True, options=None, - inputs=None + inputs=None, ): """ Set the initial stoichiometry of the working electrode, based on the initial @@ -300,7 +300,12 @@ def set_initial_stoichiometry_half_cell( """ param = param or pybamm.LithiumIonParameters(options) x = pybamm.lithium_ion.get_initial_stoichiometry_half_cell( - initial_value, self, param=param, known_value=known_value, options=options, inputs=inputs + initial_value, + self, + param=param, + known_value=known_value, + options=options, + inputs=inputs, ) if inplace: parameter_values = self @@ -340,6 +345,7 @@ def set_initial_stoichiometries( known_value=known_value, options=options, tol=tol, + inputs=inputs, ) if inplace: parameter_values = self @@ -771,7 +777,7 @@ def _process_symbol(self, symbol): # Backup option: return the object return symbol - def evaluate(self, symbol): + def evaluate(self, symbol, inputs=None): """ Process and evaluate a symbol. @@ -789,7 +795,15 @@ def evaluate(self, symbol): if processed_symbol.is_constant(): return processed_symbol.evaluate() else: - raise ValueError("symbol must evaluate to a constant scalar or array") + # In the case that the only issue is an input parameter contained in inputs, + # go ahead and try and evaluate it with the inputs. If it doesn't work, raise + # the value error. + try: + return processed_symbol.evaluate(inputs=inputs) + except Exception as exc: + raise ValueError( + "symbol must evaluate to a constant scalar or array" + ) from exc def _ipython_key_completions_(self): return list(self._dict_items.keys()) diff --git a/pybamm/simulation.py b/pybamm/simulation.py index f0e61df796..d4faa5c47c 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -355,13 +355,21 @@ def set_initial_soc(self, initial_soc, inputs=None): elif options["working electrode"] == "positive": self._parameter_values = ( self._unprocessed_parameter_values.set_initial_stoichiometry_half_cell( - initial_soc, param=param, inplace=False, options=options, inputs=inputs + initial_soc, + param=param, + inplace=False, + options=options, + inputs=inputs, ) ) else: self._parameter_values = ( self._unprocessed_parameter_values.set_initial_stoichiometries( - initial_soc, param=param, inplace=False, options=options, inputs=inputs + initial_soc, + param=param, + inplace=False, + options=options, + inputs=inputs, ) ) @@ -587,7 +595,9 @@ def solve( stacklevel=2, ) - self._solution = solver.solve(self.built_model, t_eval, inputs=inputs, **kwargs) + self._solution = solver.solve( + self.built_model, t_eval, inputs=inputs, **kwargs + ) elif self.operating_mode == "with experiment": callbacks.on_experiment_start(logs) @@ -621,6 +631,7 @@ def solve( [starting_solution], esoh_solver=esoh_solver, save_this_cycle=True, + inputs=user_inputs, ) starting_solution_cycles = [cycle_solution] starting_solution_summary_variables = [cycle_sum_vars] @@ -908,7 +919,10 @@ def solve( "due to exceeded bounds at initial conditions." ) cycle_sol = pybamm.make_cycle_solution( - steps, esoh_solver=esoh_solver, save_this_cycle=save_this_cycle + steps, + esoh_solver=esoh_solver, + save_this_cycle=save_this_cycle, + inputs=user_inputs, ) cycle_solution, cycle_sum_vars, cycle_first_state = cycle_sol all_cycle_solutions.append(cycle_solution) diff --git a/pybamm/solvers/solution.py b/pybamm/solvers/solution.py index b456bfb2d0..a52441dc8c 100644 --- a/pybamm/solvers/solution.py +++ b/pybamm/solvers/solution.py @@ -867,7 +867,9 @@ def copy(self): return EmptySolution(termination=self.termination, t=self.t) -def make_cycle_solution(step_solutions, esoh_solver=None, save_this_cycle=True): +def make_cycle_solution( + step_solutions, esoh_solver=None, save_this_cycle=True, inputs=None +): """ Function to create a Solution for an entire cycle, and associated summary variables @@ -913,7 +915,9 @@ def make_cycle_solution(step_solutions, esoh_solver=None, save_this_cycle=True): cycle_solution.steps = step_solutions - cycle_summary_variables = _get_cycle_summary_variables(cycle_solution, esoh_solver) + cycle_summary_variables = _get_cycle_summary_variables( + cycle_solution, esoh_solver, user_inputs=inputs + ) cycle_first_state = cycle_solution.first_state @@ -925,7 +929,8 @@ def make_cycle_solution(step_solutions, esoh_solver=None, save_this_cycle=True): return cycle_solution, cycle_summary_variables, cycle_first_state -def _get_cycle_summary_variables(cycle_solution, esoh_solver): +def _get_cycle_summary_variables(cycle_solution, esoh_solver, user_inputs=None): + user_inputs = user_inputs or {} model = cycle_solution.all_models[0] cycle_summary_variables = pybamm.FuzzyDict({}) @@ -974,10 +979,11 @@ def _get_cycle_summary_variables(cycle_solution, esoh_solver): Q_p = last_state["Positive electrode capacity [A.h]"].data[0] Q_Li = last_state["Total lithium capacity in particles [A.h]"].data[0] - inputs = {"Q_n": Q_n, "Q_p": Q_p, "Q_Li": Q_Li} + esoh_inputs = {"Q_n": Q_n, "Q_p": Q_p, "Q_Li": Q_Li} + user_inputs.update(esoh_inputs) try: - esoh_sol = esoh_solver.solve(inputs) + esoh_sol = esoh_solver.solve(inputs=user_inputs) except pybamm.SolverError as error: # pragma: no cover raise pybamm.SolverError( "Could not solve for summary variables, run " diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py index b5d551793a..b56cf0661f 100644 --- a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py +++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py @@ -239,14 +239,17 @@ def test_known_solution(self): param = pybamm.LithiumIonParameters({"working electrode": "positive"}) parameter_values = pybamm.ParameterValues("Xu2019") Q_w = param.p.Q_init - #Q_w = parameter_values.evaluate(param.p.Q_init) + # Q_w = parameter_values.evaluate(param.p.Q_init) # Add Q_w to parameter values - parameter_values.update({"Total lithium in the working electrode [mol]": Q_w}, check_already_exists=False) + parameter_values.update( + {"Total lithium in the working electrode [mol]": Q_w}, + check_already_exists=False, + ) sim = pybamm.Simulation(model, parameter_values=parameter_values) V_min = 3.5 V_max = 4.2 - #Q_w = parameter_values.evaluate(param.p.Q_init) + # Q_w = parameter_values.evaluate(param.p.Q_init) # Solve the model and check outputs sol = sim.solve([0], inputs={}) diff --git a/tests/unit/test_simulation.py b/tests/unit/test_simulation.py index 1b0aae245f..253f84b741 100644 --- a/tests/unit/test_simulation.py +++ b/tests/unit/test_simulation.py @@ -205,14 +205,41 @@ def test_solve_with_initial_soc(self): self.assertEqual(sim._built_initial_soc, 0.5) # Test that initial soc works with a relevant input parameter - #model = pybamm.lithium_ion.DFN() - #param = model.default_parameter_values - #param["Positive electrode active material volume fraction"] = ( - # pybamm.InputParameter("eps_p") - #) - #sim = pybamm.Simulation(model, parameter_values=param) - #sim.solve(t_eval=[0, 1], initial_soc=0.8, inputs={"eps_p": 1e-10}) - #self.assertEqual(sim._built_initial_soc, 0.8) + model = pybamm.lithium_ion.DFN() + param = model.default_parameter_values + og_eps_p = param["Positive electrode active material volume fraction"] + param["Positive electrode active material volume fraction"] = ( + pybamm.InputParameter("eps_p") + ) + sim = pybamm.Simulation(model, parameter_values=param) + sim.solve(t_eval=[0, 1], initial_soc=0.8, inputs={"eps_p": og_eps_p}) + self.assertEqual(sim._built_initial_soc, 0.8) + + model = pybamm.lithium_ion.DFN() + parameter_values = pybamm.ParameterValues("Chen2020") + + def graphite_LGM50_ocp_Chen2020(sto): + a = pybamm.Parameter("a") + u_eq = a * ( + 1.9793 * pybamm.exp(-39.3631 * sto) + + 0.2482 + - 0.0909 * pybamm.tanh(29.8538 * (sto - 0.1234)) + - 0.04478 * pybamm.tanh(14.9159 * (sto - 0.2769)) + - 0.0205 * pybamm.tanh(30.4444 * (sto - 0.6103)) + ) + return u_eq + + parameter_values.update( + { + "Negative electrode OCP [V]": graphite_LGM50_ocp_Chen2020, + } + ) + parameter_values.update({"a": "[input]"}, check_already_exists=False) + experiment = pybamm.Experiment(["Discharge at 1C until 2.5 V"]) + sim = pybamm.Simulation( + model, parameter_values=parameter_values, experiment=experiment + ) + sim.solve([0, 3600], inputs={"a": 1}) # Test whether initial_soc works with half cell (solve) options = {"working electrode": "positive"} From 2b8ec0cabd1cdd156905b404e810faf92c98d9ae Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Fri, 22 Mar 2024 15:56:16 -0700 Subject: [PATCH 03/10] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17bd234ca0..83f00ad17b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Updated `_steps_util.py` to throw a specific exception when drive cycle starts at t>0 ([#3756](https://github.com/pybamm-team/PyBaMM/pull/3756)) - Updated `plot_voltage_components.py` to support both `Simulation` and `Solution` objects. Added new methods in both `Simulation` and `Solution` classes for allow the syntax `simulation.plot_voltage_components` and `solution.plot_voltage_components`. Updated `test_plot_voltage_components.py` to reflect these changes ([#3723](https://github.com/pybamm-team/PyBaMM/pull/3723)). - The SEI thickness decreased at some intervals when the 'electron-migration limited' model was used. It has been corrected ([#3622](https://github.com/pybamm-team/PyBaMM/pull/3622)) +- Allow input parameters in ESOH model ([#3921](https://github.com/pybamm-team/PyBaMM/pull/3921)) ## Optimizations From ff6fe4b25aabd3e77ed919485cca76641ba9feb3 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Sat, 23 Mar 2024 14:59:32 -0700 Subject: [PATCH 04/10] valentin comments --- .../lithium_ion/electrode_soh.py | 18 +++++++-------- .../lithium_ion/electrode_soh_half_cell.py | 15 ++++-------- pybamm/solvers/solution.py | 7 ++---- .../test_lithium_ion/test_electrode_soh.py | 14 ++--------- tests/unit/test_simulation.py | 23 +++++++------------ 5 files changed, 26 insertions(+), 51 deletions(-) diff --git a/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py b/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py index d47ef1c00e..3169404cd1 100644 --- a/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py +++ b/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py @@ -605,10 +605,10 @@ def _check_esoh_feasible(self, inputs): else: # address numpy 1.25 deprecation warning: array should have ndim=0 # before conversion - inputs.update({"x": x0_min, "y": y0_max}, check_already_exists=False) - V_lower_bound = float(self.OCV_function.evaluate(inputs=inputs).item()) - inputs.update({"x": x100_max, "y": y100_min}) - V_upper_bound = float(self.OCV_function.evaluate(inputs=inputs).item()) + all_inputs = {**inputs, "x": x0_min, "y": y0_max} + V_lower_bound = float(self.OCV_function.evaluate(inputs=all_inputs).item()) + all_inputs.update({"x": x100_max, "y": y100_min}) + V_upper_bound = float(self.OCV_function.evaluate(inputs=all_inputs).item()) # Check that the min and max achievable voltages span wider than the desired # voltage range @@ -732,16 +732,16 @@ def get_min_max_stoichiometries(self, inputs=None): if self.known_value == "cyclable lithium capacity": Q_Li = parameter_values.evaluate(param.Q_Li_particles_init, inputs=inputs) - inputs.update( - {"Q_n": Q_n, "Q_p": Q_p, "Q_Li": Q_Li}, check_already_exists=False - ) + all_inputs = {**inputs, "Q_n": Q_n, "Q_p": Q_p, "Q_Li": Q_Li} elif self.known_value == "cell capacity": Q = parameter_values.evaluate( param.Q / param.n_electrodes_parallel, inputs=inputs ) - inputs.update({"Q_n": Q_n, "Q_p": Q_p, "Q": Q}, check_already_exists=False) + all_inputs = {**inputs, "Q_n": Q_n, "Q_p": Q_p, "Q": Q} + else: + all_inputs = inputs # Solve the model and check outputs - sol = self.solve(inputs) + sol = self.solve(all_inputs) return [sol["x_0"], sol["x_100"], sol["y_100"], sol["y_0"]] def get_initial_ocps(self, initial_value, tol=1e-6): diff --git a/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py b/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py index acf68fe573..0b2d7bd14c 100644 --- a/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py +++ b/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py @@ -28,7 +28,7 @@ def __init__(self, name="ElectrodeSOH model"): x_100 = pybamm.Variable("x_100", bounds=(0, 1)) x_0 = pybamm.Variable("x_0", bounds=(0, 1)) - Q_w = pybamm.Parameter("Total lithium in the working electrode [mol]") + Q_w = pybamm.InputParameter("Q_w") T_ref = param.T_ref U_w = param.p.prim.U Q = Q_w * (x_100 - x_0) @@ -148,15 +148,10 @@ def get_min_max_stoichiometries(parameter_values, options=None, inputs=None): options = {"working electrode": "positive"} esoh_model = pybamm.lithium_ion.ElectrodeSOHHalfCell("ElectrodeSOH") param = pybamm.LithiumIonParameters(options) - # Use Q_w as a symbol rather than a value - Q_w = param.p.Q_init - # Q_w = parameter_values.evaluate(param.p.Q_init) - # Add Q_w to parameter values - parameter_values.update( - {"Total lithium in the working electrode [mol]": Q_w}, - check_already_exists=False, - ) + Q_w = parameter_values.evaluate(param.p.Q_init, inputs=inputs) + # Add Q_w to input parameters + all_inputs = {**inputs, "Q_w": Q_w} esoh_sim = pybamm.Simulation(esoh_model, parameter_values=parameter_values) - esoh_sol = esoh_sim.solve([0], inputs=inputs) + esoh_sol = esoh_sim.solve([0], inputs=all_inputs) x_0, x_100 = esoh_sol["x_0"].data[0], esoh_sol["x_100"].data[0] return x_0, x_100 diff --git a/pybamm/solvers/solution.py b/pybamm/solvers/solution.py index a52441dc8c..440d7381dc 100644 --- a/pybamm/solvers/solution.py +++ b/pybamm/solvers/solution.py @@ -978,12 +978,9 @@ def _get_cycle_summary_variables(cycle_solution, esoh_solver, user_inputs=None): Q_n = last_state["Negative electrode capacity [A.h]"].data[0] Q_p = last_state["Positive electrode capacity [A.h]"].data[0] Q_Li = last_state["Total lithium capacity in particles [A.h]"].data[0] - - esoh_inputs = {"Q_n": Q_n, "Q_p": Q_p, "Q_Li": Q_Li} - user_inputs.update(esoh_inputs) - + all_inputs = {**user_inputs, "Q_n": Q_n, "Q_p": Q_p, "Q_Li": Q_Li} try: - esoh_sol = esoh_solver.solve(inputs=user_inputs) + esoh_sol = esoh_solver.solve(inputs=all_inputs) except pybamm.SolverError as error: # pragma: no cover raise pybamm.SolverError( "Could not solve for summary variables, run " diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py index b56cf0661f..4d06fcb71d 100644 --- a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py +++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py @@ -235,24 +235,14 @@ def test_error(self): class TestElectrodeSOHHalfCell(TestCase): def test_known_solution(self): model = pybamm.lithium_ion.ElectrodeSOHHalfCell() - param = pybamm.LithiumIonParameters({"working electrode": "positive"}) parameter_values = pybamm.ParameterValues("Xu2019") - Q_w = param.p.Q_init - # Q_w = parameter_values.evaluate(param.p.Q_init) - # Add Q_w to parameter values - parameter_values.update( - {"Total lithium in the working electrode [mol]": Q_w}, - check_already_exists=False, - ) + Q_w = parameter_values.evaluate(param.p.Q_init) sim = pybamm.Simulation(model, parameter_values=parameter_values) - V_min = 3.5 V_max = 4.2 - # Q_w = parameter_values.evaluate(param.p.Q_init) - # Solve the model and check outputs - sol = sim.solve([0], inputs={}) + sol = sim.solve([0], inputs={"Q_w": Q_w}) self.assertAlmostEqual(sol["Uw(x_100)"].data[0], V_max, places=5) self.assertAlmostEqual(sol["Uw(x_0)"].data[0], V_min, places=5) diff --git a/tests/unit/test_simulation.py b/tests/unit/test_simulation.py index 253f84b741..d6502e2fe7 100644 --- a/tests/unit/test_simulation.py +++ b/tests/unit/test_simulation.py @@ -215,23 +215,16 @@ def test_solve_with_initial_soc(self): sim.solve(t_eval=[0, 1], initial_soc=0.8, inputs={"eps_p": og_eps_p}) self.assertEqual(sim._built_initial_soc, 0.8) - model = pybamm.lithium_ion.DFN() - parameter_values = pybamm.ParameterValues("Chen2020") - - def graphite_LGM50_ocp_Chen2020(sto): - a = pybamm.Parameter("a") - u_eq = a * ( - 1.9793 * pybamm.exp(-39.3631 * sto) - + 0.2482 - - 0.0909 * pybamm.tanh(29.8538 * (sto - 0.1234)) - - 0.04478 * pybamm.tanh(14.9159 * (sto - 0.2769)) - - 0.0205 * pybamm.tanh(30.4444 * (sto - 0.6103)) - ) - return u_eq - + # test having an input parameter in the ocv function + model = pybamm.lithium_ion.SPM() + parameter_values = model.default_parameter_values + a = pybamm.Parameter("a") + def ocv_with_parameter(sto): + u_eq = (4.2 - 2.5) * (1 - sto) + 2.5 + return a * u_eq parameter_values.update( { - "Negative electrode OCP [V]": graphite_LGM50_ocp_Chen2020, + "Positive electrode OCP [V]": ocv_with_parameter, } ) parameter_values.update({"a": "[input]"}, check_already_exists=False) From 65fd2261265b8895dd941bc6a1e865f13ab63e23 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 23 Mar 2024 22:00:49 +0000 Subject: [PATCH 05/10] style: pre-commit fixes --- tests/unit/test_simulation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/test_simulation.py b/tests/unit/test_simulation.py index d6502e2fe7..85a5ddc2af 100644 --- a/tests/unit/test_simulation.py +++ b/tests/unit/test_simulation.py @@ -219,9 +219,11 @@ def test_solve_with_initial_soc(self): model = pybamm.lithium_ion.SPM() parameter_values = model.default_parameter_values a = pybamm.Parameter("a") + def ocv_with_parameter(sto): u_eq = (4.2 - 2.5) * (1 - sto) + 2.5 return a * u_eq + parameter_values.update( { "Positive electrode OCP [V]": ocv_with_parameter, From 525066978b24fa548a44e24401f00e83d0a79e0b Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Mon, 25 Mar 2024 10:12:51 -0700 Subject: [PATCH 06/10] eric comments and coverage ok --- .../lithium_ion/electrode_soh.py | 2 - tests/unit/test_simulation.py | 38 +++++++++++++++---- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py b/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py index 3169404cd1..ab8af2f24b 100644 --- a/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py +++ b/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py @@ -738,8 +738,6 @@ def get_min_max_stoichiometries(self, inputs=None): param.Q / param.n_electrodes_parallel, inputs=inputs ) all_inputs = {**inputs, "Q_n": Q_n, "Q_p": Q_p, "Q": Q} - else: - all_inputs = inputs # Solve the model and check outputs sol = self.solve(all_inputs) return [sol["x_0"], sol["x_100"], sol["y_100"], sol["y_0"]] diff --git a/tests/unit/test_simulation.py b/tests/unit/test_simulation.py index 85a5ddc2af..6565a6d930 100644 --- a/tests/unit/test_simulation.py +++ b/tests/unit/test_simulation.py @@ -263,6 +263,37 @@ def ocv_with_parameter(sto): voltage = sol["Terminal voltage [V]"].entries self.assertAlmostEqual(voltage[0], ucv, places=5) + # test with MSMR + model = pybamm.lithium_ion.MSMR({"number of MSMR reactions": ("6", "4")}) + param = pybamm.ParameterValues("MSMR_Example") + sim = pybamm.Simulation(model, parameter_values=param) + sim.build(initial_soc=0.5) + self.assertEqual(sim._built_initial_soc, 0.5) + + def test_solve_with_initial_soc_with_input_param_in_ocv(self): + # test having an input parameter in the ocv function + model = pybamm.lithium_ion.SPM() + parameter_values = model.default_parameter_values + a = pybamm.Parameter("a") + + def ocv_with_parameter(sto): + u_eq = (4.2 - 2.5) * (1 - sto) + 2.5 + return a * u_eq + + parameter_values.update( + { + "Positive electrode OCP [V]": ocv_with_parameter, + } + ) + parameter_values.update({"a": "[input]"}, check_already_exists=False) + experiment = pybamm.Experiment(["Discharge at 1C until 2.5 V"]) + sim = pybamm.Simulation( + model, parameter_values=parameter_values, experiment=experiment + ) + sim.solve([0, 3600], inputs={"a": 1}, initial_soc=0.8) + self.assertEqual(sim._built_initial_soc, 0.8) + + def test_esoh_with_input_param(self): # Test that initial soc works with a relevant input parameter model = pybamm.lithium_ion.DFN({"working electrode": "positive"}) param = model.default_parameter_values @@ -274,13 +305,6 @@ def ocv_with_parameter(sto): sim.solve(t_eval=[0, 1], initial_soc=0.8, inputs={"eps_p": original_eps_p}) self.assertEqual(sim._built_initial_soc, 0.8) - # test with MSMR - model = pybamm.lithium_ion.MSMR({"number of MSMR reactions": ("6", "4")}) - param = pybamm.ParameterValues("MSMR_Example") - sim = pybamm.Simulation(model, parameter_values=param) - sim.build(initial_soc=0.5) - self.assertEqual(sim._built_initial_soc, 0.5) - def test_solve_with_inputs(self): model = pybamm.lithium_ion.SPM() param = model.default_parameter_values From 664b277f3b4fb5bd892727939f8fcec81c4b0c72 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Tue, 26 Mar 2024 12:03:19 -0700 Subject: [PATCH 07/10] add safety lines in a few places --- .../lithium_ion/electrode_soh.py | 10 ++++++++++ .../lithium_ion/electrode_soh_half_cell.py | 2 +- .../test_lithium_ion/test_electrode_soh.py | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py b/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py index ab8af2f24b..9f4eba35d3 100644 --- a/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py +++ b/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py @@ -121,6 +121,10 @@ def __init__( Q_Li = pybamm.InputParameter("Q_Li") elif known_value == "cell capacity": Q = pybamm.InputParameter("Q") + else: + raise ValueError( + "Known value must be cell capacity or cyclable lithium capacity" + ) # Define variables for 100% state of charge if "x_100" in solve_for: @@ -207,6 +211,10 @@ def __init__( Q_Li = pybamm.InputParameter("Q_Li") elif known_value == "cell capacity": Q = pybamm.InputParameter("Q") + else: + raise ValueError( + "Known value must be cell capacity or cyclable lithium capacity" + ) # Define variables for 0% state of charge # TODO: thermal effects (include dU/dT) @@ -287,6 +295,8 @@ def __init__( ): self.parameter_values = parameter_values self.param = param or pybamm.LithiumIonParameters(options) + if known_value not in ["cell capacity", "cyclable lithium capacity"]: + raise ValueError("Known value must be cell capacity or cyclable lithium capacity") self.known_value = known_value self.options = options or pybamm.BatteryModelOptions({}) diff --git a/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py b/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py index 0b2d7bd14c..4607e7d417 100644 --- a/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py +++ b/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py @@ -62,9 +62,9 @@ def get_initial_stoichiometry_half_cell( initial_value, parameter_values, param=None, - known_value="cyclable lithium capacity", options=None, inputs=None, + **kwargs, ): """ Calculate initial stoichiometry to start off the simulation at a particular diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py index 4d06fcb71d..b791c9e4cf 100644 --- a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py +++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py @@ -364,6 +364,22 @@ def test_error(self): pybamm.lithium_ion.get_initial_stoichiometry_half_cell( 2, parameter_values_half_cell ) + + with self.assertRaisesRegex( + ValueError, "Known value must be cell capacity or cyclable lithium capacity" + ): + pybamm.lithium_ion.ElectrodeSOHSolver(parameter_values, known_value="something else") + + with self.assertRaisesRegex( + ValueError, "Known value must be cell capacity or cyclable lithium capacity" + ): + param_MSMR = pybamm.lithium_ion.MSMR({"number of MSMR reactions": "3"}).param + pybamm.models.full_battery_models.lithium_ion.electrode_soh._ElectrodeSOHMSMR(param=param_MSMR, known_value="something else") + + with self.assertRaisesRegex( + ValueError, "Known value must be cell capacity or cyclable lithium capacity" + ): + pybamm.models.full_battery_models.lithium_ion.electrode_soh._ElectrodeSOH(known_value="something else") class TestGetInitialOCP(TestCase): From f0c981754edc9ac6008968f5d37b6a8e49f5e8ea Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 26 Mar 2024 19:03:41 +0000 Subject: [PATCH 08/10] style: pre-commit fixes --- .../lithium_ion/electrode_soh.py | 6 ++++-- .../test_lithium_ion/test_electrode_soh.py | 20 +++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py b/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py index 9f4eba35d3..d67a998be6 100644 --- a/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py +++ b/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py @@ -124,7 +124,7 @@ def __init__( else: raise ValueError( "Known value must be cell capacity or cyclable lithium capacity" - ) + ) # Define variables for 100% state of charge if "x_100" in solve_for: @@ -296,7 +296,9 @@ def __init__( self.parameter_values = parameter_values self.param = param or pybamm.LithiumIonParameters(options) if known_value not in ["cell capacity", "cyclable lithium capacity"]: - raise ValueError("Known value must be cell capacity or cyclable lithium capacity") + raise ValueError( + "Known value must be cell capacity or cyclable lithium capacity" + ) self.known_value = known_value self.options = options or pybamm.BatteryModelOptions({}) diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py index b791c9e4cf..dd7d35b683 100644 --- a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py +++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py @@ -364,22 +364,30 @@ def test_error(self): pybamm.lithium_ion.get_initial_stoichiometry_half_cell( 2, parameter_values_half_cell ) - + with self.assertRaisesRegex( ValueError, "Known value must be cell capacity or cyclable lithium capacity" ): - pybamm.lithium_ion.ElectrodeSOHSolver(parameter_values, known_value="something else") + pybamm.lithium_ion.ElectrodeSOHSolver( + parameter_values, known_value="something else" + ) with self.assertRaisesRegex( ValueError, "Known value must be cell capacity or cyclable lithium capacity" ): - param_MSMR = pybamm.lithium_ion.MSMR({"number of MSMR reactions": "3"}).param - pybamm.models.full_battery_models.lithium_ion.electrode_soh._ElectrodeSOHMSMR(param=param_MSMR, known_value="something else") - + param_MSMR = pybamm.lithium_ion.MSMR( + {"number of MSMR reactions": "3"} + ).param + pybamm.models.full_battery_models.lithium_ion.electrode_soh._ElectrodeSOHMSMR( + param=param_MSMR, known_value="something else" + ) + with self.assertRaisesRegex( ValueError, "Known value must be cell capacity or cyclable lithium capacity" ): - pybamm.models.full_battery_models.lithium_ion.electrode_soh._ElectrodeSOH(known_value="something else") + pybamm.models.full_battery_models.lithium_ion.electrode_soh._ElectrodeSOH( + known_value="something else" + ) class TestGetInitialOCP(TestCase): From 58337e2f26f972daf994c2acf6f4e9851dd78678 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 26 Mar 2024 21:39:37 +0000 Subject: [PATCH 09/10] style: pre-commit fixes --- pybamm/simulation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pybamm/simulation.py b/pybamm/simulation.py index bf17f2891a..79d818e966 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -862,7 +862,6 @@ def solve( return self.solution - def run_padding_rest(self, kwargs, rest_time, step_solution, inputs): model = self.steps_to_built_models["Rest for padding"] solver = self.steps_to_built_models["Rest for padding"] From 8b2d812b89c2b56626c34059d757c1309ce14271 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Wed, 27 Mar 2024 10:43:40 -0700 Subject: [PATCH 10/10] fix weird merge bug --- pybamm/simulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/simulation.py b/pybamm/simulation.py index 79d818e966..060d1cdd8c 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -864,7 +864,7 @@ def solve( def run_padding_rest(self, kwargs, rest_time, step_solution, inputs): model = self.steps_to_built_models["Rest for padding"] - solver = self.steps_to_built_models["Rest for padding"] + solver = self.steps_to_built_solvers["Rest for padding"] # Make sure we take at least 2 timesteps. The period is hardcoded to 10 # minutes,the user can always override it by adding a rest step