Skip to content

Commit 80781e3

Browse files
committed
e
1 parent a3756de commit 80781e3

File tree

12 files changed

+138
-73
lines changed

12 files changed

+138
-73
lines changed

pybamm/geometry/battery_geometry.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,19 @@ def battery_geometry(
6363
}
6464
)
6565
# Add particle size domains
66-
if options is not None and options["particle size"] == "distribution":
66+
if options is not None and options.negative["particle size"] == "distribution":
6767
R_min_n = geo.n.prim.R_min
68-
R_min_p = geo.p.prim.R_min
6968
R_max_n = geo.n.prim.R_max
70-
R_max_p = geo.p.prim.R_max
7169
geometry.update(
7270
{
7371
"negative particle size": {"R_n": {"min": R_min_n, "max": R_max_n}},
72+
}
73+
)
74+
if options is not None and options.positive["particle size"] == "distribution":
75+
R_min_p = geo.p.prim.R_min
76+
R_max_p = geo.p.prim.R_max
77+
geometry.update(
78+
{
7479
"positive particle size": {"R_p": {"min": R_min_p, "max": R_max_p}},
7580
}
7681
)

pybamm/models/full_battery_models/base_battery_model.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,6 @@ class BatteryModelOptions(pybamm.FuzzyDict):
7373
"stress-driven", "reaction-driven", or "stress and reaction-driven".
7474
A 2-tuple can be provided for different behaviour in negative and
7575
positive electrodes.
76-
* "particle phases": str
77-
Number of phases present in the electrode. A 2-tuple can be provided for
78-
different behaviour in negative and positive electrodes.
79-
For example, set to ("2", "1") for a negative electrode with 2 phases,
80-
e.g. graphite and silicon.
8176
* "operating mode" : str
8277
Sets the operating mode for the model. This determines how the current
8378
is set. Can be:
@@ -97,7 +92,19 @@ class BatteryModelOptions(pybamm.FuzzyDict):
9792
* "particle" : str
9893
Sets the submodel to use to describe behaviour within the particle.
9994
Can be "Fickian diffusion" (default), "uniform profile",
100-
"quadratic profile", or "quartic profile".
95+
"quadratic profile", or "quartic profile". A 2-tuple can be provided for
96+
different behaviour in negative and positive electrodes.
97+
* "particle mechanics" : str
98+
Sets the model to account for mechanical effects such as particle
99+
swelling and cracking. Can be "none" (default), "swelling only",
100+
or "swelling and cracking".
101+
A 2-tuple can be provided for different behaviour in negative and
102+
positive electrodes.
103+
* "particle phases": str
104+
Number of phases present in the electrode. A 2-tuple can be provided for
105+
different behaviour in negative and positive electrodes.
106+
For example, set to ("2", "1") for a negative electrode with 2 phases,
107+
e.g. graphite and silicon.
101108
* "particle shape" : str
102109
Sets the model shape of the electrode particles. This is used to
103110
calculate the surface area to volume ratio. Can be "spherical"
@@ -106,12 +113,6 @@ class BatteryModelOptions(pybamm.FuzzyDict):
106113
Sets the model to include a single active particle size or a
107114
distribution of sizes at any macroscale location. Can be "single"
108115
(default) or "distribution". Option applies to both electrodes.
109-
* "particle mechanics" : str
110-
Sets the model to account for mechanical effects such as particle
111-
swelling and cracking. Can be "none" (default), "swelling only",
112-
or "swelling and cracking".
113-
A 2-tuple can be provided for different behaviour in negative and
114-
positive electrodes.
115116
* "SEI" : str
116117
Set the SEI submodel to be used. Options are:
117118
@@ -528,7 +529,7 @@ def __init__(self, extra_options):
528529
):
529530
raise pybamm.OptionError(
530531
"If there are multiple particle phases: 'surface form' cannot be "
531-
"'false', 'particle size' must be 'false', 'particle' must be "
532+
"'false', 'particle size' must be 'single', 'particle' must be "
532533
"'Fickian diffusion'. Also the following must "
533534
"be 'none': 'particle mechanics', "
534535
"'loss of active material', 'lithium plating'"
@@ -554,9 +555,10 @@ def __init__(self, extra_options):
554555
"interface utilisation",
555556
"loss of active material",
556557
"open circuit potential",
557-
"particle mechanics",
558558
"particle",
559+
"particle mechanics",
559560
"particle phases",
561+
"particle size",
560562
"stress-induced diffusion",
561563
]
562564
and isinstance(value, tuple)

