Skip to content

Commit

Permalink
Merge pull request #842 from pybamm-team/issue-247-dfn-as-odes
Browse files Browse the repository at this point in the history
Issue 247 dfn as odes
  • Loading branch information
valentinsulzer committed Feb 24, 2020
2 parents ff885db + 5a0690a commit ea6ed83
Show file tree
Hide file tree
Showing 21 changed files with 258 additions and 66 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Features

- Changed rootfinding algorithm to CasADi, scipy.optimize.root still accessible as an option ([#844](https://github.com/pybamm-team/PyBaMM/pull/844))
- Added capacitance effects to lithium-ion models ([#842](https://github.com/pybamm-team/PyBaMM/pull/842))
- Added NCA parameter set ([#824](https://github.com/pybamm-team/PyBaMM/pull/824))
- Added functionality to `Solution` that automatically gets `t_eval` from the data when simulating drive cycles and performs checks to ensure the output has the required resolution to accurately capture the input current ([#819](https://github.com/pybamm-team/PyBaMM/pull/819))
- Added options to export a solution to matlab or csv ([#811](https://github.com/pybamm-team/PyBaMM/pull/811))
Expand Down
8 changes: 4 additions & 4 deletions pybamm/models/full_battery_models/base_battery_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,10 @@ def options(self, extra_options):

# Options that are incompatible with models
if isinstance(self, pybamm.lithium_ion.BaseModel):
if options["surface form"] is not False:
raise pybamm.OptionError(
"surface form not implemented for lithium-ion models"
)
# if options["surface form"] is not False:
# raise pybamm.OptionError(
# "surface form not implemented for lithium-ion models"
# )
if options["convection"] is True:
raise pybamm.OptionError(
"convection not implemented for lithium-ion models"
Expand Down
34 changes: 25 additions & 9 deletions pybamm/models/full_battery_models/lithium_ion/dfn.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,24 +84,40 @@ def set_particle_submodel(self):

def set_solid_submodel(self):

self.submodels["negative electrode"] = pybamm.electrode.ohm.Full(
self.param, "Negative", self.reactions
)
self.submodels["positive electrode"] = pybamm.electrode.ohm.Full(
self.param, "Positive", self.reactions
)
if self.options["surface form"] is False:
submod_n = pybamm.electrode.ohm.Full(self.param, "Negative", self.reactions)
submod_p = pybamm.electrode.ohm.Full(self.param, "Positive", self.reactions)
else:
submod_n = pybamm.electrode.ohm.SurfaceForm(self.param, "Negative")
submod_p = pybamm.electrode.ohm.SurfaceForm(self.param, "Positive")

self.submodels["negative electrode"] = submod_n
self.submodels["positive electrode"] = submod_p

def set_electrolyte_submodel(self):

electrolyte = pybamm.electrolyte.stefan_maxwell
surf_form = electrolyte.conductivity.surface_potential_form

self.submodels["electrolyte conductivity"] = electrolyte.conductivity.Full(
self.param, self.reactions
)
self.submodels["electrolyte diffusion"] = electrolyte.diffusion.Full(
self.param, self.reactions
)

if self.options["surface form"] is False:
self.submodels["electrolyte conductivity"] = electrolyte.conductivity.Full(
self.param, self.reactions
)
elif self.options["surface form"] == "differential":
for domain in ["Negative", "Separator", "Positive"]:
self.submodels[
domain.lower() + " electrolyte conductivity"
] = surf_form.FullDifferential(self.param, domain, self.reactions)
elif self.options["surface form"] == "algebraic":
for domain in ["Negative", "Separator", "Positive"]:
self.submodels[
domain.lower() + " electrolyte conductivity"
] = surf_form.FullAlgebraic(self.param, domain, self.reactions)

@property
def default_geometry(self):
dimensionality = self.options["dimensionality"]
Expand Down
44 changes: 35 additions & 9 deletions pybamm/models/full_battery_models/lithium_ion/spm.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class SPM(BaseModel):
def __init__(self, options=None, name="Single Particle Model", build=True):
super().__init__(options, name)

self.set_reactions()
self.set_external_circuit_submodel()
self.set_porosity_submodel()
self.set_tortuosity_submodels()
Expand All @@ -57,12 +58,21 @@ def set_convection_submodel(self):

def set_interfacial_submodel(self):

self.submodels[
"negative interface"
] = pybamm.interface.lithium_ion.InverseButlerVolmer(self.param, "Negative")
self.submodels[
"positive interface"
] = pybamm.interface.lithium_ion.InverseButlerVolmer(self.param, "Positive")
if self.options["surface form"] is False:
self.submodels[
"negative interface"
] = pybamm.interface.lithium_ion.InverseButlerVolmer(self.param, "Negative")
self.submodels[
"positive interface"
] = pybamm.interface.lithium_ion.InverseButlerVolmer(self.param, "Positive")
else:
self.submodels[
"negative interface"
] = pybamm.interface.lithium_ion.ButlerVolmer(self.param, "Negative")

self.submodels[
"positive interface"
] = pybamm.interface.lithium_ion.ButlerVolmer(self.param, "Positive")

def set_particle_submodel(self):

Expand Down Expand Up @@ -96,10 +106,26 @@ def set_positive_electrode_submodel(self):
def set_electrolyte_submodel(self):

electrolyte = pybamm.electrolyte.stefan_maxwell
surf_form = electrolyte.conductivity.surface_potential_form

self.submodels[
"electrolyte conductivity"
] = electrolyte.conductivity.LeadingOrder(self.param)
if self.options["surface form"] is False:
self.submodels[
"leading-order electrolyte conductivity"
] = electrolyte.conductivity.LeadingOrder(self.param)

elif self.options["surface form"] == "differential":
for domain in ["Negative", "Separator", "Positive"]:
self.submodels[
"leading-order " + domain.lower() + " electrolyte conductivity"
] = surf_form.LeadingOrderDifferential(
self.param, domain, self.reactions
)

elif self.options["surface form"] == "algebraic":
for domain in ["Negative", "Separator", "Positive"]:
self.submodels[
"leading-order " + domain.lower() + " electrolyte conductivity"
] = surf_form.LeadingOrderAlgebraic(self.param, domain, self.reactions)
self.submodels[
"electrolyte diffusion"
] = electrolyte.diffusion.ConstantConcentration(self.param)
Expand Down
16 changes: 16 additions & 0 deletions pybamm/models/submodels/interface/lithium_ion.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#
from .base_interface import BaseInterface
from . import inverse_kinetics, kinetics
import pybamm


class BaseInterfaceLithiumIon(BaseInterface):
Expand Down Expand Up @@ -43,6 +44,16 @@ def _get_exchange_current_density(self, variables):
c_e = variables[self.domain + " electrolyte concentration"]
T = variables[self.domain + " electrode temperature"]

# If variable was broadcast, take only the orphan
if (
isinstance(c_s_surf, pybamm.Broadcast)
and isinstance(c_e, pybamm.Broadcast)
and isinstance(T, pybamm.Broadcast)
):
c_s_surf = c_s_surf.orphans[0]
c_e = c_e.orphans[0]
T = T.orphans[0]

if self.domain == "Negative":
prefactor = self.param.m_n(T) / self.param.C_r_n

Expand Down Expand Up @@ -75,6 +86,11 @@ def _get_open_circuit_potential(self, variables):
c_s_surf = variables[self.domain + " particle surface concentration"]
T = variables[self.domain + " electrode temperature"]

# If variable was broadcast, take only the orphan
if isinstance(c_s_surf, pybamm.Broadcast) and isinstance(T, pybamm.Broadcast):
c_s_surf = c_s_surf.orphans[0]
T = T.orphans[0]

if self.domain == "Negative":
ocp = self.param.U_n(c_s_surf, T)
dUdT = self.param.dUdT_n(c_s_surf)
Expand Down
5 changes: 4 additions & 1 deletion pybamm/parameters/print_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ def print_parameters(parameters, parameter_values, output_file=None):
proc_symbol = parameter_values.process_symbol(symbol)
if not (
callable(proc_symbol)
or isinstance(proc_symbol, pybamm.Concatenation)
or any(
isinstance(x, (pybamm.Concatenation, pybamm.Broadcast))
for x in proc_symbol.pre_order()
)
):
evaluated_parameters[name].append(proc_symbol.evaluate(t=0))

Expand Down
26 changes: 18 additions & 8 deletions pybamm/parameters/standard_parameters_lithium_ion.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,12 @@
# Electrochemical reactions
ne_n = pybamm.Parameter("Negative electrode electrons in reaction")
ne_p = pybamm.Parameter("Positive electrode electrons in reaction")
C_dl_dimensional = pybamm.Parameter("Double-layer capacity [F.m-2]")
C_dl_n_dimensional = pybamm.Parameter(
"Negative electrode double-layer capacity [F.m-2]"
)
C_dl_p_dimensional = pybamm.Parameter(
"Positive electrode double-layer capacity [F.m-2]"
)


# Initial conditions
Expand Down Expand Up @@ -285,10 +290,15 @@ def U_p_dimensional(sto, T):
centre_z_tab_p = pybamm.geometric_parameters.centre_z_tab_p

# Microscale geometry
var = pybamm.standard_spatial_vars
epsilon_n = pybamm.FunctionParameter("Negative electrode porosity", var.x_n)
epsilon_s = pybamm.FunctionParameter("Separator porosity", var.x_s)
epsilon_p = pybamm.FunctionParameter("Positive electrode porosity", var.x_p)
epsilon_n = pybamm.FunctionParameter(
"Negative electrode porosity", pybamm.standard_spatial_vars.x_n
)
epsilon_s = pybamm.FunctionParameter(
"Separator porosity", pybamm.standard_spatial_vars.x_s
)
epsilon_p = pybamm.FunctionParameter(
"Positive electrode porosity", pybamm.standard_spatial_vars.x_p
)
epsilon = pybamm.Concatenation(epsilon_n, epsilon_s, epsilon_p)
epsilon_s_n = pybamm.Parameter("Negative electrode active material volume fraction")
epsilon_s_p = pybamm.Parameter("Positive electrode active material volume fraction")
Expand All @@ -315,7 +325,7 @@ def U_p_dimensional(sto, T):

# Electrolyte Properties
t_plus = pybamm.Parameter("Cation transference number")
beta_surf = 0
beta_surf = pybamm.Scalar(0)
s = 1 - t_plus


Expand All @@ -329,10 +339,10 @@ def chi(c_e):

# Electrochemical Reactions
C_dl_n = (
C_dl_dimensional * potential_scale / interfacial_current_scale_n / tau_discharge
C_dl_n_dimensional * potential_scale / interfacial_current_scale_n / tau_discharge
)
C_dl_p = (
C_dl_dimensional * potential_scale / interfacial_current_scale_p / tau_discharge
C_dl_p_dimensional * potential_scale / interfacial_current_scale_p / tau_discharge
)

# Electrical
Expand Down
14 changes: 9 additions & 5 deletions pybamm/processed_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,22 +149,24 @@ def initialise_2D(self):
self.entries = entries
self.dimensions = 2
if self.domain[0] in ["negative particle", "positive particle"]:
self.spatial_var_name = "r"
self.first_dimension = "r"
self.r_sol = space
elif self.domain[0] in [
"negative electrode",
"separator",
"positive electrode",
]:
self.spatial_var_name = "x"
self.first_dimension = "x"
self.x_sol = space
elif self.domain == ["current collector"]:
self.spatial_var_name = "z"
self.first_dimension = "z"
self.z_sol = space
else:
self.spatial_var_name = "x"
self.first_dimension = "x"
self.x_sol = space

self.first_dim_pts = space

# set up interpolation
# note that the order of 't' and 'space' is the reverse of what you'd expect

Expand Down Expand Up @@ -242,6 +244,8 @@ def initialise_3D(self):
# assign attributes for reference
self.entries = entries
self.dimensions = 3
self.first_dim_pts = first_dim_pts
self.second_dim_pts = second_dim_pts

# set up interpolation
self._interpolation_function = interp.RegularGridInterpolator(
Expand Down Expand Up @@ -335,7 +339,7 @@ def __call__(self, t=None, x=None, r=None, y=None, z=None, warn=True):

def call_2D(self, t, x, r, z):
"Evaluate a 2D variable"
spatial_var = eval_dimension_name(self.spatial_var_name, x, r, None, z)
spatial_var = eval_dimension_name(self.first_dimension, x, r, None, z)
return self._interpolation_function(t, spatial_var)

def call_3D(self, t, x, r, y, z):
Expand Down
4 changes: 2 additions & 2 deletions pybamm/quick_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,8 @@ def set_output_variables(self, output_variables, solutions):

# Set the x variable for any two-dimensional variables
if first_variable.dimensions == 2:
spatial_variable_key = first_variable.spatial_var_name
spatial_variable_value = first_variable.mesh[0].edges
spatial_variable_key = first_variable.first_dimension
spatial_variable_value = first_variable.first_dim_pts
self.spatial_variable[key] = (
spatial_variable_key,
spatial_variable_value,
Expand Down
18 changes: 9 additions & 9 deletions tests/integration/test_models/standard_output_comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ def test_all(self, skip_first_timestep=False):
self.run_test_class(VariablesComparison, skip_first_timestep)

if self.chemistry == "Lithium-ion":
self.run_test_class(ParticleConcentrationComparison)
self.run_test_class(ParticleConcentrationComparison, skip_first_timestep)
elif self.chemistry == "Lead-acid":
self.run_test_class(PorosityComparison)
self.run_test_class(PorosityComparison, skip_first_timestep)


class BaseOutputComparison(object):
Expand All @@ -67,13 +67,14 @@ def compare(self, var, tol=1e-2):
model_variables = [solution[var] for solution in self.solutions]
var0 = model_variables[0]

if var0.mesh is None:
x = None
else:
x = var0.mesh[0].nodes
spatial_pts = {}
if var0.dimensions >= 2:
spatial_pts[var0.first_dimension] = var0.first_dim_pts
if var0.dimensions >= 3:
spatial_pts[var0.second_dimension] = var0.second_dim_pts

# Calculate tolerance based on the value of var0
maxvar0 = np.max(abs(var0(self.t, x)))
maxvar0 = np.max(abs(var0(self.t, **spatial_pts)))
if maxvar0 < 1e-14:
decimal = -int(np.log10(tol))
else:
Expand All @@ -82,7 +83,7 @@ def compare(self, var, tol=1e-2):
for model_var in model_variables[1:]:
np.testing.assert_equal(var0.dimensions, model_var.dimensions)
np.testing.assert_array_almost_equal(
model_var(self.t, x), var0(self.t, x), decimal
model_var(self.t, **spatial_pts), var0(self.t, **spatial_pts), decimal
)


Expand Down Expand Up @@ -123,7 +124,6 @@ def test_all(self):
self.compare("X-averaged negative electrode open circuit potential")
self.compare("X-averaged positive electrode open circuit potential")
self.compare("Terminal voltage")
self.compare("X-averaged electrolyte overpotential")
self.compare("X-averaged solid phase ohmic losses")
self.compare("Negative electrode reaction overpotential")
self.compare("Positive electrode reaction overpotential")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,17 @@ def test_compare_averages_asymptotics(self):
comparison = StandardOutputComparison(solutions)
comparison.test_averages()

def test_compare_outputs_capacitance(self):
def test_compare_outputs_surface_form(self):
"""
Check that the leading-order model solution converges linearly in C_e to the
full model solution
Check that the models agree with the different surface forms
"""
# load models
options = [
{"surface form": cap} for cap in [False, "differential", "algebraic"]
]
model_combos = [
([pybamm.lead_acid.LOQS(opt) for opt in options]),
([pybamm.lead_acid.Composite(opt) for opt in options]),
([pybamm.lead_acid.Full(opt) for opt in options]),
]

Expand Down
Loading

0 comments on commit ea6ed83

Please sign in to comment.