Skip to content

Commit

Permalink
Merge pull request #1512 from pybamm-team/issue-1511-initial-soc
Browse files Browse the repository at this point in the history
#1511 add function for initial soc
  • Loading branch information
valentinsulzer authored Jun 13, 2021
2 parents 68bc122 + 7c6ef23 commit c8f2174
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 5 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

## Features

- Added `print_name` to some symbols ([#1495](https://github.com/pybamm-team/PyBaMM/pull/1497), [#1495](https://github.com/pybamm-team/PyBaMM/pull/1497))
- Added `initial_soc` argument to `Simualtion.solve` for specifying the initial SOC when solving a model ([#1512](https://github.com/pybamm-team/PyBaMM/pull/1512))
- Added `print_name` to some symbols ([#1495](https://github.com/pybamm-team/PyBaMM/pull/1495), [#1497](https://github.com/pybamm-team/PyBaMM/pull/1497))
- Added Base Parameters class and SymPy in dependencies ([#1495](https://github.com/pybamm-team/PyBaMM/pull/1495))
- Added a new "reaction-driven" model for LAM from Reniers et al (2019) ([#1490](https://github.com/pybamm-team/PyBaMM/pull/1490))
- Some features ("loss of active material" and "particle mechanics") can now be specified separately for the negative electrode and positive electrode by passing a 2-tuple ([#1490](https://github.com/pybamm-team/PyBaMM/pull/1490))
Expand Down
2 changes: 1 addition & 1 deletion pybamm/models/full_battery_models/lithium_ion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Root of the lithium-ion models module.
#
from .base_lithium_ion_model import BaseModel
from .electrode_soh import ElectrodeSOH
from .electrode_soh import ElectrodeSOH, get_initial_stoichiometries
from .spm import SPM
from .spme import SPMe
from .dfn import DFN
Expand Down
54 changes: 54 additions & 0 deletions pybamm/models/full_battery_models/lithium_ion/electrode_soh.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,57 @@ def __init__(self, name="Electrode-specific SOH model"):
def default_solver(self):
# Use AlgebraicSolver as CasadiAlgebraicSolver gives unnecessary warnings
return pybamm.AlgebraicSolver()


def get_initial_stoichiometries(initial_soc, parameter_values):
"""
Calculate initial stoichiometries to start off the simulation at a particular
state of charge, given voltage limits, open-circuit potentials, etc defined by
parameter_values
Parameters
----------
initial_soc : float
Target initial SOC. Must be between 0 and 1.
parameter_values : :class:`pybamm.ParameterValues`
The parameter values class that will be used for the simulation. Required for
calculating appropriate initial stoichiometries.
Returns
-------
x, y
The initial stoichiometries that give the desired initial state of charge
"""
if initial_soc < 0 or initial_soc > 1:
raise ValueError("Initial SOC should be between 0 and 1")

model = pybamm.lithium_ion.ElectrodeSOH()

param = pybamm.LithiumIonParameters()
sim = pybamm.Simulation(model, parameter_values=parameter_values)

V_min = parameter_values.evaluate(param.voltage_low_cut_dimensional)
V_max = parameter_values.evaluate(param.voltage_high_cut_dimensional)
C_n = parameter_values.evaluate(param.C_n_init)
C_p = parameter_values.evaluate(param.C_p_init)
n_Li = parameter_values.evaluate(param.n_Li_particles_init)

# Solve the model and check outputs
sol = sim.solve(
[0],
inputs={
"V_min": V_min,
"V_max": V_max,
"C_n": C_n,
"C_p": C_p,
"n_Li": n_Li,
},
)

x_0 = sol["x_0"].data[0]
y_0 = sol["y_0"].data[0]
C = sol["C"].data[0]
x = x_0 + initial_soc * C / C_n
y = y_0 - initial_soc * C / C_p

return x, y
48 changes: 46 additions & 2 deletions pybamm/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ def __init__(
# Initialize empty built states
self._model_with_set_params = None
self._built_model = None
self._built_initial_soc = None
self.op_conds_to_built_models = None
self._mesh = None
self._disc = None
self._solution = None
Expand Down Expand Up @@ -329,7 +331,6 @@ def set_up_model_for_experiment_old(self, model):
op_cond[:2]: (new_model, self.parameter_values)
for op_cond in set(self.experiment.operating_conditions)
}
self.op_conds_to_built_models = None

def set_up_model_for_experiment_new(self, model):
"""
Expand All @@ -340,7 +341,6 @@ def set_up_model_for_experiment_new(self, model):
reduces simulation time since the model formulation is efficient.
"""
self.op_conds_to_model_and_param = {}
self.op_conds_to_built_models = None
for op_cond, op_inputs in zip(
self.experiment.operating_conditions, self._experiment_inputs
):
Expand Down Expand Up @@ -545,6 +545,7 @@ def solve(
save_at_cycles=None,
calc_esoh=True,
starting_solution=None,
initial_soc=None,
**kwargs,
):
"""
Expand Down Expand Up @@ -585,6 +586,10 @@ def solve(
starting_solution : :class:`pybamm.Solution`
The solution to start stepping from. If None (default), then self._solution
is used. Must be None if not using an experiment.
initial_soc : float, optional
Initial State of Charge (SOC) for the simulation. Must be between 0 and 1.
If given, overwrites the initial concentrations provided in the parameter
set.
**kwargs
Additional key-word arguments passed to `solver.solve`.
See :meth:`pybamm.BaseSolver.solve`.
Expand All @@ -593,6 +598,36 @@ def solve(
if solver is None:
solver = self.solver

if initial_soc is not None:
if self._built_initial_soc != initial_soc:
# reset
self._model_with_set_params = None
self._built_model = None
self.op_conds_to_built_models = None

c_n_init = self.parameter_values[
"Initial concentration in negative electrode [mol.m-3]"
]
c_p_init = self.parameter_values[
"Initial concentration in positive electrode [mol.m-3]"
]
param = pybamm.LithiumIonParameters()
c_n_max = self.parameter_values.evaluate(param.c_n_max)
c_p_max = self.parameter_values.evaluate(param.c_p_max)
x, y = pybamm.lithium_ion.get_initial_stoichiometries(
initial_soc, self.parameter_values
)
self.parameter_values.update(
{
"Initial concentration in negative electrode [mol.m-3]": x
* c_n_max,
"Initial concentration in positive electrode [mol.m-3]": y
* c_p_max,
}
)
# Save solved initial SOC in case we need to re-build the model
self._built_initial_soc = initial_soc

if self.operating_mode in ["without experiment", "drive cycle"]:
self.build(check_model=check_model)
if save_at_cycles is not None:
Expand Down Expand Up @@ -838,6 +873,15 @@ def solve(
"Finish experiment simulation, took {}".format(timer.time())
)

# reset parameter values
if initial_soc is not None:
self.parameter_values.update(
{
"Initial concentration in negative electrode [mol.m-3]": c_n_init,
"Initial concentration in positive electrode [mol.m-3]": c_p_init,
}
)

return self.solution

def step(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def test_known_solution(self):

param = pybamm.LithiumIonParameters()
parameter_values = pybamm.ParameterValues(
chemistry=pybamm.parameter_sets.Marquis2019
chemistry=pybamm.parameter_sets.Mohtat2020
)
sim = pybamm.Simulation(model, parameter_values=parameter_values)

Expand All @@ -38,6 +38,48 @@ def test_known_solution(self):
self.assertAlmostEqual(sol["n_Li_0"].data[0], n_Li, places=5)


class TestSetInitialSOC(unittest.TestCase):
def test_known_solutions(self):
model = pybamm.lithium_ion.ElectrodeSOH()

param = pybamm.LithiumIonParameters()
parameter_values = pybamm.ParameterValues(
chemistry=pybamm.parameter_sets.Mohtat2020
)
sim = pybamm.Simulation(model, parameter_values=parameter_values)

V_min = parameter_values.evaluate(param.voltage_low_cut_dimensional)
V_max = parameter_values.evaluate(param.voltage_high_cut_dimensional)
C_n = parameter_values.evaluate(param.C_n_init)
C_p = parameter_values.evaluate(param.C_p_init)
n_Li = parameter_values.evaluate(param.n_Li_particles_init)

# Solve the model and check outputs
esoh_sol = sim.solve(
[0],
inputs={
"V_min": V_min,
"V_max": V_max,
"C_n": C_n,
"C_p": C_p,
"n_Li": n_Li,
},
)

x, y = pybamm.lithium_ion.get_initial_stoichiometries(1, parameter_values)
self.assertAlmostEqual(x, esoh_sol["x_100"].data[0])
self.assertAlmostEqual(y, esoh_sol["y_100"].data[0])
x, y = pybamm.lithium_ion.get_initial_stoichiometries(0, parameter_values)
self.assertAlmostEqual(x, esoh_sol["x_0"].data[0])
self.assertAlmostEqual(y, esoh_sol["y_0"].data[0])

def test_error(self):
with self.assertRaisesRegex(
ValueError, "Initial SOC should be between 0 and 1"
):
pybamm.lithium_ion.get_initial_stoichiometries(2, None)


if __name__ == "__main__":
print("Add -v for more debug output")
import sys
Expand Down
9 changes: 9 additions & 0 deletions tests/unit/test_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,15 @@ def test_step(self):
self.assertEqual(sim.solution.t[1], dt / tau)
self.assertEqual(sim.solution.t[2], 2 * dt / tau)

def test_solve_with_initial_soc(self):
model = pybamm.lithium_ion.SPM()
param = model.default_parameter_values
sim = pybamm.Simulation(model, parameter_values=param)
sim.solve(t_eval=[0, 600], initial_soc=1)
self.assertEqual(sim._built_initial_soc, 1)
sim.solve(t_eval=[0, 600], 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
Expand Down

0 comments on commit c8f2174

Please sign in to comment.