pybamm/models/submodels/active_material/base_active_material.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,11 @@ def _get_standard_active_material_variables(self, eps_solid):
8383
# R_n, R_p. For a size distribution, calculate the area-weighted
8484
# mean using the distribution instead. Then the surface area is
8585
# calculated the same way
86-
if self.options["particle size"] == "single":
86+
domain_options = getattr(self.options, domain)
87+
if domain_options["particle size"] == "single":
8788
R = self.phase_param.R
8889
R_dim = self.phase_param.R_dimensional
89-
elif self.options["particle size"] == "distribution":
90+
elif domain_options["particle size"] == "distribution":
9091
if self.domain == "negative":
9192
R_ = pybamm.standard_spatial_vars.R_n
9293
elif self.domain == "positive":

pybamm/models/submodels/interface/base_interface.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ def _get_exchange_current_density(self, variables):
7373
if self.reaction == "lithium-ion main":
7474
# For "particle-size distribution" submodels, take distribution version
7575
# of c_s_surf that depends on particle size.
76-
if self.options["particle size"] == "distribution":
76+
domain_options = getattr(self.options, domain)
77+
if domain_options["particle size"] == "distribution":
7778
c_s_surf = variables[
7879
f"{Domain} {phase_name}particle surface concentration distribution"
7980
]

pybamm/models/submodels/interface/kinetics/base_kinetics.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,17 @@ def get_coupled_variables(self, variables):
7272
delta_phi = delta_phi.orphans[0]
7373
# For "particle-size distribution" models, delta_phi must then be
7474
# broadcast to "particle size" domain
75+
domain_options = getattr(self.options, domain)
7576
if (
7677
self.reaction == "lithium-ion main"
77-
and self.options["particle size"] == "distribution"
78+
and domain_options["particle size"] == "distribution"
7879
):
7980
delta_phi = pybamm.PrimaryBroadcast(delta_phi, [f"{domain} particle size"])
8081

8182
# Get exchange-current density
8283
j0 = self._get_exchange_current_density(variables)
8384
# Get open-circuit potential variables and reaction overpotential
84-
if self.options["particle size"] == "distribution":
85+
if domain_options["particle size"] == "distribution":
8586
ocp = variables[
8687
f"{Domain} electrode {reaction_name}open circuit potential distribution"
8788
]

pybamm/models/submodels/interface/open_circuit_potential/current_sigmoid_ocp.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,26 @@ def get_coupled_variables(self, variables):
1212
m_lith = pybamm.sigmoid(current, 0, k) # for lithation (current < 0)
1313
m_delith = 1 - m_lith # for delithiation (current > 0)
1414

15-
Domain = self.domain.capitalize()
15+
domain, Domain = self.domain_Domain
1616
phase_name = self.phase_name
1717

