Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make particle size distribution work with composite electrode. #4687

Merged
merged 21 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Features

- Made composite electrode model compatible with particle size distribution ([#4687](https://github.com/pybamm-team/PyBaMM/pull/4687))
- Added `Symbol.post_order()` method to return an iterable that steps through the tree in post-order fashion. ([#4684](https://github.com/pybamm-team/PyBaMM/pull/4684))
- Added two more submodels (options) for the SEI: Lars von Kolzenberg (2020) model and Tunneling Limit model ([#4394](https://github.com/pybamm-team/PyBaMM/pull/4394))

Expand Down
4 changes: 2 additions & 2 deletions docs/source/examples/notebooks/models/using-submodels.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -527,10 +527,10 @@
" model.param, \"positive\", model.options, cracks=True\n",
")\n",
"model.submodels[\"Negative lithium plating\"] = pybamm.lithium_plating.NoPlating(\n",
" model.param, \"Negative\"\n",
" model.param, \"negative\"\n",
")\n",
"model.submodels[\"Positive lithium plating\"] = pybamm.lithium_plating.NoPlating(\n",
" model.param, \"Positive\"\n",
" model.param, \"positive\"\n",
")"
]
},
Expand Down
55 changes: 49 additions & 6 deletions src/pybamm/expression_tree/averages.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,10 @@
return yz_average(x_average(symbol))


def xyzs_average(symbol: pybamm.Symbol) -> pybamm.Symbol:
return xyz_average(size_average(symbol))


def r_average(symbol: pybamm.Symbol) -> pybamm.Symbol:
"""
Convenience function for creating an average in the r-direction.
Expand Down Expand Up @@ -373,7 +377,15 @@
# If symbol doesn't have a domain, or doesn't have "negative particle size"
# or "positive particle size" as a domain, it's average value is itself
if symbol.domain == [] or not any(
domain in [["negative particle size"], ["positive particle size"]]
domain
in [
["negative particle size"],
["positive particle size"],
["negative primary particle size"],
["positive primary particle size"],
["negative secondary particle size"],
["positive secondary particle size"],
]
for domain in list(symbol.domains.values())
):
return symbol
Expand All @@ -394,13 +406,44 @@
else:
if f_a_dist is None:
geo = pybamm.geometric_parameters
R = pybamm.SpatialVariable(
"R", domains=symbol.domains, coord_sys="cartesian"
)
if ["negative particle size"] in symbol.domains.values():
if "negative" in symbol.domain[0]:
aabills marked this conversation as resolved.
Show resolved Hide resolved
if "primary" in symbol.domain[0]:
R = pybamm.SpatialVariable(

Check warning on line 411 in src/pybamm/expression_tree/averages.py

View check run for this annotation

Codecov / codecov/patch

src/pybamm/expression_tree/averages.py#L411

Added line #L411 was not covered by tests
"R_n_prim", domains=symbol.domains, coord_sys="cartesian"
)
elif "secondary" in symbol.domain[0]:
R = pybamm.SpatialVariable(

Check warning on line 415 in src/pybamm/expression_tree/averages.py

View check run for this annotation

Codecov / codecov/patch

src/pybamm/expression_tree/averages.py#L415

Added line #L415 was not covered by tests
"R_n_sec", domains=symbol.domains, coord_sys="cartesian"
)
else:
R = pybamm.SpatialVariable(
"R_n", domains=symbol.domains, coord_sys="cartesian"
)
else:
if "primary" in symbol.domain[0]:
R = pybamm.SpatialVariable(

Check warning on line 424 in src/pybamm/expression_tree/averages.py

View check run for this annotation

Codecov / codecov/patch

src/pybamm/expression_tree/averages.py#L424

Added line #L424 was not covered by tests
"R_p_prim", domains=symbol.domains, coord_sys="cartesian"
)
elif "secondary" in symbol.domain[0]:
R = pybamm.SpatialVariable(

Check warning on line 428 in src/pybamm/expression_tree/averages.py

View check run for this annotation

Codecov / codecov/patch

src/pybamm/expression_tree/averages.py#L428

Added line #L428 was not covered by tests
"R_p_sec", domains=symbol.domains, coord_sys="cartesian"
)
else:
R = pybamm.SpatialVariable(
"R_p", domains=symbol.domains, coord_sys="cartesian"
)
aabills marked this conversation as resolved.
Show resolved Hide resolved
if ["negative particle size"] in symbol.domains.values() or [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI geo.domain_params["negative"] is equivalent to geo.n

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how to interpret this comment, as far as I can tell, I never use geo.domain_params['negative']. Is there something here you want changed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry I wasn’t clear. I meant that instead of doing an if statement by domain and getting eg geo.n you can do geo.domain_params[domain]. More of a comment then necessarily a suggestion / required change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like I left this comment in the wrong place too lol

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good, thanks

"negative primary particle size"
] in symbol.domains.values():
f_a_dist = geo.n.prim.f_a_dist(R)
elif ["positive particle size"] in symbol.domains.values():
elif ["negative secondary particle size"] in symbol.domains.values():
f_a_dist = geo.n.sec.f_a_dist(R)

Check warning on line 440 in src/pybamm/expression_tree/averages.py

View check run for this annotation

Codecov / codecov/patch

src/pybamm/expression_tree/averages.py#L440

Added line #L440 was not covered by tests
elif ["positive particle size"] in symbol.domains.values() or [
"positive primary particle size"
] in symbol.domains.values():
f_a_dist = geo.p.prim.f_a_dist(R)
elif ["positive secondary particle size"] in symbol.domains.values():
f_a_dist = geo.p.sec.f_a_dist(R)

Check warning on line 446 in src/pybamm/expression_tree/averages.py

View check run for this annotation

Codecov / codecov/patch

src/pybamm/expression_tree/averages.py#L445-L446

Added lines #L445 - L446 were not covered by tests
return SizeAverage(symbol, f_a_dist)


Expand Down
24 changes: 24 additions & 0 deletions src/pybamm/geometry/battery_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,18 @@
"negative particle size": {"R_n": {"min": R_min_n, "max": R_max_n}},
}
)
phases = int(options.negative["particle phases"])
if phases >= 2:
geometry.update(

Check warning on line 88 in src/pybamm/geometry/battery_geometry.py

View check run for this annotation

Codecov / codecov/patch

src/pybamm/geometry/battery_geometry.py#L88

Added line #L88 was not covered by tests
{
"negative primary particle size": {
"R_n_prim": {"min": R_min_n, "max": R_max_n}
},
"negative secondary particle size": {
"R_n_sec": {"min": R_min_n, "max": R_max_n}
},
}
)
if (
options is not None
and options.positive["particle size"] == "distribution"
Expand All @@ -95,6 +107,18 @@
"positive particle size": {"R_p": {"min": R_min_p, "max": R_max_p}},
}
)
phases = int(options.positive["particle phases"])
if phases >= 2:
geometry.update(

Check warning on line 112 in src/pybamm/geometry/battery_geometry.py

View check run for this annotation

Codecov / codecov/patch

src/pybamm/geometry/battery_geometry.py#L112

Added line #L112 was not covered by tests
{
"positive primary particle size": {
"R_p_prim": {"min": R_min_p, "max": R_max_p}
},
"positive secondary particle size": {
"R_p_sec": {"min": R_min_p, "max": R_max_p}
},
}
)

# Add current collector domains
current_collector_dimension = options["dimensionality"]
Expand Down
39 changes: 39 additions & 0 deletions src/pybamm/geometry/standard_spatial_vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
},
coord_sys="cartesian",
)

R_p = pybamm.SpatialVariable(
"R_p",
domain=["positive particle size"],
Expand All @@ -108,6 +109,44 @@
coord_sys="cartesian",
)

R_n_prim = pybamm.SpatialVariable(
"R_n_prim",
domain=["negative primary particle size"],
auxiliary_domains={
"secondary": "negative electrode",
"tertiary": "current collector",
},
coord_sys="cartesian",
)
R_p_prim = pybamm.SpatialVariable(
"R_p_prim",
domain=["positive primary particle size"],
auxiliary_domains={
"secondary": "positive electrode",
"tertiary": "current collector",
},
coord_sys="cartesian",
)

R_n_sec = pybamm.SpatialVariable(
"R_n_sec",
domain=["negative secondary particle size"],
auxiliary_domains={
"secondary": "negative electrode",
"tertiary": "current collector",
},
coord_sys="cartesian",
)
R_p_sec = pybamm.SpatialVariable(
"R_p_sec",
domain=["positive secondary particle size"],
auxiliary_domains={
"secondary": "positive electrode",
"tertiary": "current collector",
},
coord_sys="cartesian",
)

# Domains at cell edges
x_n_edge = pybamm.SpatialVariableEdge(
"x_n",
Expand Down
13 changes: 12 additions & 1 deletion src/pybamm/models/full_battery_models/base_battery_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,6 @@ def __init__(self, extra_options):
if options["particle phases"] not in ["1", ("1", "1")]:
if not (
options["surface form"] != "false"
and options["particle size"] == "single"
and options["particle"] == "Fickian diffusion"
):
raise pybamm.OptionError(
Expand Down Expand Up @@ -868,6 +867,10 @@ def default_var_pts(self):
"z": 10,
"R_n": 30,
"R_p": 30,
"R_n_prim": 30,
"R_p_prim": 30,
"R_n_sec": 30,
"R_p_sec": 30,
}
# Reduce the default points for 2D current collectors
if self.options["dimensionality"] == 2:
Expand All @@ -888,6 +891,10 @@ def default_submesh_types(self):
"positive secondary particle": pybamm.Uniform1DSubMesh,
"negative particle size": pybamm.Uniform1DSubMesh,
"positive particle size": pybamm.Uniform1DSubMesh,
"negative primary particle size": pybamm.Uniform1DSubMesh,
"positive primary particle size": pybamm.Uniform1DSubMesh,
"negative secondary particle size": pybamm.Uniform1DSubMesh,
"positive secondary particle size": pybamm.Uniform1DSubMesh,
}
if self.options["dimensionality"] == 0:
base_submeshes["current collector"] = pybamm.SubMesh0D
Expand All @@ -910,6 +917,10 @@ def default_spatial_methods(self):
"positive secondary particle": pybamm.FiniteVolume(),
"negative particle size": pybamm.FiniteVolume(),
"positive particle size": pybamm.FiniteVolume(),
"negative primary particle size": pybamm.FiniteVolume(),
"positive primary particle size": pybamm.FiniteVolume(),
"negative secondary particle size": pybamm.FiniteVolume(),
"positive secondary particle size": pybamm.FiniteVolume(),
}
if self.options["dimensionality"] == 0:
# 0D submesh - use base spatial method
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,19 @@
R = self.phase_param.R
elif domain_options["particle size"] == "distribution":
if self.domain == "negative":
R_ = pybamm.standard_spatial_vars.R_n
if self.phase_name == "primary ":
R_ = pybamm.standard_spatial_vars.R_n_prim

Check warning on line 85 in src/pybamm/models/submodels/active_material/base_active_material.py

View check run for this annotation

Codecov / codecov/patch

src/pybamm/models/submodels/active_material/base_active_material.py#L85

Added line #L85 was not covered by tests
elif self.phase_name == "secondary ":
R_ = pybamm.standard_spatial_vars.R_n_sec

Check warning on line 87 in src/pybamm/models/submodels/active_material/base_active_material.py

View check run for this annotation

Codecov / codecov/patch

src/pybamm/models/submodels/active_material/base_active_material.py#L87

Added line #L87 was not covered by tests
else:
R_ = pybamm.standard_spatial_vars.R_n
elif self.domain == "positive":
R_ = pybamm.standard_spatial_vars.R_p
if self.phase_name == "primary ":
R_ = pybamm.standard_spatial_vars.R_p_prim

Check warning on line 92 in src/pybamm/models/submodels/active_material/base_active_material.py

View check run for this annotation

Codecov / codecov/patch

src/pybamm/models/submodels/active_material/base_active_material.py#L92

Added line #L92 was not covered by tests
elif self.phase_name == "secondary ":
R_ = pybamm.standard_spatial_vars.R_p_sec

Check warning on line 94 in src/pybamm/models/submodels/active_material/base_active_material.py

View check run for this annotation

Codecov / codecov/patch

src/pybamm/models/submodels/active_material/base_active_material.py#L94

Added line #L94 was not covered by tests
else:
R_ = pybamm.standard_spatial_vars.R_p
R = pybamm.size_average(R_)

R_av = pybamm.x_average(R)
Expand Down
49 changes: 40 additions & 9 deletions src/pybamm/models/submodels/interface/base_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
self.reaction_name = self.phase_name + self.reaction_name

self.reaction = reaction
domain_options = getattr(self.options, domain)
self.size_distribution = domain_options["particle size"] == "distribution"

def _get_exchange_current_density(self, variables):
"""
Expand Down Expand Up @@ -93,8 +95,10 @@
# "current collector"
c_e = pybamm.PrimaryBroadcast(c_e, ["current collector"])
# broadcast c_e, T onto "particle size"
c_e = pybamm.PrimaryBroadcast(c_e, [f"{domain} particle size"])
T = pybamm.PrimaryBroadcast(T, [f"{domain} particle size"])
c_e = pybamm.PrimaryBroadcast(
c_e, [f"{domain} {phase_name}particle size"]
)
T = pybamm.PrimaryBroadcast(T, [f"{domain} {phase_name}particle size"])

else:
c_s_surf = variables[
Expand Down Expand Up @@ -215,13 +219,23 @@

# Size average. For j variables that depend on particle size, see
# "_get_standard_size_distribution_interfacial_current_variables"
if j.domain in [["negative particle size"], ["positive particle size"]]:
if j.domain in [
["negative particle size"],
["positive particle size"],
["negative primary particle size"],
["positive primary particle size"],
["negative secondary particle size"],
["positive secondary particle size"],
]:
j = pybamm.size_average(j)
# Average, and broadcast if necessary
j_av = pybamm.x_average(j)
if j.domain == []:
j = pybamm.FullBroadcast(j, f"{domain} electrode", "current collector")
elif j.domain == ["current collector"]:
elif (
j.domain == ["current collector"]
and getattr(self, "reaction_loc", None) != "interface"
):
j = pybamm.PrimaryBroadcast(j, f"{domain} electrode")

variables = {
Expand All @@ -230,6 +244,13 @@
f"X-averaged {domain} electrode {reaction_name}"
"interfacial current density [A.m-2]": j_av,
}
if self.phase_name == reaction_name:
variables.update(
{
f"{Domain} electrode {reaction_name}interfacial current density [A.m-2]": j,
f"X-averaged {domain} electrode {reaction_name}interfacial current density [A.m-2]": j_av,
}
)

return variables

Expand Down Expand Up @@ -298,10 +319,18 @@
a = variables[
f"{Domain} electrode {phase_name}surface area to volume ratio [m-1]"
]

j = variables[
f"{Domain} electrode {reaction_name}interfacial current density [A.m-2]"
]
if "plating" in reaction_name:
j = variables[
f"{Domain} electrode {reaction_name}interfacial current density [A.m-2]"
]
elif phase_name in reaction_name:
j = variables[
f"{Domain} electrode {reaction_name}interfacial current density [A.m-2]"
]
else:
j = variables[

Check warning on line 331 in src/pybamm/models/submodels/interface/base_interface.py

View check run for this annotation

Codecov / codecov/patch

src/pybamm/models/submodels/interface/base_interface.py#L331

Added line #L331 was not covered by tests
f"{Domain} electrode {phase_name}{reaction_name}interfacial current density [A.m-2]"
]
a_j = a * j
a_j_av = pybamm.x_average(a_j)

Expand Down Expand Up @@ -410,9 +439,11 @@
if j.domains["secondary"] == [f"{domain} electrode"]:
# x-average
j_xav = pybamm.x_average(j)
else:
elif getattr(self, "reaction_loc", None) != "interface":
j_xav = j
j = pybamm.SecondaryBroadcast(j_xav, [f"{domain} electrode"])
else:
j_xav = j

variables = {
f"{Domain} electrode {reaction_name}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ def get_coupled_variables(self, variables):
self.reaction == "lithium-ion main"
and domain_options["particle size"] == "distribution"
):
delta_phi = pybamm.PrimaryBroadcast(delta_phi, [f"{domain} particle size"])
delta_phi = pybamm.PrimaryBroadcast(
delta_phi, [f"{domain} {phase_name}particle size"]
)

# Get exchange-current density. For MSMR models we calculate the exchange
# current density for each reaction, then sum these to give a total exchange
Expand Down Expand Up @@ -169,7 +171,7 @@ def get_coupled_variables(self, variables):
elif j0.domain in ["current collector", ["current collector"]]:
T = variables["X-averaged cell temperature [K]"]
u = variables[f"X-averaged {domain} electrode interface utilisation"]
elif j0.domain == [f"{domain} particle size"]:
elif j0.domain == [f"{domain} {phase_name}particle size"]:
if j0.domains["secondary"] != [f"{domain} electrode"]:
T = variables["X-averaged cell temperature [K]"]
u = variables[f"X-averaged {domain} electrode interface utilisation"]
Expand All @@ -178,7 +180,7 @@ def get_coupled_variables(self, variables):
u = variables[f"{Domain} electrode interface utilisation"]

# Broadcast T onto "particle size" domain
T = pybamm.PrimaryBroadcast(T, [f"{domain} particle size"])
T = pybamm.PrimaryBroadcast(T, [f"{domain} {phase_name}particle size"])
else:
T = variables[f"{Domain} electrode temperature [K]"]
u = variables[f"{Domain} electrode interface utilisation"]
Expand All @@ -198,7 +200,9 @@ def get_coupled_variables(self, variables):
else:
j = self._get_kinetics(j0, ne, eta_r, T, u)

if j.domain == [f"{domain} particle size"]:
if j.domain == [f"{domain} particle size"] or j.domain == [
f"{domain} {phase_name}particle size"
]:
# If j depends on particle size, get size-dependent "distribution"
# variables first
variables.update(
Expand Down
Loading
Loading