1818
if self.reaction == "lithium-ion main":
1919
T = variables[f"{Domain} electrode temperature"]
20-
# Particle size distribution is not yet implemented
21-
if self.options["particle size"] != "distribution":
20+
# For "particle-size distribution" models, take distribution version
21+
# of c_s_surf that depends on particle size.
22+
domain_options = getattr(self.options, domain)
23+
if domain_options["particle size"] == "distribution":
24+
c_s_surf = variables[
25+
f"{Domain} {phase_name}particle surface concentration distribution"
26+
]
27+
# If variable was broadcast, take only the orphan
28+
if isinstance(c_s_surf, pybamm.Broadcast) and isinstance(
29+
T, pybamm.Broadcast
30+
):
31+
c_s_surf = c_s_surf.orphans[0]
32+
T = T.orphans[0]
33+
T = pybamm.PrimaryBroadcast(T, [f"{domain} particle size"])
34+
else:
2235
c_s_surf = variables[
2336
f"{Domain} {phase_name}particle surface concentration"
2437
]

pybamm/models/submodels/interface/open_circuit_potential/single_ocp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ def get_coupled_variables(self, variables):
1111
phase_name = self.phase_name
1212

1313
if self.reaction == "lithium-ion main":
14-
1514
T = variables[f"{Domain} electrode temperature"]
1615
# For "particle-size distribution" models, take distribution version
1716
# of c_s_surf that depends on particle size.
18-
if self.options["particle size"] == "distribution":
17+
domain_options = getattr(self.options, domain)
18+
if domain_options["particle size"] == "distribution":
1919
c_s_surf = variables[
2020
f"{Domain} {phase_name}particle surface concentration distribution"
2121
]

pybamm/models/submodels/particle/base_particle.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ class BaseParticle(pybamm.BaseSubModel):
2626
def __init__(self, param, domain, options, phase="primary"):
2727
super().__init__(param, domain, options=options, phase=phase)
2828
# Read from options to see if we have a particle size distribution
29-
self.size_distribution = self.options["particle size"] == "distribution"
29+
domain_options = getattr(self.options, domain)
30+
self.size_distribution = domain_options["particle size"] == "distribution"
3031

3132
def _get_effective_diffusivity(self, c, T):
3233
param = self.param

pybamm/parameters/size_distribution_parameters.py

Lines changed: 64 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def get_size_distribution_parameters(
1717
R_min_p=None,
1818
R_max_n=None,
1919
R_max_p=None,
20+
electrode="both",
2021
):
2122
"""
2223
A convenience method to add standard area-weighted particle-size distribution
@@ -54,50 +55,70 @@ def get_size_distribution_parameters(
5455
R_max_p : float (optional)
5556
Maximum radius in positive electrode, scaled by the mean radius R_p_av.
5657
Default is 5 standard deviations above the mean.
58+
electrode : str (optional)
59+
Which electrode to add parameters for. If "both" (default), size distribution
60+
parameters are added for both electrodes. Otherwise can be "negative" or
61+
"positive" to indicate a half-cell model, in which case size distribution
62+
parameters are only added for a single electrode.
5763
"""
58-
59-
# Radii from given parameter set
60-
R_n_typ = param["Negative particle radius [m]"]
61-
R_p_typ = param["Positive particle radius [m]"]
62-
63-
# Set the mean particle radii for each electrode
64-
R_n_av = R_n_av or R_n_typ
65-
R_p_av = R_p_av or R_p_typ
66-
67-
# Minimum radii
68-
R_min_n = R_min_n or np.max([0, 1 - sd_n * 5])
69-
R_min_p = R_min_p or np.max([0, 1 - sd_p * 5])
70-
71-
# Max radii
72-
R_max_n = R_max_n or (1 + sd_n * 5)
73-
R_max_p = R_max_p or (1 + sd_p * 5)
74-
75-
# Area-weighted particle-size distributions
76-
def f_a_dist_n_dim(R):
77-
return lognormal(R, R_n_av, sd_n * R_n_av)
78-
79-
def f_a_dist_p_dim(R):
80-
return lognormal(R, R_p_av, sd_p * R_p_av)
81-
82-
param.update(
83-
{
84-
"Negative area-weighted mean particle radius [m]": R_n_av,
85-
"Positive area-weighted mean particle radius [m]": R_p_av,
86-
"Negative area-weighted particle-size "
87-
+ "standard deviation [m]": sd_n * R_n_av,
88-
"Positive area-weighted particle-size "
89-
+ "standard deviation [m]": sd_p * R_p_av,
90-
"Negative minimum particle radius [m]": R_min_n * R_n_av,
91-
"Positive minimum particle radius [m]": R_min_p * R_p_av,
92-
"Negative maximum particle radius [m]": R_max_n * R_n_av,
93-
"Positive maximum particle radius [m]": R_max_p * R_p_av,
94-
"Negative area-weighted "
95-
+ "particle-size distribution [m-1]": f_a_dist_n_dim,
96-
"Positive area-weighted "
97-
+ "particle-size distribution [m-1]": f_a_dist_p_dim,
98-
},
99-
check_already_exists=False,
100-
)
64+
if electrode in ["both", "negative"]:
65+
# Radii from given parameter set
66+
R_n_typ = param["Negative particle radius [m]"]
67+
68+
# Set the mean particle radii
69+
R_n_av = R_n_av or R_n_typ
70+
71+
# Minimum radii
72+
R_min_n = R_min_n or np.max([0, 1 - sd_n * 5])
73+
74+
# Max radii
75+
R_max_n = R_max_n or (1 + sd_n * 5)
76+
77+
# Area-weighted particle-size distribution
78+
def f_a_dist_n_dim(R):
79+
return lognormal(R, R_n_av, sd_n * R_n_av)
80+
81+
param.update(
82+
{
83+
"Negative area-weighted mean particle radius [m]": R_n_av,
84+
"Negative area-weighted particle-size "
85+
+ "standard deviation [m]": sd_n * R_n_av,
86+
"Negative minimum particle radius [m]": R_min_n * R_n_av,
87+
"Negative maximum particle radius [m]": R_max_n * R_n_av,
88+
"Negative area-weighted "
89+
+ "particle-size distribution [m-1]": f_a_dist_n_dim,
90+
},
91+
check_already_exists=False,
92+
)
93+
if electrode in ["both", "positive"]:
94+
# Radii from given parameter set
95+
R_p_typ = param["Positive particle radius [m]"]
96+
97+
# Set the mean particle radii
98+
R_p_av = R_p_av or R_p_typ
99+
100+
# Minimum radii
101+
R_min_p = R_min_p or np.max([0, 1 - sd_p * 5])
102+
103+
# Max radii
104+
R_max_p = R_max_p or (1 + sd_p * 5)
105+
106+
# Area-weighted particle-size distribution
107+
def f_a_dist_p_dim(R):
108+
return lognormal(R, R_p_av, sd_p * R_p_av)
109+
110+
param.update(
111+
{
112+
"Positive area-weighted mean particle radius [m]": R_p_av,
113+
"Positive area-weighted particle-size "
114+
+ "standard deviation [m]": sd_p * R_p_av,
115+
"Positive minimum particle radius [m]": R_min_p * R_p_av,
116+
"Positive maximum particle radius [m]": R_max_p * R_p_av,
117+
"Positive area-weighted "
118+
+ "particle-size distribution [m-1]": f_a_dist_p_dim,
119+
},
120+
check_already_exists=False,
121+
)
101122
return param
102123

103124

tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ def test_basic_processing(self):
5959
)
6060
modeltest.test_all()
6161

62+
def test_basic_processing_tuple(self):
63+
options = {"particle size": ("single", "distribution")}
64+
model = pybamm.lithium_ion.DFN(options)
65+
modeltest = tests.StandardModelTest(
66+
model, parameter_values=self.params, var_pts=self.var_pts
67+
)
68+
modeltest.test_all()
69+
6270
def test_uniform_profile(self):
6371
options = {"particle size": "distribution", "particle": "uniform profile"}
6472
model = pybamm.lithium_ion.DFN(options)

0 commit comments

Comments
 (0)